From 14270750051aeb6dfdea687ecc57a6fd07edcd9c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Jan 2017 12:24:48 -0800 Subject: [PATCH] terraform: wip moving validation to new graph --- terraform/context.go | 25 +++++++-- terraform/context_graph_type.go | 2 + terraform/graph_builder_input.go | 27 ++++++++++ terraform/graph_builder_plan.go | 83 ++++++++++++++++++----------- terraform/graph_builder_validate.go | 34 ++++++++++++ terraform/node_resource_validate.go | 66 +++++++++++++++++++++++ 6 files changed, 202 insertions(+), 35 deletions(-) create mode 100644 terraform/graph_builder_input.go create mode 100644 terraform/graph_builder_validate.go create mode 100644 terraform/node_resource_validate.go diff --git a/terraform/context.go b/terraform/context.go index 04f43c855..cf18c74c2 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -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} } diff --git a/terraform/context_graph_type.go b/terraform/context_graph_type.go index 0dff25f15..084f0105d 100644 --- a/terraform/context_graph_type.go +++ b/terraform/context_graph_type.go @@ -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, } diff --git a/terraform/graph_builder_input.go b/terraform/graph_builder_input.go new file mode 100644 index 000000000..0df48cdb8 --- /dev/null +++ b/terraform/graph_builder_input.go @@ -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 +} diff --git a/terraform/graph_builder_plan.go b/terraform/graph_builder_plan.go index f53024270..ecb8f4ae5 100644 --- a/terraform/graph_builder_plan.go +++ b/terraform/graph_builder_plan.go @@ -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, + } + } +} diff --git a/terraform/graph_builder_validate.go b/terraform/graph_builder_validate.go new file mode 100644 index 000000000..1881f95f2 --- /dev/null +++ b/terraform/graph_builder_validate.go @@ -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 +} diff --git a/terraform/node_resource_validate.go b/terraform/node_resource_validate.go new file mode 100644 index 000000000..bbdc6f544 --- /dev/null +++ b/terraform/node_resource_validate.go @@ -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 +}