terraform: wip moving validation to new graph

This commit is contained in:
Mitchell Hashimoto 2017-01-25 12:24:48 -08:00
parent 66f6f70cdb
commit 1427075005
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
6 changed files with 202 additions and 35 deletions

View File

@ -219,15 +219,32 @@ func (c *Context) Graph(typ GraphType, opts *ContextGraphOpts) (*Graph, error) {
case GraphTypeInput:
// The input graph is just a slightly modified plan graph
fallthrough
case GraphTypeValidate:
// The validate graph is just a slightly modified plan graph
fallthrough
case GraphTypePlan:
return (&PlanGraphBuilder{
// Create the plan graph builder
p := &PlanGraphBuilder{
Module: c.module,
State: c.state,
Providers: c.components.ResourceProviders(),
Targets: c.targets,
Validate: opts.Validate,
Input: typ == GraphTypeInput,
}).Build(RootModulePath)
}
// Some special cases for other graph types shared with plan currently
var b GraphBuilder = p
switch typ {
case GraphTypeInput:
b = InputGraphBuilder(p)
case GraphTypeValidate:
// We need to set the provisioners so those can be validated
p.Provisioners = c.components.ResourceProvisioners()
b = ValidateGraphBuilder(p)
}
return b.Build(RootModulePath)
case GraphTypePlanDestroy:
return (&DestroyPlanGraphBuilder{
@ -661,7 +678,7 @@ func (c *Context) Validate() ([]string, []error) {
// We also validate the graph generated here, but this graph doesn't
// necessarily match the graph that Plan will generate, so we'll validate the
// graph again later after Planning.
graph, err := c.Graph(GraphTypeLegacy, nil)
graph, err := c.Graph(GraphTypeValidate, nil)
if err != nil {
return nil, []error{err}
}

View File

@ -15,6 +15,7 @@ const (
GraphTypePlanDestroy
GraphTypeApply
GraphTypeInput
GraphTypeValidate
)
// GraphTypeMap is a mapping of human-readable string to GraphType. This
@ -27,4 +28,5 @@ var GraphTypeMap = map[string]GraphType{
"plan-destroy": GraphTypePlanDestroy,
"refresh": GraphTypeRefresh,
"legacy": GraphTypeLegacy,
"validate": GraphTypeValidate,
}

View File

@ -0,0 +1,27 @@
package terraform
import (
"github.com/hashicorp/terraform/dag"
)
// InputGraphBuilder creates the graph for the input operation.
//
// Unlike other graph builders, this is a function since it currently modifies
// and is based on the PlanGraphBuilder. The PlanGraphBuilder passed in will be
// modified and should not be used for any other operations.
func InputGraphBuilder(p *PlanGraphBuilder) GraphBuilder {
// We're going to customize the concrete functions
p.CustomConcrete = true
// Set the provider to the normal provider. This will ask for input.
p.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
return &NodeApplyableProvider{
NodeAbstractProvider: a,
}
}
// We purposely don't set any more concrete fields since the remainder
// should be no-ops.
return p
}

View File

@ -1,6 +1,8 @@
package terraform
import (
"sync"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/dag"
)
@ -26,6 +28,9 @@ type PlanGraphBuilder struct {
// Providers is the list of providers supported.
Providers []string
// Provisioners is the list of provisioners supported.
Provisioners []string
// Targets are resources to target
Targets []string
@ -35,13 +40,15 @@ type PlanGraphBuilder struct {
// Validate will do structural validation of the graph.
Validate bool
// Input, if true, modifies this graph for inputs. There isn't a
// dedicated input graph because asking for input is identical to
// planning except for the operations done. You still need to know WHAT
// you're going to plan since you only need to ask for input for things
// that are necessary for planning. This requirement makes the graphs
// very similar.
Input bool
// CustomConcrete can be set to customize the node types created
// for various parts of the plan. This is useful in order to customize
// the plan behavior.
CustomConcrete bool
ConcreteProvider ConcreteProviderNodeFunc
ConcreteResource ConcreteResourceNodeFunc
ConcreteResourceOrphan ConcreteResourceNodeFunc
once sync.Once
}
// See GraphBuilder
@ -55,32 +62,12 @@ func (b *PlanGraphBuilder) Build(path []string) (*Graph, error) {
// See GraphBuilder
func (b *PlanGraphBuilder) Steps() []GraphTransformer {
// Custom factory for creating providers.
concreteProvider := func(a *NodeAbstractProvider) dag.Vertex {
return &NodeApplyableProvider{
NodeAbstractProvider: a,
}
}
var concreteResource, concreteResourceOrphan ConcreteResourceNodeFunc
if !b.Input {
concreteResource = func(a *NodeAbstractResource) dag.Vertex {
return &NodePlannableResource{
NodeAbstractResource: a,
}
}
concreteResourceOrphan = func(a *NodeAbstractResource) dag.Vertex {
return &NodePlannableResourceOrphan{
NodeAbstractResource: a,
}
}
}
b.once.Do(b.init)
steps := []GraphTransformer{
// Creates all the resources represented in the config
&ConfigTransformer{
Concrete: concreteResource,
Concrete: b.ConcreteResource,
Module: b.Module,
},
@ -89,7 +76,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
// Add orphan resources
&OrphanResourceTransformer{
Concrete: concreteResourceOrphan,
Concrete: b.ConcreteResourceOrphan,
State: b.State,
Module: b.Module,
},
@ -104,12 +91,21 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
&RootVariableTransformer{Module: b.Module},
// Create all the providers
&MissingProviderTransformer{Providers: b.Providers, Concrete: concreteProvider},
&MissingProviderTransformer{Providers: b.Providers, Concrete: b.ConcreteProvider},
&ProviderTransformer{},
&DisableProviderTransformer{},
&ParentProviderTransformer{},
&AttachProviderConfigTransformer{Module: b.Module},
// Provisioner-related transformations. Only add these if requested.
GraphTransformIf(
func() bool { return b.Provisioners != nil },
GraphTransformMulti(
&MissingProvisionerTransformer{Provisioners: b.Provisioners},
&ProvisionerTransformer{},
),
),
// Add module variables
&ModuleVariableTransformer{Module: b.Module},
@ -132,3 +128,28 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
return steps
}
func (b *PlanGraphBuilder) init() {
// Do nothing if the user requests customizing the fields
if b.CustomConcrete {
return
}
b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
return &NodeApplyableProvider{
NodeAbstractProvider: a,
}
}
b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
return &NodePlannableResource{
NodeAbstractResource: a,
}
}
b.ConcreteResourceOrphan = func(a *NodeAbstractResource) dag.Vertex {
return &NodePlannableResourceOrphan{
NodeAbstractResource: a,
}
}
}

View File

@ -0,0 +1,34 @@
package terraform
import (
"github.com/hashicorp/terraform/dag"
)
// ValidateGraphBuilder creates the graph for the validate operation.
//
// ValidateGraphBuilder is based on the PlanGraphBuilder. We do this so that
// we only have to validate what we'd normally plan anyways. The
// PlanGraphBuilder given will be modified so it shouldn't be used for anything
// else after calling this function.
func ValidateGraphBuilder(p *PlanGraphBuilder) GraphBuilder {
// We're going to customize the concrete functions
p.CustomConcrete = true
// Set the provider to the normal provider. This will ask for input.
p.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
return &NodeApplyableProvider{
NodeAbstractProvider: a,
}
}
p.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
return &NodeValidatableResource{
NodeAbstractResource: a,
}
}
// We purposely don't set any other concrete types since they don't
// require validation.
return p
}

View File

@ -0,0 +1,66 @@
package terraform
// NodeValidatableResource represents a resource that is used for validation
// only.
type NodeValidatableResource struct {
*NodeAbstractResource
}
// GraphNodeEvalable
func (n *NodeValidatableResource) EvalTree() EvalNode {
addr := n.NodeAbstractResource.Addr
// Build the resource for eval
resource := &Resource{
Name: addr.Name,
Type: addr.Type,
CountIndex: addr.Index,
}
if resource.CountIndex < 0 {
resource.CountIndex = 0
}
// Declare a bunch of variables that are used for state during
// evaluation. Most of this are written to by-address below.
var config *ResourceConfig
var provider ResourceProvider
seq := &EvalSequence{
Nodes: []EvalNode{
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalInterpolate{
Config: n.Config.RawConfig.Copy(),
Resource: resource,
Output: &config,
},
&EvalValidateResource{
Provider: &provider,
Config: &config,
ResourceName: n.Config.Name,
ResourceType: n.Config.Type,
ResourceMode: n.Config.Mode,
},
},
}
// Validate all the provisioners
for _, p := range n.Config.Provisioners {
var provisioner ResourceProvisioner
seq.Nodes = append(seq.Nodes, &EvalGetProvisioner{
Name: p.Type,
Output: &provisioner,
}, &EvalInterpolate{
Config: p.RawConfig.Copy(),
Resource: resource,
Output: &config,
}, &EvalValidateProvisioner{
Provisioner: &provisioner,
Config: &config,
})
}
return seq
}