core: Don't DynamicExpand during validate
Previously we would attempt to DynamicExpand during the validate walk and then validate each expanded instance separately. However, this meant that we would not be able to validate the contents of a block where count = 0 or if count is not yet known. Here we instead do a more static validation pass against the resource configuration itself, setting count.index to cty.UnknownVal(cty.Number) so we can type-check everything inside the block as being correct regardless of the final count. This is another step towards repairing the "validate" command for our changed assumptions in a world where we have a more sophisticated type checker. This doesn't yet address the remaining problem that the expression evaluator can't, with the current state structures, distinguish between a completed resource with count = 0 and a resource that doesn't exist at all (during validate), and so we'll still get errors if an expression elsewhere in configuration refers to a dynamic index of a resource with "count" set. That's a pre-existing condition that's no longer being masked by _this_ problem, but can't be addressed until we've introduced the new state types (states.State, etc) and thus we _can_ distinguish these two situations. That will therefore be addressed in a later commit.
This commit is contained in:
parent
fd371d838d
commit
cf4a5e6336
|
@ -4102,3 +4102,120 @@ func TestContext2Plan_computedAttrRefTypeMismatch(t *testing.T) {
|
|||
t.Fatalf("expected:\n\n%s\n\nto contain:\n\n%s", errStr, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_selfRef(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
p.GetSchemaReturn = &ProviderSchema{
|
||||
ResourceTypes: map[string]*configschema.Block{
|
||||
"aws_instance": {
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.String, Optional: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
m := testModule(t, "plan-self-ref")
|
||||
c := testContext2(t, &ContextOpts{
|
||||
Config: m,
|
||||
ProviderResolver: ResourceProviderResolverFixed(
|
||||
map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
),
|
||||
})
|
||||
|
||||
diags := c.Validate()
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected validation failure: %s", diags.Err())
|
||||
}
|
||||
|
||||
_, diags = c.Plan()
|
||||
if !diags.HasErrors() {
|
||||
t.Fatalf("plan succeeded; want error")
|
||||
}
|
||||
|
||||
gotErrStr := diags.Err().Error()
|
||||
wantErrStr := "Self-referential block"
|
||||
if !strings.Contains(gotErrStr, wantErrStr) {
|
||||
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_selfRefMulti(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
p.GetSchemaReturn = &ProviderSchema{
|
||||
ResourceTypes: map[string]*configschema.Block{
|
||||
"aws_instance": {
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.String, Optional: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
m := testModule(t, "plan-self-ref-multi")
|
||||
c := testContext2(t, &ContextOpts{
|
||||
Config: m,
|
||||
ProviderResolver: ResourceProviderResolverFixed(
|
||||
map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
),
|
||||
})
|
||||
|
||||
diags := c.Validate()
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected validation failure: %s", diags.Err())
|
||||
}
|
||||
|
||||
_, diags = c.Plan()
|
||||
if !diags.HasErrors() {
|
||||
t.Fatalf("plan succeeded; want error")
|
||||
}
|
||||
|
||||
gotErrStr := diags.Err().Error()
|
||||
wantErrStr := "Self-referential block"
|
||||
if !strings.Contains(gotErrStr, wantErrStr) {
|
||||
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_selfRefMultiAll(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
p.GetSchemaReturn = &ProviderSchema{
|
||||
ResourceTypes: map[string]*configschema.Block{
|
||||
"aws_instance": {
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.List(cty.String), Optional: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
m := testModule(t, "plan-self-ref-multi-all")
|
||||
c := testContext2(t, &ContextOpts{
|
||||
Config: m,
|
||||
ProviderResolver: ResourceProviderResolverFixed(
|
||||
map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
),
|
||||
})
|
||||
|
||||
diags := c.Validate()
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected validation failure: %s", diags.Err())
|
||||
}
|
||||
|
||||
_, diags = c.Plan()
|
||||
if !diags.HasErrors() {
|
||||
t.Fatalf("plan succeeded; want error")
|
||||
}
|
||||
|
||||
gotErrStr := diags.Err().Error()
|
||||
wantErrStr := "Self-referential block"
|
||||
if !strings.Contains(gotErrStr, wantErrStr) {
|
||||
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -853,90 +853,6 @@ func TestContext2Validate_resourceConfig_good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestContext2Validate_selfRef(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
p.GetSchemaReturn = &ProviderSchema{
|
||||
ResourceTypes: map[string]*configschema.Block{
|
||||
"aws_instance": {
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.String, Optional: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
m := testModule(t, "validate-self-ref")
|
||||
c := testContext2(t, &ContextOpts{
|
||||
Config: m,
|
||||
ProviderResolver: ResourceProviderResolverFixed(
|
||||
map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
),
|
||||
})
|
||||
|
||||
diags := c.Validate()
|
||||
if !diags.HasErrors() {
|
||||
t.Fatalf("succeeded; want error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Validate_selfRefMulti(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
p.GetSchemaReturn = &ProviderSchema{
|
||||
ResourceTypes: map[string]*configschema.Block{
|
||||
"aws_instance": {
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.String, Optional: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
m := testModule(t, "validate-self-ref-multi")
|
||||
c := testContext2(t, &ContextOpts{
|
||||
Config: m,
|
||||
ProviderResolver: ResourceProviderResolverFixed(
|
||||
map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
),
|
||||
})
|
||||
|
||||
diags := c.Validate()
|
||||
if !diags.HasErrors() {
|
||||
t.Fatalf("succeeded; want error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Validate_selfRefMultiAll(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
p.GetSchemaReturn = &ProviderSchema{
|
||||
ResourceTypes: map[string]*configschema.Block{
|
||||
"aws_instance": {
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.List(cty.String), Optional: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
m := testModule(t, "validate-self-ref-multi-all")
|
||||
c := testContext2(t, &ContextOpts{
|
||||
Config: m,
|
||||
ProviderResolver: ResourceProviderResolverFixed(
|
||||
map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
),
|
||||
})
|
||||
|
||||
diags := c.Validate()
|
||||
if !diags.HasErrors() {
|
||||
t.Fatalf("succeeded; want error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Validate_tainted(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
p.GetSchemaReturn = &ProviderSchema{
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/hashicorp/terraform/configs"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
)
|
||||
|
||||
|
@ -122,11 +123,12 @@ func (n *EvalValidateProvider) Eval(ctx EvalContext) (interface{}, error) {
|
|||
// EvalValidateProvisioner is an EvalNode implementation that validates
|
||||
// the configuration of a provisioner belonging to a resource.
|
||||
type EvalValidateProvisioner struct {
|
||||
ResourceAddr addrs.ResourceInstance
|
||||
Provisioner *ResourceProvisioner
|
||||
Schema **configschema.Block
|
||||
Config *configs.Provisioner
|
||||
ConnConfig *configs.Connection
|
||||
ResourceAddr addrs.Resource
|
||||
Provisioner *ResourceProvisioner
|
||||
Schema **configschema.Block
|
||||
Config *configs.Provisioner
|
||||
ConnConfig *configs.Connection
|
||||
ResourceHasCount bool
|
||||
}
|
||||
|
||||
func (n *EvalValidateProvisioner) Eval(ctx EvalContext) (interface{}, error) {
|
||||
|
@ -142,9 +144,7 @@ func (n *EvalValidateProvisioner) Eval(ctx EvalContext) (interface{}, error) {
|
|||
{
|
||||
// Validate the provisioner's own config first
|
||||
|
||||
keyData := EvalDataForInstanceKey(n.ResourceAddr.Key)
|
||||
|
||||
configVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema, n.ResourceAddr, keyData)
|
||||
configVal, _, configDiags := n.evaluateBlock(ctx, config.Config, schema)
|
||||
diags = diags.Append(configDiags)
|
||||
if configDiags.HasErrors() {
|
||||
return nil, diags.Err()
|
||||
|
@ -199,17 +199,36 @@ func (n *EvalValidateProvisioner) validateConnConfig(ctx EvalContext, config *co
|
|||
return diags
|
||||
}
|
||||
|
||||
keyData := EvalDataForInstanceKey(n.ResourceAddr.Key)
|
||||
|
||||
// We evaluate here just by evaluating the block and returning any
|
||||
// diagnostics we get, since evaluation alone is enough to check for
|
||||
// extraneous arguments and incorrectly-typed arguments.
|
||||
_, _, configDiags := ctx.EvaluateBlock(config.Config, connectionBlockSupersetSchema, self, keyData)
|
||||
_, _, configDiags := n.evaluateBlock(ctx, config.Config, connectionBlockSupersetSchema)
|
||||
diags = diags.Append(configDiags)
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func (n *EvalValidateProvisioner) evaluateBlock(ctx EvalContext, body hcl.Body, schema *configschema.Block) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
|
||||
keyData := EvalDataForNoInstanceKey
|
||||
selfAddr := n.ResourceAddr.Instance(addrs.NoKey)
|
||||
|
||||
if n.ResourceHasCount {
|
||||
// For a resource that has count, we allow count.index but don't
|
||||
// know at this stage what it will return.
|
||||
keyData = InstanceKeyEvalData{
|
||||
CountIndex: cty.UnknownVal(cty.Number),
|
||||
}
|
||||
|
||||
// "self" can't point to an unknown key, but we'll force it to be
|
||||
// key 0 here, which should return an unknown value of the
|
||||
// expected type since none of these elements are known at this
|
||||
// point anyway.
|
||||
selfAddr = n.ResourceAddr.Instance(addrs.IntKey(0))
|
||||
}
|
||||
|
||||
return ctx.EvaluateBlock(body, schema, selfAddr, keyData)
|
||||
}
|
||||
|
||||
// connectionBlockSupersetSchema is a schema representing the superset of all
|
||||
// possible arguments for "connection" blocks across all supported connection
|
||||
// types.
|
||||
|
@ -318,7 +337,7 @@ var connectionBlockSupersetSchema = &configschema.Block{
|
|||
// EvalValidateResource is an EvalNode implementation that validates
|
||||
// the configuration of a resource.
|
||||
type EvalValidateResource struct {
|
||||
Addr addrs.ResourceInstance
|
||||
Addr addrs.Resource
|
||||
Provider *ResourceProvider
|
||||
ProviderSchema **ProviderSchema
|
||||
Config *configs.Resource
|
||||
|
@ -346,6 +365,21 @@ func (n *EvalValidateResource) Eval(ctx EvalContext) (interface{}, error) {
|
|||
schema := *n.ProviderSchema
|
||||
mode := cfg.Mode
|
||||
|
||||
keyData := EvalDataForNoInstanceKey
|
||||
if n.Config.Count != nil {
|
||||
// If the config block has count, we'll evaluate with an unknown
|
||||
// number as count.index so we can still type check even though
|
||||
// we won't expand count until the plan phase.
|
||||
keyData = InstanceKeyEvalData{
|
||||
CountIndex: cty.UnknownVal(cty.Number),
|
||||
}
|
||||
|
||||
// Basic type-checking of the count argument. More complete validation
|
||||
// of this will happen when we DynamicExpand during the plan walk.
|
||||
countDiags := n.validateCount(ctx, n.Config.Count)
|
||||
diags = diags.Append(countDiags)
|
||||
}
|
||||
|
||||
var warns []string
|
||||
var errs []error
|
||||
|
||||
|
@ -365,8 +399,6 @@ func (n *EvalValidateResource) Eval(ctx EvalContext) (interface{}, error) {
|
|||
return nil, diags.Err()
|
||||
}
|
||||
|
||||
keyData := EvalDataForInstanceKey(n.Addr.Key)
|
||||
|
||||
configVal, _, valDiags := ctx.EvaluateBlock(cfg.Config, schema, nil, keyData)
|
||||
diags = diags.Append(valDiags)
|
||||
if valDiags.HasErrors() {
|
||||
|
@ -394,8 +426,6 @@ func (n *EvalValidateResource) Eval(ctx EvalContext) (interface{}, error) {
|
|||
return nil, diags.Err()
|
||||
}
|
||||
|
||||
keyData := EvalDataForInstanceKey(n.Addr.Key)
|
||||
|
||||
configVal, _, valDiags := ctx.EvaluateBlock(cfg.Config, schema, nil, keyData)
|
||||
diags = diags.Append(valDiags)
|
||||
if valDiags.HasErrors() {
|
||||
|
@ -434,3 +464,71 @@ func (n *EvalValidateResource) Eval(ctx EvalContext) (interface{}, error) {
|
|||
return nil, diags.NonFatalErr()
|
||||
}
|
||||
}
|
||||
|
||||
func (n *EvalValidateResource) validateCount(ctx EvalContext, expr hcl.Expression) tfdiags.Diagnostics {
|
||||
if expr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
countVal, countDiags := ctx.EvaluateExpr(expr, cty.Number, nil)
|
||||
diags = diags.Append(countDiags)
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
||||
if countVal.IsNull() {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid count argument",
|
||||
Detail: `The given "count" argument value is null. An integer is required.`,
|
||||
Subject: expr.Range().Ptr(),
|
||||
})
|
||||
return diags
|
||||
}
|
||||
|
||||
var err error
|
||||
countVal, err = convert.Convert(countVal, cty.Number)
|
||||
if err != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid count argument",
|
||||
Detail: fmt.Sprintf(`The given "count" argument value is unsuitable: %s.`, err),
|
||||
Subject: expr.Range().Ptr(),
|
||||
})
|
||||
return diags
|
||||
}
|
||||
|
||||
// If the value isn't known then that's the best we can do for now, but
|
||||
// we'll check more thoroughly during the plan walk.
|
||||
if !countVal.IsKnown() {
|
||||
return diags
|
||||
}
|
||||
|
||||
// If we _do_ know the value, then we can do a few more checks here.
|
||||
var count int
|
||||
err = gocty.FromCtyValue(countVal, &count)
|
||||
if err != nil {
|
||||
// Isn't a whole number, etc.
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid count argument",
|
||||
Detail: fmt.Sprintf(`The given "count" argument value is unsuitable: %s.`, err),
|
||||
Subject: expr.Range().Ptr(),
|
||||
})
|
||||
return diags
|
||||
}
|
||||
|
||||
if count < 0 {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid count argument",
|
||||
Detail: `The given "count" argument value is unsuitable: count cannot be negative.`,
|
||||
Subject: expr.Range().Ptr(),
|
||||
})
|
||||
return diags
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/hcl2/hcltest"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
|
@ -39,12 +40,59 @@ func TestEvalValidateResource_managedResource(t *testing.T) {
|
|||
}),
|
||||
}
|
||||
node := &EvalValidateResource{
|
||||
Addr: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "foo",
|
||||
},
|
||||
Addr: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "foo",
|
||||
},
|
||||
Provider: &p,
|
||||
Config: rc,
|
||||
ProviderSchema: &mp.GetSchemaReturn,
|
||||
}
|
||||
|
||||
ctx := &MockEvalContext{}
|
||||
ctx.installSimpleEval()
|
||||
|
||||
_, err := node.Eval(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !mp.ValidateResourceCalled {
|
||||
t.Fatal("Expected ValidateResource to be called, but it was not!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalValidateResource_managedResourceCount(t *testing.T) {
|
||||
mp := simpleMockProvider()
|
||||
mp.ValidateResourceFn = func(rt string, c *ResourceConfig) (ws []string, es []error) {
|
||||
expected := "test_object"
|
||||
if rt != expected {
|
||||
t.Fatalf("wrong resource type\ngot: %#v\nwant: %#v", rt, expected)
|
||||
}
|
||||
expected = "bar"
|
||||
val, _ := c.Get("test_string")
|
||||
if val != expected {
|
||||
t.Fatalf("wrong value for test_string\ngot: %#v\nwant: %#v", val, expected)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
p := ResourceProvider(mp)
|
||||
rc := &configs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_object",
|
||||
Name: "foo",
|
||||
Count: hcltest.MockExprLiteral(cty.NumberIntVal(2)),
|
||||
Config: configs.SynthBody("", map[string]cty.Value{
|
||||
"test_string": cty.StringVal("bar"),
|
||||
}),
|
||||
}
|
||||
node := &EvalValidateResource{
|
||||
Addr: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "foo",
|
||||
},
|
||||
Provider: &p,
|
||||
Config: rc,
|
||||
|
@ -90,12 +138,10 @@ func TestEvalValidateResource_dataSource(t *testing.T) {
|
|||
}
|
||||
|
||||
node := &EvalValidateResource{
|
||||
Addr: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.DataResourceMode,
|
||||
Type: "aws_ami",
|
||||
Name: "foo",
|
||||
},
|
||||
Addr: addrs.Resource{
|
||||
Mode: addrs.DataResourceMode,
|
||||
Type: "aws_ami",
|
||||
Name: "foo",
|
||||
},
|
||||
Provider: &p,
|
||||
Config: rc,
|
||||
|
@ -129,12 +175,10 @@ func TestEvalValidateResource_validReturnsNilError(t *testing.T) {
|
|||
Config: configs.SynthBody("", map[string]cty.Value{}),
|
||||
}
|
||||
node := &EvalValidateResource{
|
||||
Addr: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_object",
|
||||
Name: "foo",
|
||||
},
|
||||
Addr: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_object",
|
||||
Name: "foo",
|
||||
},
|
||||
Provider: &p,
|
||||
Config: rc,
|
||||
|
@ -166,12 +210,10 @@ func TestEvalValidateResource_warningsAndErrorsPassedThrough(t *testing.T) {
|
|||
Config: configs.SynthBody("", map[string]cty.Value{}),
|
||||
}
|
||||
node := &EvalValidateResource{
|
||||
Addr: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_object",
|
||||
Name: "foo",
|
||||
},
|
||||
Addr: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_object",
|
||||
Name: "foo",
|
||||
},
|
||||
Provider: &p,
|
||||
Config: rc,
|
||||
|
@ -215,12 +257,10 @@ func TestEvalValidateResource_ignoreWarnings(t *testing.T) {
|
|||
Config: configs.SynthBody("", map[string]cty.Value{}),
|
||||
}
|
||||
node := &EvalValidateResource{
|
||||
Addr: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test-object",
|
||||
Name: "foo",
|
||||
},
|
||||
Addr: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test-object",
|
||||
Name: "foo",
|
||||
},
|
||||
Provider: &p,
|
||||
Config: rc,
|
||||
|
@ -247,12 +287,10 @@ func TestEvalValidateProvisioner_valid(t *testing.T) {
|
|||
schema := &configschema.Block{}
|
||||
|
||||
node := &EvalValidateProvisioner{
|
||||
ResourceAddr: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "foo",
|
||||
Name: "bar",
|
||||
},
|
||||
ResourceAddr: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "foo",
|
||||
Name: "bar",
|
||||
},
|
||||
Provisioner: &p,
|
||||
Schema: &schema,
|
||||
|
@ -295,12 +333,10 @@ func TestEvalValidateProvisioner_warning(t *testing.T) {
|
|||
}
|
||||
|
||||
node := &EvalValidateProvisioner{
|
||||
ResourceAddr: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "foo",
|
||||
Name: "bar",
|
||||
},
|
||||
ResourceAddr: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "foo",
|
||||
Name: "bar",
|
||||
},
|
||||
Provisioner: &p,
|
||||
Schema: &schema,
|
||||
|
@ -348,12 +384,10 @@ func TestEvalValidateProvisioner_connectionInvalid(t *testing.T) {
|
|||
}
|
||||
|
||||
node := &EvalValidateProvisioner{
|
||||
ResourceAddr: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "foo",
|
||||
Name: "bar",
|
||||
},
|
||||
ResourceAddr: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "foo",
|
||||
Name: "bar",
|
||||
},
|
||||
Provisioner: &p,
|
||||
Schema: &schema,
|
||||
|
|
|
@ -48,6 +48,7 @@ func (n *NodePlannableResourceInstance) EvalTree() EvalNode {
|
|||
}
|
||||
|
||||
func (n *NodePlannableResourceInstance) evalTreeDataResource(addr addrs.AbsResourceInstance, stateId string, stateDeps []string) EvalNode {
|
||||
config := n.Config
|
||||
var provider ResourceProvider
|
||||
var providerSchema *ProviderSchema
|
||||
var diff *InstanceDiff
|
||||
|
@ -67,6 +68,12 @@ func (n *NodePlannableResourceInstance) evalTreeDataResource(addr addrs.AbsResou
|
|||
Schema: &providerSchema,
|
||||
},
|
||||
|
||||
&EvalValidateSelfRef{
|
||||
Addr: addr.Resource,
|
||||
Config: config.Config,
|
||||
ProviderSchema: &providerSchema,
|
||||
},
|
||||
|
||||
&EvalReadDataDiff{
|
||||
Addr: addr.Resource,
|
||||
Config: n.Config,
|
||||
|
@ -94,6 +101,7 @@ func (n *NodePlannableResourceInstance) evalTreeDataResource(addr addrs.AbsResou
|
|||
}
|
||||
|
||||
func (n *NodePlannableResourceInstance) evalTreeManagedResource(addr addrs.AbsResourceInstance, stateId string, stateDeps []string) EvalNode {
|
||||
config := n.Config
|
||||
var provider ResourceProvider
|
||||
var providerSchema *ProviderSchema
|
||||
var diff *InstanceDiff
|
||||
|
@ -112,6 +120,12 @@ func (n *NodePlannableResourceInstance) evalTreeManagedResource(addr addrs.AbsRe
|
|||
Schema: &providerSchema,
|
||||
},
|
||||
|
||||
&EvalValidateSelfRef{
|
||||
Addr: addr.Resource,
|
||||
Config: config.Config,
|
||||
ProviderSchema: &providerSchema,
|
||||
},
|
||||
|
||||
&EvalDiff{
|
||||
Addr: addr.Resource,
|
||||
Config: n.Config,
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/config/configschema"
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
|
@ -17,116 +13,21 @@ type NodeValidatableResource struct {
|
|||
|
||||
var (
|
||||
_ GraphNodeSubPath = (*NodeValidatableResource)(nil)
|
||||
_ GraphNodeDynamicExpandable = (*NodeValidatableResource)(nil)
|
||||
_ GraphNodeEvalable = (*NodeValidatableResource)(nil)
|
||||
_ GraphNodeReferenceable = (*NodeValidatableResource)(nil)
|
||||
_ GraphNodeReferencer = (*NodeValidatableResource)(nil)
|
||||
_ GraphNodeResource = (*NodeValidatableResource)(nil)
|
||||
_ GraphNodeAttachResourceConfig = (*NodeValidatableResource)(nil)
|
||||
)
|
||||
|
||||
// GraphNodeDynamicExpandable
|
||||
func (n *NodeValidatableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
count, countDiags := evaluateResourceCountExpression(n.Config.Count, ctx)
|
||||
diags = diags.Append(countDiags)
|
||||
if countDiags.HasErrors() {
|
||||
if count != 0 {
|
||||
log.Printf("[TRACE] %T %s: count expression has errors", n, n.Name())
|
||||
return nil, diags.Err()
|
||||
}
|
||||
|
||||
// evaluateResourceCountExpression returns zero+errors only in the
|
||||
// case where the count value successfully evaluated to an unknown
|
||||
// number. We don't treat this as an error here because counts that
|
||||
// are computed during validate can become known during the plan
|
||||
// walk, if they refer to data resources, and so we'll just defer
|
||||
// our validation steps to the plan phase in that case.
|
||||
log.Printf("[TRACE] %T %s: count expression value not yet known, so deferring validation until the plan walk", n, n.Name())
|
||||
} else if count >= 0 {
|
||||
log.Printf("[TRACE] %T %s: count expression evaluates to %d", n, n.Name(), count)
|
||||
} else {
|
||||
log.Printf("[TRACE] %T %s: no count argument present", n, n.Name())
|
||||
}
|
||||
|
||||
// Next we need to potentially rename an instance address in the state
|
||||
// if we're transitioning whether "count" is set at all.
|
||||
fixResourceCountSetTransition(ctx, n.ResourceAddr().Resource, count != -1)
|
||||
|
||||
// Grab the state which we read
|
||||
state, lock := ctx.State()
|
||||
lock.RLock()
|
||||
defer lock.RUnlock()
|
||||
|
||||
// The concrete resource factory we'll use
|
||||
concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex {
|
||||
// Add the config and state since we don't do that via transforms
|
||||
a.Config = n.Config
|
||||
a.ResolvedProvider = n.ResolvedProvider
|
||||
|
||||
return &NodeValidatableResourceInstance{
|
||||
NodeAbstractResourceInstance: a,
|
||||
}
|
||||
}
|
||||
|
||||
// Start creating the steps
|
||||
steps := []GraphTransformer{
|
||||
// Expand the count.
|
||||
&ResourceCountTransformer{
|
||||
Concrete: concreteResource,
|
||||
Schema: n.Schema,
|
||||
Count: count,
|
||||
Addr: n.ResourceAddr(),
|
||||
},
|
||||
|
||||
// Attach the state
|
||||
&AttachStateTransformer{State: state},
|
||||
|
||||
// Targeting
|
||||
&TargetsTransformer{Targets: n.Targets},
|
||||
|
||||
// Connect references so ordering is correct
|
||||
&ReferenceTransformer{},
|
||||
|
||||
// Make sure there is a single root
|
||||
&RootTransformer{},
|
||||
}
|
||||
|
||||
// Build the graph
|
||||
b := &BasicGraphBuilder{
|
||||
Steps: steps,
|
||||
Validate: true,
|
||||
Name: "NodeValidatableResource",
|
||||
}
|
||||
|
||||
graph, diags := b.Build(ctx.Path())
|
||||
return graph, diags.ErrWithWarnings()
|
||||
}
|
||||
|
||||
// This represents a _single_ resource instance to validate.
|
||||
type NodeValidatableResourceInstance struct {
|
||||
*NodeAbstractResourceInstance
|
||||
}
|
||||
|
||||
var (
|
||||
_ GraphNodeSubPath = (*NodeValidatableResourceInstance)(nil)
|
||||
_ GraphNodeReferenceable = (*NodeValidatableResourceInstance)(nil)
|
||||
_ GraphNodeReferencer = (*NodeValidatableResourceInstance)(nil)
|
||||
_ GraphNodeResource = (*NodeValidatableResourceInstance)(nil)
|
||||
_ GraphNodeResourceInstance = (*NodeValidatableResourceInstance)(nil)
|
||||
_ GraphNodeAttachResourceConfig = (*NodeValidatableResourceInstance)(nil)
|
||||
_ GraphNodeAttachResourceState = (*NodeValidatableResourceInstance)(nil)
|
||||
_ GraphNodeEvalable = (*NodeValidatableResourceInstance)(nil)
|
||||
)
|
||||
|
||||
// GraphNodeEvalable
|
||||
func (n *NodeValidatableResourceInstance) EvalTree() EvalNode {
|
||||
addr := n.ResourceInstanceAddr()
|
||||
func (n *NodeValidatableResource) EvalTree() EvalNode {
|
||||
addr := n.ResourceAddr()
|
||||
config := n.Config
|
||||
|
||||
// Declare a bunch of variables that are used for state during
|
||||
// evaluation. These are written to via pointers passed to the EvalNodes
|
||||
// below.
|
||||
// Declare the variables will be used are used to pass values along
|
||||
// the evaluation sequence below. These are written to via pointers
|
||||
// passed to the EvalNodes.
|
||||
var provider ResourceProvider
|
||||
var providerSchema *ProviderSchema
|
||||
var configVal cty.Value
|
||||
|
@ -138,11 +39,6 @@ func (n *NodeValidatableResourceInstance) EvalTree() EvalNode {
|
|||
Output: &provider,
|
||||
Schema: &providerSchema,
|
||||
},
|
||||
&EvalValidateSelfRef{
|
||||
Addr: addr.Resource,
|
||||
Config: config.Config,
|
||||
ProviderSchema: &providerSchema,
|
||||
},
|
||||
&EvalValidateResource{
|
||||
Addr: addr.Resource,
|
||||
Provider: &provider,
|
||||
|
@ -154,6 +50,8 @@ func (n *NodeValidatableResourceInstance) EvalTree() EvalNode {
|
|||
}
|
||||
|
||||
if managed := n.Config.Managed; managed != nil {
|
||||
hasCount := n.Config.Count != nil
|
||||
|
||||
// Validate all the provisioners
|
||||
for _, p := range managed.Provisioners {
|
||||
var provisioner ResourceProvisioner
|
||||
|
@ -166,10 +64,11 @@ func (n *NodeValidatableResourceInstance) EvalTree() EvalNode {
|
|||
Schema: &provisionerSchema,
|
||||
},
|
||||
&EvalValidateProvisioner{
|
||||
ResourceAddr: addr.Resource,
|
||||
Provisioner: &provisioner,
|
||||
Schema: &provisionerSchema,
|
||||
Config: p,
|
||||
ResourceAddr: addr.Resource,
|
||||
Provisioner: &provisioner,
|
||||
Schema: &provisionerSchema,
|
||||
Config: p,
|
||||
ResourceHasCount: hasCount,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue