Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: allow non-computed data source values in "count" #11482

Merged
merged 5 commits into from
Jan 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}"
}