blob: efb9afeced281afb415328d56c4cbf0af497d70a [file] [log] [blame] [edit]
// 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
}