github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/shims.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package schema 7 8 import ( 9 "encoding/json" 10 11 "github.com/zclconf/go-cty/cty" 12 ctyjson "github.com/zclconf/go-cty/cty/json" 13 14 "github.com/opentofu/opentofu/internal/configs/configschema" 15 "github.com/opentofu/opentofu/internal/configs/hcl2shim" 16 "github.com/opentofu/opentofu/internal/legacy/tofu" 17 ) 18 19 // DiffFromValues takes the current state and desired state as cty.Values and 20 // derives a tofu.InstanceDiff to give to the legacy providers. This is 21 // used to take the states provided by the new ApplyResourceChange method and 22 // convert them to a state+diff required for the legacy Apply method. 23 func DiffFromValues(prior, planned cty.Value, res *Resource) (*tofu.InstanceDiff, error) { 24 return diffFromValues(prior, planned, res, nil) 25 } 26 27 // diffFromValues takes an additional CustomizeDiffFunc, so we can generate our 28 // test fixtures from the legacy tests. In the new provider protocol the diff 29 // only needs to be created for the apply operation, and any customizations 30 // have already been done. 31 func diffFromValues(prior, planned cty.Value, res *Resource, cust CustomizeDiffFunc) (*tofu.InstanceDiff, error) { 32 instanceState, err := res.ShimInstanceStateFromValue(prior) 33 if err != nil { 34 return nil, err 35 } 36 37 configSchema := res.CoreConfigSchema() 38 39 cfg := tofu.NewResourceConfigShimmed(planned, configSchema) 40 removeConfigUnknowns(cfg.Config) 41 removeConfigUnknowns(cfg.Raw) 42 43 diff, err := schemaMap(res.Schema).Diff(instanceState, cfg, cust, nil, false) 44 if err != nil { 45 return nil, err 46 } 47 48 return diff, err 49 } 50 51 // During apply the only unknown values are those which are to be computed by 52 // the resource itself. These may have been marked as unknown config values, and 53 // need to be removed to prevent the UnknownVariableValue from appearing the diff. 54 func removeConfigUnknowns(cfg map[string]interface{}) { 55 for k, v := range cfg { 56 switch v := v.(type) { 57 case string: 58 if v == hcl2shim.UnknownVariableValue { 59 delete(cfg, k) 60 } 61 case []interface{}: 62 for _, i := range v { 63 if m, ok := i.(map[string]interface{}); ok { 64 removeConfigUnknowns(m) 65 } 66 } 67 case map[string]interface{}: 68 removeConfigUnknowns(v) 69 } 70 } 71 } 72 73 // ApplyDiff takes a cty.Value state and applies a tofu.InstanceDiff to 74 // get a new cty.Value state. This is used to convert the diff returned from 75 // the legacy provider Diff method to the state required for the new 76 // PlanResourceChange method. 77 func ApplyDiff(base cty.Value, d *tofu.InstanceDiff, schema *configschema.Block) (cty.Value, error) { 78 return d.ApplyToValue(base, schema) 79 } 80 81 // StateValueToJSONMap converts a cty.Value to generic JSON map via the cty JSON 82 // encoding. 83 func StateValueToJSONMap(val cty.Value, ty cty.Type) (map[string]interface{}, error) { 84 js, err := ctyjson.Marshal(val, ty) 85 if err != nil { 86 return nil, err 87 } 88 89 var m map[string]interface{} 90 if err := json.Unmarshal(js, &m); err != nil { 91 return nil, err 92 } 93 94 return m, nil 95 } 96 97 // JSONMapToStateValue takes a generic json map[string]interface{} and converts it 98 // to the specific type, ensuring that the values conform to the schema. 99 func JSONMapToStateValue(m map[string]interface{}, block *configschema.Block) (cty.Value, error) { 100 var val cty.Value 101 102 js, err := json.Marshal(m) 103 if err != nil { 104 return val, err 105 } 106 107 val, err = ctyjson.Unmarshal(js, block.ImpliedType()) 108 if err != nil { 109 return val, err 110 } 111 112 return block.CoerceValue(val) 113 } 114 115 // StateValueFromInstanceState converts a tofu.InstanceState to a 116 // cty.Value as described by the provided cty.Type, and maintains the resource 117 // ID as the "id" attribute. 118 func StateValueFromInstanceState(is *tofu.InstanceState, ty cty.Type) (cty.Value, error) { 119 return is.AttrsAsObjectValue(ty) 120 }