Merge pull request #11482 from hashicorp/f-computed-count
core: allow non-computed data source values in "count"
This commit is contained in:
commit
195d34424e
|
@ -2,6 +2,7 @@ package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -575,7 +575,7 @@ func TestPlan_validate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
actual := ui.ErrorWriter.String()
|
actual := ui.ErrorWriter.String()
|
||||||
if !strings.Contains(actual, "can't reference") {
|
if !strings.Contains(actual, "cannot be computed") {
|
||||||
t.Fatalf("bad: %s", actual)
|
t.Fatalf("bad: %s", actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -505,23 +505,17 @@ func (c *Config) Validate() error {
|
||||||
"%s: resource count can't reference count variable: %s",
|
"%s: resource count can't reference count variable: %s",
|
||||||
n,
|
n,
|
||||||
v.FullKey()))
|
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:
|
case *SimpleVariable:
|
||||||
errs = append(errs, fmt.Errorf(
|
errs = append(errs, fmt.Errorf(
|
||||||
"%s: resource count can't reference variable: %s",
|
"%s: resource count can't reference variable: %s",
|
||||||
n,
|
n,
|
||||||
v.FullKey()))
|
v.FullKey()))
|
||||||
case *UserVariable:
|
|
||||||
// Good
|
// Good
|
||||||
|
case *ModuleVariable:
|
||||||
|
case *ResourceVariable:
|
||||||
|
case *UserVariable:
|
||||||
|
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("Unknown type in count var in %s: %T", n, v))
|
panic(fmt.Sprintf("Unknown type in count var in %s: %T", n, v))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
func TestConfigValidate_countNotInt(t *testing.T) {
|
||||||
c := testConfig(t, "validate-count-not-int")
|
c := testConfig(t, "validate-count-not-int")
|
||||||
if err := c.Validate(); err == nil {
|
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) {
|
func TestConfigValidate_countUserVar(t *testing.T) {
|
||||||
c := testConfig(t, "validate-count-user-var")
|
c := testConfig(t, "validate-count-user-var")
|
||||||
if err := c.Validate(); err != nil {
|
if err := c.Validate(); err != nil {
|
||||||
|
|
|
@ -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) {
|
func TestContext2Plan_countIndex(t *testing.T) {
|
||||||
m := testModule(t, "plan-count-index")
|
m := testModule(t, "plan-count-index")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
|
|
@ -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) {
|
func TestContext2Validate_countNegative(t *testing.T) {
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
m := testModule(t, "validate-count-negative")
|
m := testModule(t, "validate-count-negative")
|
||||||
|
|
|
@ -7,6 +7,10 @@ type EvalSequence struct {
|
||||||
|
|
||||||
func (n *EvalSequence) Eval(ctx EvalContext) (interface{}, error) {
|
func (n *EvalSequence) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
for _, n := range n.Nodes {
|
for _, n := range n.Nodes {
|
||||||
|
if n == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := EvalRaw(n, ctx); err != nil {
|
if _, err := EvalRaw(n, ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ func (n *EvalValidateCount) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
c[n.Resource.RawCount.Key] = "1"
|
c[n.Resource.RawCount.Key] = "1"
|
||||||
count = 1
|
count = 1
|
||||||
}
|
}
|
||||||
|
err = nil
|
||||||
|
|
||||||
if count < 0 {
|
if count < 0 {
|
||||||
errs = append(errs, fmt.Errorf(
|
errs = append(errs, fmt.Errorf(
|
||||||
|
|
|
@ -14,6 +14,14 @@ type NodeAbstractCountResource struct {
|
||||||
|
|
||||||
// GraphNodeEvalable
|
// GraphNodeEvalable
|
||||||
func (n *NodeAbstractCountResource) EvalTree() EvalNode {
|
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{
|
return &EvalSequence{
|
||||||
Nodes: []EvalNode{
|
Nodes: []EvalNode{
|
||||||
// The EvalTree for a plannable resource primarily involves
|
// The EvalTree for a plannable resource primarily involves
|
||||||
|
@ -24,7 +32,8 @@ func (n *NodeAbstractCountResource) EvalTree() EvalNode {
|
||||||
// into the proper number of instances.
|
// into the proper number of instances.
|
||||||
&EvalInterpolate{Config: n.Config.RawCount},
|
&EvalInterpolate{Config: n.Config.RawCount},
|
||||||
|
|
||||||
&EvalCountCheckComputed{Resource: n.Config},
|
// Check if the count is computed
|
||||||
|
evalCountCheckComputed,
|
||||||
|
|
||||||
// If validation is enabled, perform the validation
|
// If validation is enabled, perform the validation
|
||||||
&EvalIf{
|
&EvalIf{
|
||||||
|
|
|
@ -26,10 +26,14 @@ func (n *NodeValidatableResource) DynamicExpand(ctx EvalContext) (*Graph, error)
|
||||||
defer lock.RUnlock()
|
defer lock.RUnlock()
|
||||||
|
|
||||||
// Expand the resource count which must be available by now from EvalTree
|
// Expand the resource count which must be available by now from EvalTree
|
||||||
count, err := n.Config.Count()
|
count := 1
|
||||||
|
if n.Config.RawCount.Value() != unknownValue() {
|
||||||
|
var err error
|
||||||
|
count, err = n.Config.Count()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The concrete resource factory we'll use
|
// The concrete resource factory we'll use
|
||||||
concreteResource := func(a *NodeAbstractResource) dag.Vertex {
|
concreteResource := func(a *NodeAbstractResource) dag.Vertex {
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
variable "value" {}
|
||||||
|
|
||||||
|
resource "aws_instance" "foo" {
|
||||||
|
count = "${var.value}"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
variable "value" {}
|
||||||
|
|
||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
value = "${var.value}"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
variable "foo" { default = "3" }
|
||||||
|
|
||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
value = "${var.foo}"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
variable "value" {}
|
||||||
|
|
||||||
|
resource "aws_instance" "foo" {
|
||||||
|
count = "${var.value}"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
variable "foo" { default = "3" }
|
||||||
|
|
||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
value = "${var.foo}"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
data "aws_data_source" "foo" {
|
||||||
|
compute = "value"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "bar" {
|
||||||
|
count = "${data.aws_data_source.foo.value}"
|
||||||
|
}
|
Loading…
Reference in New Issue