package schema import ( "encoding/json" "github.com/zclconf/go-cty/cty" ctyjson "github.com/zclconf/go-cty/cty/json" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/configs/hcl2shim" "github.com/hashicorp/terraform/internal/legacy/terraform" ) // DiffFromValues takes the current state and desired state as cty.Values and // derives a terraform.InstanceDiff to give to the legacy providers. This is // used to take the states provided by the new ApplyResourceChange method and // convert them to a state+diff required for the legacy Apply method. func DiffFromValues(prior, planned cty.Value, res *Resource) (*terraform.InstanceDiff, error) { return diffFromValues(prior, planned, res, nil) } // diffFromValues takes an additional CustomizeDiffFunc, so we can generate our // test fixtures from the legacy tests. In the new provider protocol the diff // only needs to be created for the apply operation, and any customizations // have already been done. func diffFromValues(prior, planned cty.Value, res *Resource, cust CustomizeDiffFunc) (*terraform.InstanceDiff, error) { instanceState, err := res.ShimInstanceStateFromValue(prior) if err != nil { return nil, err } configSchema := res.CoreConfigSchema() cfg := terraform.NewResourceConfigShimmed(planned, configSchema) removeConfigUnknowns(cfg.Config) removeConfigUnknowns(cfg.Raw) diff, err := schemaMap(res.Schema).Diff(instanceState, cfg, cust, nil, false) if err != nil { return nil, err } return diff, err } // During apply the only unknown values are those which are to be computed by // the resource itself. These may have been marked as unknown config values, and // need to be removed to prevent the UnknownVariableValue from appearing the diff. func removeConfigUnknowns(cfg map[string]interface{}) { for k, v := range cfg { switch v := v.(type) { case string: if v == hcl2shim.UnknownVariableValue { delete(cfg, k) } case []interface{}: for _, i := range v { if m, ok := i.(map[string]interface{}); ok { removeConfigUnknowns(m) } } case map[string]interface{}: removeConfigUnknowns(v) } } } // ApplyDiff takes a cty.Value state and applies a terraform.InstanceDiff to // get a new cty.Value state. This is used to convert the diff returned from // the legacy provider Diff method to the state required for the new // PlanResourceChange method. func ApplyDiff(base cty.Value, d *terraform.InstanceDiff, schema *configschema.Block) (cty.Value, error) { return d.ApplyToValue(base, schema) } // StateValueToJSONMap converts a cty.Value to generic JSON map via the cty JSON // encoding. func StateValueToJSONMap(val cty.Value, ty cty.Type) (map[string]interface{}, error) { js, err := ctyjson.Marshal(val, ty) if err != nil { return nil, err } var m map[string]interface{} if err := json.Unmarshal(js, &m); err != nil { return nil, err } return m, nil } // JSONMapToStateValue takes a generic json map[string]interface{} and converts it // to the specific type, ensuring that the values conform to the schema. func JSONMapToStateValue(m map[string]interface{}, block *configschema.Block) (cty.Value, error) { var val cty.Value js, err := json.Marshal(m) if err != nil { return val, err } val, err = ctyjson.Unmarshal(js, block.ImpliedType()) if err != nil { return val, err } return block.CoerceValue(val) } // StateValueFromInstanceState converts a terraform.InstanceState to a // cty.Value as described by the provided cty.Type, and maintains the resource // ID as the "id" attribute. func StateValueFromInstanceState(is *terraform.InstanceState, ty cty.Type) (cty.Value, error) { return is.AttrsAsObjectValue(ty) }