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

Various fixes for "terraform validate" command #18318

Merged
merged 3 commits into from
Jun 26, 2018
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
21 changes: 21 additions & 0 deletions command/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"path/filepath"
"strings"

"github.com/zclconf/go-cty/cty"

"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
)
Expand Down Expand Up @@ -119,8 +121,27 @@ func (c *ValidateCommand) validate(dir string) tfdiags.Diagnostics {
return diags
}

// "validate" is to check if the given module is valid regardless of
// input values, current state, etc. Therefore we populate all of the
// input values with unknown values of the expected type, allowing us
// to perform a type check without assuming any particular values.
varValues := make(terraform.InputValues)
for name, variable := range cfg.Module.Variables {
ty := variable.Type
if ty == cty.NilType {
// Can't predict the type at all, so we'll just mark it as
// cty.DynamicVal (unknown value of cty.DynamicPseudoType).
ty = cty.DynamicPseudoType
}
varValues[name] = &terraform.InputValue{
Value: cty.UnknownVal(ty),
SourceType: terraform.ValueFromCLIArg,
}
}

opts := c.contextOpts()
opts.Config = cfg
opts.Variables = varValues

tfCtx, ctxDiags := terraform.NewContext(opts)
diags = diags.Append(ctxDiags)
Expand Down
2 changes: 1 addition & 1 deletion terraform/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ func (c *Context) Eval(path addrs.ModuleInstance) (*lang.Scope, tfdiags.Diagnost
// caches its contexts, so we should get hold of the context that was
// previously used for evaluation here, unless we skipped walking.
evalCtx := walker.EnterPath(path)
return evalCtx.EvaluationScope(nil, addrs.NoKey), diags
return evalCtx.EvaluationScope(nil, EvalDataForNoInstanceKey), diags
}

