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