diff --git a/states/instance_object_src.go b/states/instance_object_src.go index 4bb333057..a18cf313c 100644 --- a/states/instance_object_src.go +++ b/states/instance_object_src.go @@ -88,6 +88,7 @@ func (os *ResourceInstanceObjectSrc) Decode(ty cty.Type) (*ResourceInstanceObjec Value: val, Status: os.Status, Dependencies: os.Dependencies, + DependsOn: os.DependsOn, Private: os.Private, }, nil } diff --git a/states/state_deepcopy.go b/states/state_deepcopy.go index dfa1fbc05..7d7a7ef10 100644 --- a/states/state_deepcopy.go +++ b/states/state_deepcopy.go @@ -172,6 +172,7 @@ func (obj *ResourceInstanceObjectSrc) DeepCopy() *ResourceInstanceObjectSrc { AttrsFlat: attrsFlat, AttrsJSON: attrsJSON, Dependencies: dependencies, + DependsOn: dependsOn, } } diff --git a/states/statefile/roundtrip_test.go b/states/statefile/roundtrip_test.go index 81158aa3f..693a9be96 100644 --- a/states/statefile/roundtrip_test.go +++ b/states/statefile/roundtrip_test.go @@ -2,7 +2,6 @@ package statefile import ( "bytes" - "encoding/json" "io/ioutil" "os" "path/filepath" @@ -11,8 +10,6 @@ import ( "testing" "github.com/go-test/deep" - - tfversion "github.com/hashicorp/terraform/version" ) func TestRoundtrip(t *testing.T) { @@ -22,8 +19,6 @@ func TestRoundtrip(t *testing.T) { t.Fatal(err) } - currentVersion := tfversion.Version - for _, info := range entries { const inSuffix = ".in.tfstate" const outSuffix = ".out.tfstate" @@ -39,14 +34,20 @@ func TestRoundtrip(t *testing.T) { outName := name + outSuffix t.Run(name, func(t *testing.T) { - ir, err := os.Open(filepath.Join(dir, inName)) - if err != nil { - t.Fatal(err) - } oSrcWant, err := ioutil.ReadFile(filepath.Join(dir, outName)) if err != nil { t.Fatal(err) } + oWant, diags := readStateV4(oSrcWant) + if diags.HasErrors() { + t.Fatal(diags.Err()) + } + + ir, err := os.Open(filepath.Join(dir, inName)) + if err != nil { + t.Fatal(err) + } + defer ir.Close() f, err := Read(ir) if err != nil { @@ -58,20 +59,12 @@ func TestRoundtrip(t *testing.T) { if err != nil { t.Fatal(err) } - oSrcGot := buf.Bytes() + oSrcWritten := buf.Bytes() - var oGot, oWant map[string]interface{} - err = json.Unmarshal(oSrcGot, &oGot) - if err != nil { - t.Fatalf("result isn't JSON: %s", err) + oGot, diags := readStateV4(oSrcWritten) + if diags.HasErrors() { + t.Fatal(diags.Err()) } - err = json.Unmarshal(oSrcWant, &oWant) - if err != nil { - t.Fatalf("wanted result isn't JSON: %s", err) - } - - // A newly written state should always reflect the current terraform version. - oWant["terraform_version"] = currentVersion problems := deep.Equal(oGot, oWant) sort.Strings(problems) diff --git a/states/statefile/testdata/roundtrip/v4-foreach.in.tfstate b/states/statefile/testdata/roundtrip/v4-foreach.in.tfstate new file mode 100644 index 000000000..0b5085f9a --- /dev/null +++ b/states/statefile/testdata/roundtrip/v4-foreach.in.tfstate @@ -0,0 +1,36 @@ +{ + "version": 4, + "serial": 0, + "lineage": "f2968801-fa14-41ab-a044-224f3a4adf04", + "terraform_version": "0.12.0", + "outputs": { + "numbers": { + "type": "string", + "value": "0,1" + } + }, + "resources": [ + { + "module": "module.modA", + "mode": "managed", + "type": "null_resource", + "name": "resource", + "provider": "provider.null", + "instances": [ + { + "schema_version": 0, + "attributes": { + "id": "4639265839606265182", + "triggers": { + "input": "test" + } + }, + "private": "bnVsbA==", + "depends_on": [ + "var.input" + ] + } + ] + } + ] +} diff --git a/states/statefile/testdata/roundtrip/v4-foreach.out.tfstate b/states/statefile/testdata/roundtrip/v4-foreach.out.tfstate new file mode 120000 index 000000000..d35986e2e --- /dev/null +++ b/states/statefile/testdata/roundtrip/v4-foreach.out.tfstate @@ -0,0 +1 @@ +v4-foreach.in.tfstate \ No newline at end of file diff --git a/states/statefile/testdata/roundtrip/v4-modules.in.tfstate b/states/statefile/testdata/roundtrip/v4-modules.in.tfstate new file mode 100644 index 000000000..af2e62d4a --- /dev/null +++ b/states/statefile/testdata/roundtrip/v4-modules.in.tfstate @@ -0,0 +1,88 @@ +{ + "version": 4, + "terraform_version": "0.12.0", + "serial": 0, + "lineage": "f2968801-fa14-41ab-a044-224f3a4adf04", + "outputs": { + "numbers": { + "value": "0,1", + "type": "string" + } + }, + "resources": [ + { + "mode": "managed", + "type": "null_resource", + "name": "bar", + "provider": "provider.null", + "instances": [ + { + "schema_version": 0, + "attributes_flat": { + "id": "5388490630832483079", + "triggers.%": "1", + "triggers.whaaat": "0,1" + }, + "depends_on": [ + "null_resource.foo" + ] + } + ] + }, + { + "module": "module.modB", + "mode": "managed", + "type": "null_resource", + "name": "bar", + "each": "map", + "provider": "provider.null", + "instances": [ + { + "index_key": "a", + "schema_version": 0, + "attributes_flat": { + "id": "8212585058302700791" + }, + "dependencies": [ + "module.modA.null_resource.resource" + ] + }, + { + "index_key": "b", + "schema_version": 0, + "attributes_flat": { + "id": "1523897709610803586" + }, + "dependencies": [ + "module.modA.null_resource.resource" + ] + } + ] + }, + { + "module": "module.modA", + "mode": "managed", + "type": "null_resource", + "name": "resource", + "provider": "provider.null", + "instances": [ + { + "schema_version": 0, + "attributes": { + "id": "4639265839606265182", + "triggers": { + "input": "test" + } + }, + "private": "bnVsbA==", + "dependencies": [ + "null_resource.bar" + ], + "depends_on": [ + "var.input" + ] + } + ] + } + ] +} diff --git a/states/statefile/testdata/roundtrip/v4-modules.out.tfstate b/states/statefile/testdata/roundtrip/v4-modules.out.tfstate new file mode 120000 index 000000000..009f759ed --- /dev/null +++ b/states/statefile/testdata/roundtrip/v4-modules.out.tfstate @@ -0,0 +1 @@ +v4-modules.in.tfstate \ No newline at end of file diff --git a/states/statefile/version3_upgrade.go b/states/statefile/version3_upgrade.go index fbec5477c..943e0f45c 100644 --- a/states/statefile/version3_upgrade.go +++ b/states/statefile/version3_upgrade.go @@ -313,7 +313,7 @@ func upgradeInstanceObjectV3ToV4(rsOld *resourceStateV2, isOld *instanceStateV2, Status: status, Deposed: string(deposedKey), AttributesFlat: attributes, - Dependencies: dependencies, + DependsOn: dependencies, SchemaVersion: schemaVersion, PrivateRaw: privateJSON, }, nil diff --git a/states/statefile/version4.go b/states/statefile/version4.go index ee8b65236..2cc0677ab 100644 --- a/states/statefile/version4.go +++ b/states/statefile/version4.go @@ -181,7 +181,10 @@ func prepareStateV4(sV4 *stateV4) (*File, tfdiags.Diagnostics) { } { - depsRaw := isV4.Dependencies + // Allow both the deprecated `depends_on` and new + // `dependencies` to coexist for now so resources can be + // upgraded as they are refreshed. + depsRaw := isV4.DependsOn deps := make([]addrs.Referenceable, 0, len(depsRaw)) for _, depRaw := range depsRaw { ref, refDiags := addrs.ParseRefStr(depRaw) @@ -202,6 +205,20 @@ func prepareStateV4(sV4 *stateV4) (*File, tfdiags.Diagnostics) { } deps = append(deps, ref.Subject) } + obj.DependsOn = deps + } + + { + depsRaw := isV4.Dependencies + deps := make([]addrs.AbsResource, 0, len(depsRaw)) + for _, depRaw := range depsRaw { + addr, addrDiags := addrs.ParseAbsResourceStr(depRaw) + diags = diags.Append(addrDiags) + if addrDiags.HasErrors() { + continue + } + deps = append(deps, addr) + } obj.Dependencies = deps } @@ -466,6 +483,11 @@ func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstanc deps[i] = depAddr.String() } + depOn := make([]string, len(obj.DependsOn)) + for i, depAddr := range obj.DependsOn { + depOn[i] = depAddr.String() + } + var rawKey interface{} switch tk := key.(type) { case addrs.IntKey: @@ -491,6 +513,7 @@ func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstanc AttributesRaw: obj.AttrsJSON, PrivateRaw: privateRaw, Dependencies: deps, + DependsOn: depOn, }), diags } @@ -540,7 +563,8 @@ type instanceObjectStateV4 struct { PrivateRaw []byte `json:"private,omitempty"` - Dependencies []string `json:"depends_on,omitempty"` + Dependencies []string `json:"dependencies,omitempty"` + DependsOn []string `json:"depends_on,omitempty"` } // stateVersionV4 is a weird special type we use to produce our hard-coded