Skip to content

Commit

Permalink
Merge pull request #11482 from hashicorp/f-computed-count
Browse files Browse the repository at this point in the history
core: allow non-computed data source values in "count"
  • Loading branch information
mitchellh authored Jan 30, 2017
2 parents 17cb8d8 + dd8ee38 commit 195d344
Show file tree
Hide file tree
Showing 16 changed files with 197 additions and 37 deletions.
44 changes: 44 additions & 0 deletions builtin/providers/test/data_source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package test

import (
"errors"
"fmt"
"strings"
"testing"

Expand Down Expand Up @@ -55,3 +56,46 @@ resource "test_resource" "foo" {
},
})
}

// Test that the output of a data source can be used as the value for
// a "count" in a real resource. This would fail with "count cannot be computed"
// at some point.
func TestDataSource_valueAsResourceCount(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
Providers: testAccProviders,
CheckDestroy: func(s *terraform.State) error {
return nil
},
Steps: []resource.TestStep{
{
Config: strings.TrimSpace(`
data "test_data_source" "test" {
input = "4"
}
resource "test_resource" "foo" {
count = "${data.test_data_source.test.output}"
required = "yep"
required_map = {
key = "value"
}
}
`),
Check: func(s *terraform.State) error {
count := 0
for k, _ := range s.RootModule().Resources {
if strings.HasPrefix(k, "test_resource.foo.") {
count++
}
}

if count != 4 {
return fmt.Errorf("bad count: %d", count)
}
return nil
},
},
},
})
}
2 changes: 1 addition & 1 deletion command/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ func TestPlan_validate(t *testing.T) {
}

actual := ui.ErrorWriter.String()
if !strings.Contains(actual, "can't reference") {
if !strings.Contains(actual, "cannot be computed") {
t.Fatalf("bad: %s", actual)
}
}
Expand Down
16 changes: 5 additions & 11 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down
21 changes: 0 additions & 21 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,34 +254,13 @@ 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 {
t.Fatal("should not be valid")
}
}

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 {
Expand Down
68 changes: 68 additions & 0 deletions terraform/context_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1477,6 +1477,74 @@ func TestContext2Plan_countComputedModule(t *testing.T) {
}
}

func TestContext2Plan_countModuleStatic(t *testing.T) {
m := testModule(t, "plan-count-module-static")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})

plan, err := ctx.Plan()
if err != nil {
t.Fatalf("err: %s", err)
}

actual := strings.TrimSpace(plan.String())
expected := strings.TrimSpace(`
DIFF:
module.child:
CREATE: aws_instance.foo.0
CREATE: aws_instance.foo.1
CREATE: aws_instance.foo.2
STATE:
<no state>
`)
if actual != expected {
t.Fatalf("bad:\n%s", actual)
}
}

func TestContext2Plan_countModuleStaticGrandchild(t *testing.T) {
m := testModule(t, "plan-count-module-static-grandchild")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})

plan, err := ctx.Plan()
if err != nil {
t.Fatalf("err: %s", err)
}

actual := strings.TrimSpace(plan.String())
expected := strings.TrimSpace(`
DIFF:
module.child.child:
CREATE: aws_instance.foo.0
CREATE: aws_instance.foo.1
CREATE: aws_instance.foo.2
STATE:
<no state>
`)
if actual != expected {
t.Fatalf("bad:\n%s", actual)
}
}

func TestContext2Plan_countIndex(t *testing.T) {
m := testModule(t, "plan-count-index")
p := testProvider("aws")
Expand Down
22 changes: 22 additions & 0 deletions terraform/context_validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 4 additions & 0 deletions terraform/eval_sequence.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions terraform/eval_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
11 changes: 10 additions & 1 deletion terraform/node_resource_abstract_count.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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{
Expand Down
10 changes: 7 additions & 3 deletions terraform/node_resource_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
variable "value" {}

resource "aws_instance" "foo" {
count = "${var.value}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
variable "value" {}

module "child" {
source = "./child"
value = "${var.value}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
variable "foo" { default = "3" }

module "child" {
source = "./child"
value = "${var.foo}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
variable "value" {}

resource "aws_instance" "foo" {
count = "${var.value}"
}
6 changes: 6 additions & 0 deletions terraform/test-fixtures/plan-count-module-static/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
variable "foo" { default = "3" }

module "child" {
source = "./child"
value = "${var.foo}"
}
7 changes: 7 additions & 0 deletions terraform/test-fixtures/validate-count-computed/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
data "aws_data_source" "foo" {
compute = "value"
}

resource "aws_instance" "bar" {
count = "${data.aws_data_source.foo.value}"
}

0 comments on commit 195d344

Please sign in to comment.