Commit 75ef61c7 authored by James Bardin's avatar James Bardin
Browse files

check for nested module index changes

Changing only the index on a nested module will cause all nested moves
to create cycles, since their full addresses will match both the From
and To addresses. When building the dependency graph, check if the
parent is only changing the index of the containing module, and prevent
the backwards edge for the move.
parent e7611175
main MatthewTestBranch add-cont-valid-callout add-internals-to-sidebar add-jsonstate-to-cloudbackendstate add-jsonstate-to-cloudbackendstate2 add-new-intro-docs add-note-about-spaces add-tutorial-custom-conditions add-version-notes-1.2 add-warnings-backends alisdair/disable-preconditions-postconditions alisdair/fix-configload-snapshot-panic alisdair/metadata-functions-command alisdair/resource-instance-object-dependencies b-check-output-multi-expand b-check-resource-multi-expand b-flatten-panic b-move-implied-cross-package b-type-conversion-funcs-null b-yamldecode-emptydoc-null backport/29156_do_not_log_sensitive_values/nearly-safe-bullfrog backport/4th-alternate-mirror-directory-fix/closely-key-civet backport/add-cont-valid-callout/luckily-golden-wildcat backport/add-format-function-guidance/ultimately-lucky-halibut backport/add-internals-to-sidebar/willingly-bright-sunbird backport/add-version-notes-1.2/amazingly-premium-woodcock backport/add-warnings-backends/physically-many-mantis backport/alisdair/pre-convert-optional-defaults/virtually-able-mouse backport/b-check-resource-multi-expand/extremely-key-sheep backport/backport/cstella84/patch-add-hyperlink-for-referenced-argument/manually-patient-locust backport/bugfix_typos/hardly-sharp-mosquito backport/clarify-backend-state-storage/ultimately-causal-ladybird backport/cstella84/patch-add-hyperlink-for-referenced-argument backport/doc-s3-fix/utterly-close-rabbit backport/docs-fix-typo/usefully-blessed-monkey backport/docs-for-each-list-toset/basically-still-zebra backport/docs/unknwon-value/completely-musical-lionfish backport/f-build-go1.19.3/largely-peaceful-grouper backport/fix-apt-page/abnormally-relative-quagga backport/fix-backends-link/strangely-emerging-crane backport/fix-backends-link/vertically-noble-hornet backport/fix-cdktf-link/highly-pumped-snapper backport/fix-glossary-table-contents/uniquely-pro-garfish backport/fix-grammar/precisely-polite-tortoise backport/fix-internals-overview/globally-allowed-kid backport/fix-internals-overview/noticeably-up-rodent backport/fix-links-devdot/strictly-notable-sparrow backport/jbardin/cancel-auto-approve/extremely-brave-bear backport/jbardin/static-validate-nested-types/possibly-crack-mouse backport/kevin/rewrite-internal-redirects/quietly-helped-pelican backport/main/cleanly-mature-scorpion backport/mg_no_code_prov_followup/marginally-relevant-eagle backport/mktg-tf-76ef54dc3c574e032725e0341be8e1d2/distinctly-sharp-ferret backport/mktg-tf-76ef54dc3c574e032725e0341be8e1d2/friendly-evident-grouse backport/module-invocation-warning/fully-fitting-buzzard backport/nvanthao/update-docs-implicit-provider/locally-neutral-lemur backport/optional-type-attributes-note/inherently-dear-goat backport/patch-1/gladly-mature-oarfish backport/patch-1/manually-fine-mantis backport/patch-1/nationally-working-kite backport/patch-1/noticeably-comic-manatee backport/patch-1/rarely-informed-gopher backport/patch-1/sensibly-saving-swine backport/patch-1/usually-clear-shad backport/patch-1/vaguely-deciding-beagle backport/patch-1/virtually-more-rhino backport/patch-1/wholly-verified-racer backport/patch-1/willingly-usable-husky backport/patch-1/yearly-rich-skunk backport/patch-2/badly-game-spider backport/patch-2/finally-amazed-catfish backport/patch-2/openly-clean-tick backport/patch-2/weekly-selected-tiger backport/remove-future-statement-import/briefly-viable-glowworm backport/remove-provisioners/readily-correct-ferret backport/remove-provisioners/widely-singular-hound backport/startsswith-to-startswith/highly-gorgeous-katydid backport/system-parameter/infinitely-open-bluebird backport/update-cloud-block-pages/verbally-key-kangaroo backport/update-plan-page/lightly-outgoing-halibut backport/update-run-task-result/factually-star-sunfish backport/update_docs_for_30072/gradually-trusting-wahoo backport/workspaces-confusion-fixes/secondly-huge-titmouse barrettclark/fix-state-outputs-read-permissions brandonc/changelog_nested_sensitive brandonc/changelog_sensitive_diff_fixes brandonc/cloud_upgrade_013 brandonc/nested_attr_sensitive brandonc/output_cloud_reads brandonc/providers-estimate brandonc/scheme_override_cloud build-pr-checks build-workflow-dev/cgo-enabled build-workflow-dev/liamcervante/equivalence-test-action bump-gcp-storage-dependency bump-gcp-storage-dependency-2 dependabot/go_modules/github.com/bmatcuk/doublestar-1.3.4 dev-portal-updates-docs dividers-devdot-fixes doc-provisioner-scp doc-unicode-hcl doc-yamlencode-stable docs-for-each-list-toset docs-readme-updates-versioned-docs f-addrs-static-checkable f-build-go1.19.3 f-cli-hide-fast-refresh f-cmd-web f-diagnostics-cli-reorg f-dynamic-provider-assignment f-e2etest-deps-forbidden f-expand-root-outputs f-fileexists-errmsg f-functions-in-providers f-init-provider-source-feedback f-jsonstate-2 f-moduletest-2 f-new-build-pipeline f-ng-workflow f-output-value-types f-partial-plan-on-error f-partial-plan-on-error-ui f-persistent-checks-old f-testing-with-conditions f/azurerm-backend-msal fix-apt-page fix-broken-link fix-broken-links-1-10 fix-cdktf-link fix-dividers-for-devdot fix-future-facing-language fix-future-lang-2 fix-internals-overview fix-intro-page-images fix-last-intro-nits fix-links-devdot fix-postconditions-example fix-preconditions fix-provisioners-content fix-readme-again gcs-backend-add-kms gcs-backend-add-private-connect-support gcs-refactor-credential-handling gs/add-pre-plan-run-tasks jbardin/1.3-destroy-perf jbardin/backport-31576 jbardin/call-plan-destroy jbardin/data-source-destroy-edges jbardin/output-perf jbardin/plan-orphan-deleted jbardin/remove-deprecated-backends jbardin/resolved-provided-by jbardin/terraform-data jbardin/terraform-null jbardin/trigger-replacement jbardin/variable-eval kevin/local-preview-post-split kevin/preview kevin/remove-guides-docs kevin/vercel-config kmoe/init-checksum-miss-error kmoe/misc-help-text kmoe/unused-resource-attributes lafentres/refactor-show-command laura-update-docs-readme laura-update-pre-post-conditions liamcervante/cicd-go-vet liamcervante/structured-run-output link_workflow_tutorials migrate-go-tfe-1_0 preapply-runtasks-cli-output preapply-runtasks-clioutput release-notes-env-credentials rt-backport-changelog sebasslash/add-cloud-e2e-test-workflow sebasslash/add-tf-hostname-env-var sebasslash/add-tf-org-env-var sebasslash/env-cloud-e2e-tests sebasslash/err-approval-input-false sebasslash/resolve-flaky-env-var-test sebasslash/tf-workspace-cloud-config tchupp/override-local-vars test-branch-protection-workflow uk1288/fix-for-cloud-integration-panic uk1288/update-changelog-md update-TF-WORKSPACE-variable update-cidrnetmask-docs update-depends-on-docs update-packaging-action-name update_gen_meta uturunku1-patch-1 uturunku1-patch-2 v1.4.0-alpha20221109 v1.3.5 v1.3.4 v1.3.3 v1.3.2 v1.3.1 v1.3.0 v1.3.0-rc1 v1.3.0-dev v1.3.0-beta1 v1.3.0-alpha20220817 v1.3.0-alpha20220803 v1.3.0-alpha20220706 v1.3.0-alpha20220622 v1.3.0-alpha20220608 v1.2.9 v1.2.8 v1.2.7 v1.2.6 v1.2.5 v1.2.4 v1.2.3 v1.2.2 v1.2.1 v1.2.0 v1.2.0-rc2 v1.2.0-rc1 v1.2.0-beta1 v1.2.0-alpha20220413 v1.2.0-alpha-20220328
No related merge requests found
Showing with 109 additions and 34 deletions
+109 -34
......@@ -705,10 +705,10 @@ func (r AbsResourceInstance) MoveDestination(fromMatch, toMatch *MoveEndpointInM
}
}
// IsModuleMoveReIndex takes the from and to endpoints from a move statement,
// and returns true if the only changes are to module indexes, and all
// non-absolute paths remain the same.
func IsModuleMoveReIndex(from, to *MoveEndpointInModule) bool {
// IsModuleReIndex takes the From and To endpoints from a single move
// statement, and returns true if the only changes are to module indexes, and
// all non-absolute paths remain the same.
func (from *MoveEndpointInModule) IsModuleReIndex(to *MoveEndpointInModule) bool {
// The statements must originate from the same module.
if !from.module.Equal(to.module) {
panic("cannot compare move expressions from different modules")
......@@ -718,37 +718,21 @@ func IsModuleMoveReIndex(from, to *MoveEndpointInModule) bool {
case AbsModuleCall:
switch t := to.relSubject.(type) {
case ModuleInstance:
if len(t) != 1 {
// An AbsModuleCall only ever has one segment, so the
// ModuleInstance length must match.
return false
}
return f.Call.Name == t[0].Name
// Generate a synthetic module to represent the full address of
// the module call. We're not actually comparing indexes, so the
// instance doesn't matter.
callAddr := f.Instance(NoKey).Module()
return callAddr.Equal(t.Module())
}
case ModuleInstance:
switch t := to.relSubject.(type) {
case AbsModuleCall:
if len(f) != 1 {
return false
}
return f[0].Name == t.Call.Name
callAddr := t.Instance(NoKey).Module()
return callAddr.Equal(f.Module())
case ModuleInstance:
// We must have the same number of segments, and the names must all
// match in order for this to solely be an index change operation.
if len(f) != len(t) {
return false
}
for i := range f {
if f[i].Name != t[i].Name {
return false
}
}
return true
return t.Module().Equal(f.Module())
}
}
......
......@@ -1686,6 +1686,25 @@ func TestIsModuleMoveReIndex(t *testing.T) {
},
expect: false,
},
{
from: AbsModuleCall{
Module: mustParseModuleInstanceStr(`module.bar[0]`),
Call: ModuleCall{Name: "baz"},
},
to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
to: AbsModuleCall{
Module: mustParseModuleInstanceStr(`module.bar[0]`),
Call: ModuleCall{Name: "baz"},
},
expect: true,
},
{
from: mustParseModuleInstanceStr(`module.baz`),
to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
......@@ -1709,7 +1728,7 @@ func TestIsModuleMoveReIndex(t *testing.T) {
relSubject: test.to,
}
if got := IsModuleMoveReIndex(from, to); got != test.expect {
if got := from.IsModuleReIndex(to); got != test.expect {
t.Errorf("expected %t, got %t", test.expect, got)
}
},
......
......@@ -238,11 +238,31 @@ func statementDependsOn(a, b *MoveStatement) bool {
//
// Since we are only interested in checking if A depends on B, we only need
// to check the 4 possibilities above which result in B being executed
// first.
return a.From.NestedWithin(b.To) ||
a.To.NestedWithin(b.To) ||
b.From.NestedWithin(a.From) ||
b.To.NestedWithin(a.From)
// first. If we're there's no dependency at all we can return immediately.
if !(a.From.NestedWithin(b.To) || a.To.NestedWithin(b.To) ||
b.From.NestedWithin(a.From) || b.To.NestedWithin(a.From)) {
return false
}
// If a nested move has a dependency, we need to rule out the possibility
// that this is a move inside a module only changing indexes. If an
// ancestor module is only changing the index of a nested module, any
// nested move statements are going to match both the From and To address
// when the base name is not changing, causing a cycle in the order of
// operations.
// if A is not declared in an ancestor module, then we can't be nested
// within a module index change.
if len(a.To.Module()) >= len(b.To.Module()) {
return true
}
// We only want the nested move statement to depend on the outer module
// move, so we only test this in the reverse direction.
if a.From.IsModuleReIndex(a.To) {
return false
}
return true
}
// MoveResults describes the outcome of an ApplyMoves call.
......
......@@ -404,6 +404,58 @@ Each resource can have moved from only one source resource.`,
},
WantError: `Resource type mismatch: This statement declares a move from test.nonexist1[0] to other.single, which is a resource instance of a different type.`,
},
"crossing nested statements": {
// overlapping nested moves will result in a cycle.
Statements: []MoveStatement{
makeTestMoveStmt(t, ``,
`module.nonexist.test.single`,
`module.count[0].test.count[0]`,
),
makeTestMoveStmt(t, ``,
`module.nonexist`,
`module.count[0]`,
),
},
WantError: `Cyclic dependency in move statements: The following chained move statements form a cycle, and so there is no final location to move objects to:
- test:1,1: module.nonexist → module.count[0]
- test:1,1: module.nonexist.test.single → module.count[0].test.count[0]
A chain of move statements must end with an address that doesn't appear in any other statements, and which typically also refers to an object still declared in the configuration.`,
},
"fully contained nested statements": {
// we have to avoid a cycle because the nested moves appear in both
// the from and to address of the parent when only the module index
// is changing.
Statements: []MoveStatement{
makeTestMoveStmt(t, `count`,
`test.count`,
`test.count[0]`,
),
makeTestMoveStmt(t, ``,
`module.count`,
`module.count[0]`,
),
},
},
"double fully contained nested statements": {
// we have to avoid a cycle because the nested moves appear in both
// the from and to address of the parent when only the module index
// is changing.
Statements: []MoveStatement{
makeTestMoveStmt(t, `count`,
`module.count`,
`module.count[0]`,
),
makeTestMoveStmt(t, `count.count`,
`test.count`,
`test.count[0]`,
),
makeTestMoveStmt(t, ``,
`module.count`,
`module.count[0]`,
),
},
},
}
for name, test := range tests {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment