| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: BUSL-1.1 |
| |
| package tfstackdata1 |
| |
| import ( |
| "fmt" |
| |
| "github.com/zclconf/go-cty/cty" |
| "github.com/zclconf/go-cty/cty/msgpack" |
| |
| "github.com/hashicorp/terraform/internal/addrs" |
| "github.com/hashicorp/terraform/internal/lang/marks" |
| "github.com/hashicorp/terraform/internal/plans" |
| "github.com/hashicorp/terraform/internal/plans/planfile" |
| "github.com/hashicorp/terraform/internal/plans/planproto" |
| "github.com/hashicorp/terraform/internal/rpcapi/terraform1/stacks" |
| "github.com/hashicorp/terraform/internal/states" |
| ) |
| |
| func ResourceInstanceObjectStateToTFStackData1(objSrc *states.ResourceInstanceObjectSrc, providerConfigAddr addrs.AbsProviderConfig) *StateResourceInstanceObjectV1 { |
| if objSrc == nil { |
| // This is presumably representing the absense of any prior state, |
| // such as when an object is being planned for creation. |
| return nil |
| } |
| |
| // Hack: we'll borrow NewDynamicValue's treatment of the sensitive |
| // attribute paths here just so we don't need to reimplement the |
| // slice-of-paths conversion in yet another place. We don't |
| // actually do anything with the value part of this. |
| protoValue := stacks.NewDynamicValue(plans.DynamicValue(nil), objSrc.AttrSensitivePaths) |
| rawMsg := &StateResourceInstanceObjectV1{ |
| SchemaVersion: objSrc.SchemaVersion, |
| ValueJson: objSrc.AttrsJSON, |
| SensitivePaths: Terraform1ToPlanProtoAttributePaths(protoValue.Sensitive), |
| CreateBeforeDestroy: objSrc.CreateBeforeDestroy, |
| ProviderConfigAddr: providerConfigAddr.String(), |
| ProviderSpecificData: objSrc.Private, |
| } |
| switch objSrc.Status { |
| case states.ObjectReady: |
| rawMsg.Status = StateResourceInstanceObjectV1_READY |
| case states.ObjectTainted: |
| rawMsg.Status = StateResourceInstanceObjectV1_DAMAGED |
| default: |
| rawMsg.Status = StateResourceInstanceObjectV1_UNKNOWN |
| } |
| |
| rawMsg.Dependencies = make([]string, len(objSrc.Dependencies)) |
| for i, addr := range objSrc.Dependencies { |
| rawMsg.Dependencies[i] = addr.String() |
| } |
| |
| return rawMsg |
| } |
| |
| func Terraform1ToStackDataDynamicValue(value *stacks.DynamicValue) *DynamicValue { |
| return &DynamicValue{ |
| Value: &planproto.DynamicValue{ |
| Msgpack: value.Msgpack, |
| }, |
| SensitivePaths: Terraform1ToPlanProtoAttributePaths(value.Sensitive), |
| } |
| } |
| |
| func DynamicValueFromTFStackData1(protoVal *DynamicValue, ty cty.Type) (cty.Value, error) { |
| raw := protoVal.Value.Msgpack |
| |
| unmarkedV, err := msgpack.Unmarshal(raw, ty) |
| if err != nil { |
| return cty.NilVal, err |
| } |
| |
| var markses []cty.PathValueMarks |
| if len(protoVal.SensitivePaths) != 0 { |
| markses = make([]cty.PathValueMarks, 0, len(protoVal.SensitivePaths)) |
| marks := cty.NewValueMarks(marks.Sensitive) |
| for _, protoPath := range protoVal.SensitivePaths { |
| path, err := planfile.PathFromProto(protoPath) |
| if err != nil { |
| return cty.NilVal, fmt.Errorf("invalid sensitive value path: %w", err) |
| } |
| markses = append(markses, cty.PathValueMarks{ |
| Path: path, |
| Marks: marks, |
| }) |
| } |
| } |
| return unmarkedV.MarkWithPaths(markses), nil |
| } |
| |
| func Terraform1ToPlanProtoAttributePaths(paths []*stacks.AttributePath) []*planproto.Path { |
| if len(paths) == 0 { |
| return nil |
| } |
| ret := make([]*planproto.Path, len(paths)) |
| for i, tf1Path := range paths { |
| ret[i] = Terraform1ToPlanProtoAttributePath(tf1Path) |
| } |
| return ret |
| } |
| |
| func Terraform1ToPlanProtoAttributePath(path *stacks.AttributePath) *planproto.Path { |
| if path == nil { |
| return nil |
| } |
| ret := &planproto.Path{} |
| if len(path.Steps) == 0 { |
| return ret |
| } |
| ret.Steps = make([]*planproto.Path_Step, len(path.Steps)) |
| for i, tf1Step := range path.Steps { |
| ret.Steps[i] = Terraform1ToPlanProtoAttributePathStep(tf1Step) |
| } |
| return ret |
| } |
| |
| func Terraform1ToPlanProtoAttributePathStep(step *stacks.AttributePath_Step) *planproto.Path_Step { |
| if step == nil { |
| return nil |
| } |
| ret := &planproto.Path_Step{} |
| switch sel := step.Selector.(type) { |
| case *stacks.AttributePath_Step_AttributeName: |
| ret.Selector = &planproto.Path_Step_AttributeName{ |
| AttributeName: sel.AttributeName, |
| } |
| case *stacks.AttributePath_Step_ElementKeyInt: |
| encInt, err := msgpack.Marshal(cty.NumberIntVal(sel.ElementKeyInt), cty.Number) |
| if err != nil { |
| // This should not be possible because all integers have a cty msgpack encoding |
| panic(fmt.Sprintf("unencodable element index: %s", err)) |
| } |
| ret.Selector = &planproto.Path_Step_ElementKey{ |
| ElementKey: &planproto.DynamicValue{ |
| Msgpack: encInt, |
| }, |
| } |
| case *stacks.AttributePath_Step_ElementKeyString: |
| encStr, err := msgpack.Marshal(cty.StringVal(sel.ElementKeyString), cty.String) |
| if err != nil { |
| // This should not be possible because all strings have a cty msgpack encoding |
| panic(fmt.Sprintf("unencodable element key: %s", err)) |
| } |
| ret.Selector = &planproto.Path_Step_ElementKey{ |
| ElementKey: &planproto.DynamicValue{ |
| Msgpack: encStr, |
| }, |
| } |
| default: |
| // Should not get here, because the above cases should be exhaustive |
| // for all possible *terraform1.AttributePath_Step selector types. |
| panic(fmt.Sprintf("unsupported path step selector type %T", sel)) |
| } |
| return ret |
| } |
| |
| func DecodeProtoResourceInstanceObject(protoObj *StateResourceInstanceObjectV1) (*states.ResourceInstanceObjectSrc, error) { |
| objSrc := &states.ResourceInstanceObjectSrc{ |
| SchemaVersion: protoObj.SchemaVersion, |
| AttrsJSON: protoObj.ValueJson, |
| CreateBeforeDestroy: protoObj.CreateBeforeDestroy, |
| Private: protoObj.ProviderSpecificData, |
| } |
| |
| switch protoObj.Status { |
| case StateResourceInstanceObjectV1_READY: |
| objSrc.Status = states.ObjectReady |
| case StateResourceInstanceObjectV1_DAMAGED: |
| objSrc.Status = states.ObjectTainted |
| default: |
| return nil, fmt.Errorf("unsupported status %s", protoObj.Status.String()) |
| } |
| |
| paths := make([]cty.Path, 0, len(protoObj.SensitivePaths)) |
| for _, p := range protoObj.SensitivePaths { |
| path, err := planfile.PathFromProto(p) |
| if err != nil { |
| return nil, err |
| } |
| paths = append(paths, path) |
| } |
| objSrc.AttrSensitivePaths = paths |
| |
| if len(protoObj.Dependencies) != 0 { |
| objSrc.Dependencies = make([]addrs.ConfigResource, len(protoObj.Dependencies)) |
| for i, raw := range protoObj.Dependencies { |
| instAddr, diags := addrs.ParseAbsResourceInstanceStr(raw) |
| if diags.HasErrors() { |
| return nil, fmt.Errorf("invalid dependency %q", raw) |
| } |
| // We used the resource instance address parser here but we |
| // actually want the "config resource" subset of that syntax only. |
| configAddr := instAddr.ConfigResource() |
| if configAddr.String() != instAddr.String() { |
| return nil, fmt.Errorf("invalid dependency %q", raw) |
| } |
| objSrc.Dependencies[i] = configAddr |
| } |
| } |
| |
| return objSrc, nil |
| } |