From cf46e1c3e0921fa857be158d1032d898631f59e5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 27 Jan 2017 21:15:43 -0800 Subject: [PATCH] terraform: don't validate computed values in validate This disables the computed value check for `count` during the validation pass. This enables partial support for #3888 or #1497: as long as the value is non-computed during the plan, complex values will work in counts. **Notably, this allows data source values to be present in counts!** The "count" value can be disabled during validation safely because we can treat it as if any field that uses `count.index` is computed for validation. We then validate a single instance (as if `count = 1`) just to make sure all required fields are set. --- config/config.go | 16 +++++--------- config/config_test.go | 21 ------------------ terraform/context_validate_test.go | 22 +++++++++++++++++++ terraform/eval_sequence.go | 4 ++++ terraform/eval_validate.go | 1 + terraform/node_resource_abstract_count.go | 11 +++++++++- terraform/node_resource_validate.go | 10 ++++++--- .../validate-count-computed/main.tf | 7 ++++++ 8 files changed, 56 insertions(+), 36 deletions(-) create mode 100644 terraform/test-fixtures/validate-count-computed/main.tf diff --git a/config/config.go b/config/config.go index 80db548a7..14e35d6fa 100644 --- a/config/config.go +++ b/config/config.go @@ -505,23 +505,17 @@ func (c *Config) Validate() error { "%s: resource count can't reference count variable: %s", n, v.FullKey())) - case *ModuleVariable: - errs = append(errs, fmt.Errorf( - "%s: resource count can't reference module variable: %s", - n, - v.FullKey())) - case *ResourceVariable: - errs = append(errs, fmt.Errorf( - "%s: resource count can't reference resource variable: %s", - n, - v.FullKey())) case *SimpleVariable: errs = append(errs, fmt.Errorf( "%s: resource count can't reference variable: %s", n, v.FullKey())) + + // Good + case *ModuleVariable: + case *ResourceVariable: case *UserVariable: - // Good + default: panic(fmt.Sprintf("Unknown type in count var in %s: %T", n, v)) } diff --git a/config/config_test.go b/config/config_test.go index 95acd28b7..f554061ea 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -254,13 +254,6 @@ func TestConfigValidate_countCountVar(t *testing.T) { } } -func TestConfigValidate_countModuleVar(t *testing.T) { - c := testConfig(t, "validate-count-module-var") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - func TestConfigValidate_countNotInt(t *testing.T) { c := testConfig(t, "validate-count-not-int") if err := c.Validate(); err == nil { @@ -268,20 +261,6 @@ func TestConfigValidate_countNotInt(t *testing.T) { } } -func TestConfigValidate_countResourceVar(t *testing.T) { - c := testConfig(t, "validate-count-resource-var") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_countResourceVarMulti(t *testing.T) { - c := testConfig(t, "validate-count-resource-var-multi") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - func TestConfigValidate_countUserVar(t *testing.T) { c := testConfig(t, "validate-count-user-var") if err := c.Validate(); err != nil { diff --git a/terraform/context_validate_test.go b/terraform/context_validate_test.go index 9e434a7be..e3e7de419 100644 --- a/terraform/context_validate_test.go +++ b/terraform/context_validate_test.go @@ -115,6 +115,28 @@ func TestContext2Validate_computedVar(t *testing.T) { } } +// Test that validate allows through computed counts. We do this and allow +// them to fail during "plan" since we can't know if the computed values +// can be realized during a plan. +func TestContext2Validate_countComputed(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "validate-count-computed") + c := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + t.Fatalf("bad: %s", e) + } +} + func TestContext2Validate_countNegative(t *testing.T) { p := testProvider("aws") m := testModule(t, "validate-count-negative") diff --git a/terraform/eval_sequence.go b/terraform/eval_sequence.go index 6c3c6a620..82d81782a 100644 --- a/terraform/eval_sequence.go +++ b/terraform/eval_sequence.go @@ -7,6 +7,10 @@ type EvalSequence struct { func (n *EvalSequence) Eval(ctx EvalContext) (interface{}, error) { for _, n := range n.Nodes { + if n == nil { + continue + } + if _, err := EvalRaw(n, ctx); err != nil { return nil, err } diff --git a/terraform/eval_validate.go b/terraform/eval_validate.go index 9ae221aa1..a2c122d6a 100644 --- a/terraform/eval_validate.go +++ b/terraform/eval_validate.go @@ -42,6 +42,7 @@ func (n *EvalValidateCount) Eval(ctx EvalContext) (interface{}, error) { c[n.Resource.RawCount.Key] = "1" count = 1 } + err = nil if count < 0 { errs = append(errs, fmt.Errorf( diff --git a/terraform/node_resource_abstract_count.go b/terraform/node_resource_abstract_count.go index 9b4df757f..573570d8e 100644 --- a/terraform/node_resource_abstract_count.go +++ b/terraform/node_resource_abstract_count.go @@ -14,6 +14,14 @@ type NodeAbstractCountResource struct { // GraphNodeEvalable func (n *NodeAbstractCountResource) EvalTree() EvalNode { + // We only check if the count is computed if we're not validating. + // If we're validating we allow computed counts since they just turn + // into more computed values. + var evalCountCheckComputed EvalNode + if !n.Validate { + evalCountCheckComputed = &EvalCountCheckComputed{Resource: n.Config} + } + return &EvalSequence{ Nodes: []EvalNode{ // The EvalTree for a plannable resource primarily involves @@ -24,7 +32,8 @@ func (n *NodeAbstractCountResource) EvalTree() EvalNode { // into the proper number of instances. &EvalInterpolate{Config: n.Config.RawCount}, - &EvalCountCheckComputed{Resource: n.Config}, + // Check if the count is computed + evalCountCheckComputed, // If validation is enabled, perform the validation &EvalIf{ diff --git a/terraform/node_resource_validate.go b/terraform/node_resource_validate.go index 3ea5bfd12..e01518de4 100644 --- a/terraform/node_resource_validate.go +++ b/terraform/node_resource_validate.go @@ -26,9 +26,13 @@ func (n *NodeValidatableResource) DynamicExpand(ctx EvalContext) (*Graph, error) defer lock.RUnlock() // Expand the resource count which must be available by now from EvalTree - count, err := n.Config.Count() - if err != nil { - return nil, err + count := 1 + if n.Config.RawCount.Value() != unknownValue() { + var err error + count, err = n.Config.Count() + if err != nil { + return nil, err + } } // The concrete resource factory we'll use diff --git a/terraform/test-fixtures/validate-count-computed/main.tf b/terraform/test-fixtures/validate-count-computed/main.tf new file mode 100644 index 000000000..e7de125f2 --- /dev/null +++ b/terraform/test-fixtures/validate-count-computed/main.tf @@ -0,0 +1,7 @@ +data "aws_data_source" "foo" { + compute = "value" +} + +resource "aws_instance" "bar" { + count = "${data.aws_data_source.foo.value}" +}