diff --git a/helper/schema/shims.go b/helper/schema/shims.go index 2e6777e68..89f6c6d7b 100644 --- a/helper/schema/shims.go +++ b/helper/schema/shims.go @@ -2,9 +2,7 @@ package schema import ( "encoding/json" - "fmt" - "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config/hcl2shim" "github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/terraform" @@ -40,57 +38,7 @@ func diffFromValues(prior, planned cty.Value, res *Resource, cust CustomizeDiffF // the legacy provider Diff method to the state required for the new // PlanResourceChange method. func ApplyDiff(state cty.Value, d *terraform.InstanceDiff, schemaBlock *configschema.Block) (cty.Value, error) { - // No diff means the state is unchanged. - if d.Empty() { - return state, nil - } - - // Create an InstanceState attributes from our existing state. - // We can use this to more easily apply the diff changes. - attrs := hcl2shim.FlatmapValueFromHCL2(state) - if attrs == nil { - attrs = map[string]string{} - } - - if d.Destroy || d.DestroyDeposed || d.DestroyTainted { - // to mark a destroy, we remove all attributes - attrs = map[string]string{} - } else if attrs["id"] == "" || d.RequiresNew() { - // Since "id" is always computed, make sure it always has a value. Set - // it as unknown to generate the correct cty.Value - attrs["id"] = config.UnknownVariableValue - } - - for attr, diff := range d.Attributes { - old, exists := attrs[attr] - - if exists && - old != diff.Old && - // if new or old is unknown, then there's no mismatch - old != config.UnknownVariableValue && - diff.Old != config.UnknownVariableValue { - return state, fmt.Errorf("mismatched diff: %q != %q", old, diff.Old) - } - - if diff.NewComputed { - attrs[attr] = config.UnknownVariableValue - continue - } - - if diff.NewRemoved { - delete(attrs, attr) - continue - } - - attrs[attr] = diff.New - } - - val, err := hcl2shim.HCL2ValueFromFlatmap(attrs, schemaBlock.ImpliedType()) - if err != nil { - return val, err - } - - return schemaBlock.CoerceValue(val) + return d.ApplyToValue(state, schemaBlock) } // StateValueToJSONMap converts a cty.Value to generic JSON map via the cty JSON @@ -155,12 +103,5 @@ func StateValueFromInstanceState(is *terraform.InstanceState, ty cty.Type) (cty. // the provider, because the legacy providers used the private Meta data in the // InstanceState to store the schema version. func InstanceStateFromStateValue(state cty.Value, schemaVersion int) *terraform.InstanceState { - attrs := hcl2shim.FlatmapValueFromHCL2(state) - return &terraform.InstanceState{ - ID: attrs["id"], - Attributes: attrs, - Meta: map[string]interface{}{ - "schema_version": schemaVersion, - }, - } + return terraform.NewInstanceStateShimmedFromValue(state, schemaVersion) } diff --git a/terraform/diff.go b/terraform/diff.go index 8805b0be8..74a7811c3 100644 --- a/terraform/diff.go +++ b/terraform/diff.go @@ -11,6 +11,10 @@ import ( "sync" "github.com/hashicorp/terraform/addrs" + "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/config/hcl2shim" + "github.com/hashicorp/terraform/configs/configschema" + "github.com/zclconf/go-cty/cty" "github.com/mitchellh/copystructure" ) @@ -404,6 +408,66 @@ type InstanceDiff struct { func (d *InstanceDiff) Lock() { d.mu.Lock() } func (d *InstanceDiff) Unlock() { d.mu.Unlock() } +// ApplyToValue merges the receiver into the given base value, returning a +// new value that incorporates the planned changes. The given value must +// conform to the given schema, or this method will panic. +// +// This method is intended for shimming old subsystems that still use this +// legacy diff type to work with the new-style types. +func (d *InstanceDiff) ApplyToValue(base cty.Value, schema *configschema.Block) (cty.Value, error) { + // No diff means the state is unchanged. + if d.Empty() { + return base, nil + } + + // Create an InstanceState attributes from our existing state. + // We can use this to more easily apply the diff changes. + attrs := hcl2shim.FlatmapValueFromHCL2(base) + if attrs == nil { + attrs = map[string]string{} + } + + if d.Destroy || d.DestroyDeposed || d.DestroyTainted { + // to mark a destroy, we remove all attributes + attrs = map[string]string{} + } else if attrs["id"] == "" || d.RequiresNew() { + // Since "id" is always computed, make sure it always has a value. Set + // it as unknown to generate the correct cty.Value + attrs["id"] = config.UnknownVariableValue + } + + for attr, diff := range d.Attributes { + old, exists := attrs[attr] + + if exists && + old != diff.Old && + // if new or old is unknown, then there's no mismatch + old != config.UnknownVariableValue && + diff.Old != config.UnknownVariableValue { + return base, fmt.Errorf("mismatched diff: %q != %q", old, diff.Old) + } + + if diff.NewComputed { + attrs[attr] = config.UnknownVariableValue + continue + } + + if diff.NewRemoved { + delete(attrs, attr) + continue + } + + attrs[attr] = diff.New + } + + val, err := hcl2shim.HCL2ValueFromFlatmap(attrs, schema.ImpliedType()) + if err != nil { + return val, err + } + + return schema.CoerceValue(val) +} + // ResourceAttrDiff is the diff of a single attribute of a resource. type ResourceAttrDiff struct { Old string // Old Value diff --git a/terraform/state.go b/terraform/state.go index 33064749e..2a1143682 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -17,22 +17,23 @@ import ( "sync" "github.com/hashicorp/errwrap" - "github.com/hashicorp/terraform/plans" - "github.com/hashicorp/go-multierror" "github.com/hashicorp/go-uuid" "github.com/hashicorp/go-version" "github.com/hashicorp/hcl2/hcl" "github.com/hashicorp/hcl2/hcl/hclsyntax" - "github.com/hashicorp/terraform/addrs" - "github.com/hashicorp/terraform/config" - "github.com/hashicorp/terraform/configs" - "github.com/hashicorp/terraform/configs/configschema" - "github.com/hashicorp/terraform/tfdiags" - tfversion "github.com/hashicorp/terraform/version" "github.com/mitchellh/copystructure" "github.com/zclconf/go-cty/cty" ctyjson "github.com/zclconf/go-cty/cty/json" + + "github.com/hashicorp/terraform/addrs" + "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/config/hcl2shim" + "github.com/hashicorp/terraform/configs" + "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/plans" + "github.com/hashicorp/terraform/tfdiags" + tfversion "github.com/hashicorp/terraform/version" ) const ( @@ -1658,6 +1659,22 @@ func (s *InstanceState) init() { s.Ephemeral.init() } +// NewInstanceStateShimmedFromValue is a shim method to lower a new-style +// object value representing the attributes of an instance object into the +// legacy InstanceState representation. +// +// This is for shimming to old components only and should not be used in new code. +func NewInstanceStateShimmedFromValue(state cty.Value, schemaVersion int) *InstanceState { + attrs := hcl2shim.FlatmapValueFromHCL2(state) + return &InstanceState{ + ID: attrs["id"], + Attributes: attrs, + Meta: map[string]interface{}{ + "schema_version": schemaVersion, + }, + } +} + // Copy all the Fields from another InstanceState func (s *InstanceState) Set(from *InstanceState) { s.Lock()