From aa5d16be7940e94c20866bf59596d0076738f156 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 14 Nov 2016 10:17:42 -0800 Subject: [PATCH] terraform: shadow errors with UUID() must be ignored People with `uuid()` usage in their configurations would receive shadow errors every time on plan because the UUID would change. This is hacky fix but I also believe correct: if a shadow error contains uuid() then we ignore the shadow error completely. This feels wrong but I'll explain why it is likely right: The "right" feeling solution is to create deterministic random output across graph runs. This would require using math/rand and seeding it with the same value each run. However, this alone probably won't work due to Terraform's parallelism and potential to call uuid() in different orders. In addition to this, you can't seed crypto/rand and its unlikely that we'll NEVER use crypto/rand in the future even if we switched uuid() to use math/rand. Therefore, the solution is simple: if there is no shadow error, no problem. If there is a shadow error and it contains uuid(), then ignore it. --- terraform/context_plan_test.go | 19 +++++++++++++++++++ terraform/shadow_context.go | 15 ++++++++++++++- .../test-fixtures/plan-shadow-uuid/main.tf | 3 +++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 terraform/test-fixtures/plan-shadow-uuid/main.tf diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index dc6af4acb..9c92b2f43 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -2010,6 +2010,25 @@ func TestContext2Plan_orphan(t *testing.T) { } } +// This tests that configurations with UUIDs don't produce errors. +// For shadows, this would produce errors since a UUID changes every time. +func TestContext2Plan_shadowUuid(t *testing.T) { + m := testModule(t, "plan-shadow-uuid") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + _, err := ctx.Plan() + if err != nil { + t.Fatalf("err: %s", err) + } +} + func TestContext2Plan_state(t *testing.T) { m := testModule(t, "plan-good") p := testProvider("aws") diff --git a/terraform/shadow_context.go b/terraform/shadow_context.go index 226fd396b..5e0e31609 100644 --- a/terraform/shadow_context.go +++ b/terraform/shadow_context.go @@ -2,6 +2,7 @@ package terraform import ( "fmt" + "strings" "github.com/hashicorp/go-multierror" "github.com/mitchellh/copystructure" @@ -138,5 +139,17 @@ func (c *shadowContextCloser) CloseShadow() error { } func (c *shadowContextCloser) ShadowError() error { - return c.Components.ShadowError() + err := c.Components.ShadowError() + if err == nil { + return nil + } + + // This is a sad edge case: if the configuration contains uuid() at + // any point, we cannot reason aboyt the shadow execution. Tested + // with Context2Plan_shadowUuid. + if strings.Contains(err.Error(), "uuid()") { + err = nil + } + + return err } diff --git a/terraform/test-fixtures/plan-shadow-uuid/main.tf b/terraform/test-fixtures/plan-shadow-uuid/main.tf new file mode 100644 index 000000000..2b6ec72a0 --- /dev/null +++ b/terraform/test-fixtures/plan-shadow-uuid/main.tf @@ -0,0 +1,3 @@ +resource "aws_instance" "test" { + value = "${uuid()}" +}