// Interpolater is no longer used. Use Evaluator instead.
Expand Down
117 changes: 117 additions & 0 deletions terraform/context_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4082,3 +4082,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)
}
}
84 changes: 0 additions & 84 deletions terraform/context_validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
6 changes: 4 additions & 2 deletions terraform/eval_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,13 +313,15 @@ func (n *EvalApplyProvisioners) apply(ctx EvalContext, provs []*configs.Provisio
provisioner := ctx.Provisioner(prov.Type)
schema := ctx.ProvisionerSchema(prov.Type)

keyData := EvalDataForInstanceKey(instanceAddr.Key)

// Evaluate the main provisioner configuration.
config, _, configDiags := ctx.EvaluateBlock(prov.Config, schema, instanceAddr, instanceAddr.Key)
config, _, configDiags := ctx.EvaluateBlock(prov.Config, schema, instanceAddr, keyData)
diags = diags.Append(configDiags)

// A provisioner may not have a connection block
if prov.Connection != nil {
connInfo, _, connInfoDiags := ctx.EvaluateBlock(prov.Connection.Config, connectionBlockSupersetSchema, instanceAddr, instanceAddr.Key)
connInfo, _, connInfoDiags := ctx.EvaluateBlock(prov.Connection.Config, connectionBlockSupersetSchema, instanceAddr, keyData)
diags = diags.Append(connInfoDiags)

if configDiags.HasErrors() || connInfoDiags.HasErrors() {
Expand Down
4 changes: 2 additions & 2 deletions terraform/eval_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ type EvalContext interface {
// "dynamic" blocks replaced with zero or more static blocks. This can be
// used to extract correct source location information about attributes of
// the returned object value.
EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, key addrs.InstanceKey) (cty.Value, hcl.Body, tfdiags.Diagnostics)
EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics)

// EvaluateExpr takes the given HCL expression and evaluates it to produce
// a value.
Expand All @@ -112,7 +112,7 @@ type EvalContext interface {

// EvaluationScope returns a scope that can be used to evaluate reference
// addresses in this context.
EvaluationScope(self addrs.Referenceable, key addrs.InstanceKey) *lang.Scope
EvaluationScope(self addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope

// SetModuleCallArguments defines values for the variables of a particular
// child module call.
Expand Down
14 changes: 7 additions & 7 deletions terraform/eval_context_builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,9 @@ func (ctx *BuiltinEvalContext) CloseProvisioner(n string) error {
return nil
}

func (ctx *BuiltinEvalContext) EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, key addrs.InstanceKey) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
func (ctx *BuiltinEvalContext) EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
scope := ctx.EvaluationScope(self, key)
scope := ctx.EvaluationScope(self, keyData)
body, evalDiags := scope.ExpandBlock(body, schema)
diags = diags.Append(evalDiags)
val, evalDiags := scope.EvalBlock(body, schema)
Expand All @@ -282,15 +282,15 @@ func (ctx *BuiltinEvalContext) EvaluateBlock(body hcl.Body, schema *configschema
}

func (ctx *BuiltinEvalContext) EvaluateExpr(expr hcl.Expression, wantType cty.Type, self addrs.Referenceable) (cty.Value, tfdiags.Diagnostics) {
scope := ctx.EvaluationScope(self, addrs.NoKey)
scope := ctx.EvaluationScope(self, EvalDataForNoInstanceKey)
return scope.EvalExpr(expr, wantType)
}

func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, key addrs.InstanceKey) *lang.Scope {
func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope {
data := &evaluationStateData{
Evaluator: ctx.Evaluator,
ModulePath: ctx.PathValue,
InstanceKey: key,
Evaluator: ctx.Evaluator,
ModulePath: ctx.PathValue,
InstanceKeyData: keyData,
}
return ctx.Evaluator.Scope(data, self)
}
Expand Down
24 changes: 12 additions & 12 deletions terraform/eval_context_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ type MockEvalContext struct {
EvaluateBlockBody hcl.Body
EvaluateBlockSchema *configschema.Block
EvaluateBlockSelf addrs.Referenceable
EvaluateBlockKey addrs.InstanceKey
EvaluateBlockKeyData InstanceKeyEvalData
EvaluateBlockResultFunc func(
body hcl.Body,
schema *configschema.Block,
self addrs.Referenceable,
key addrs.InstanceKey,
keyData InstanceKeyEvalData,
) (cty.Value, hcl.Body, tfdiags.Diagnostics) // overrides the other values below, if set
EvaluateBlockResult cty.Value
EvaluateBlockExpandedBody hcl.Body
Expand All @@ -103,10 +103,10 @@ type MockEvalContext struct {
EvaluateExprResult cty.Value
EvaluateExprDiags tfdiags.Diagnostics

EvaluationScopeCalled bool
EvaluationScopeSelf addrs.Referenceable
EvaluationScopeKey addrs.InstanceKey
EvaluationScopeScope *lang.Scope
EvaluationScopeCalled bool
EvaluationScopeSelf addrs.Referenceable
EvaluationScopeKeyData InstanceKeyEvalData
EvaluationScopeScope *lang.Scope

InterpolateCalled bool
InterpolateConfig *config.RawConfig
Expand Down Expand Up @@ -228,14 +228,14 @@ func (c *MockEvalContext) CloseProvisioner(n string) error {
return nil
}

func (c *MockEvalContext) EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, key addrs.InstanceKey) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
func (c *MockEvalContext) EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
c.EvaluateBlockCalled = true
c.EvaluateBlockBody = body
c.EvaluateBlockSchema = schema
c.EvaluateBlockSelf = self
c.EvaluateBlockKey = key
c.EvaluateBlockKeyData = keyData
if c.EvaluateBlockResultFunc != nil {
return c.EvaluateBlockResultFunc(body, schema, self, key)
return c.EvaluateBlockResultFunc(body, schema, self, keyData)
}
return c.EvaluateBlockResult, c.EvaluateBlockExpandedBody, c.EvaluateBlockDiags
}
Expand All @@ -261,7 +261,7 @@ func (c *MockEvalContext) EvaluateExpr(expr hcl.Expression, wantType cty.Type, s
// This function overwrites any existing functions installed in fields
// EvaluateBlockResultFunc and EvaluateExprResultFunc.
func (c *MockEvalContext) installSimpleEval() {
c.EvaluateBlockResultFunc = func(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, key addrs.InstanceKey) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
c.EvaluateBlockResultFunc = func(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
if scope := c.EvaluationScopeScope; scope != nil {
// Fully-functional codepath.
var diags tfdiags.Diagnostics
Expand Down Expand Up @@ -304,10 +304,10 @@ func (c *MockEvalContext) installSimpleEval() {
}
}

func (c *MockEvalContext) EvaluationScope(self addrs.Referenceable, key addrs.InstanceKey) *lang.Scope {
func (c *MockEvalContext) EvaluationScope(self addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope {
c.EvaluationScopeCalled = true
c.EvaluationScopeSelf = self
c.EvaluationScopeKey = key
c.EvaluationScopeKeyData = keyData
return c.EvaluationScopeScope
}

Expand Down
Loading