Merge pull request #11426 from hashicorp/f-new-graph
core: Refresh, Validate, Input on new graph builders
This commit is contained in:
commit
0b0114c9bf
|
@ -216,14 +216,35 @@ func (c *Context) Graph(typ GraphType, opts *ContextGraphOpts) (*Graph, error) {
|
||||||
Validate: opts.Validate,
|
Validate: opts.Validate,
|
||||||
}).Build(RootModulePath)
|
}).Build(RootModulePath)
|
||||||
|
|
||||||
|
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:
|
case GraphTypePlan:
|
||||||
return (&PlanGraphBuilder{
|
// Create the plan graph builder
|
||||||
|
p := &PlanGraphBuilder{
|
||||||
Module: c.module,
|
Module: c.module,
|
||||||
State: c.state,
|
State: c.state,
|
||||||
Providers: c.components.ResourceProviders(),
|
Providers: c.components.ResourceProviders(),
|
||||||
Targets: c.targets,
|
Targets: c.targets,
|
||||||
Validate: opts.Validate,
|
Validate: opts.Validate,
|
||||||
}).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:
|
case GraphTypePlanDestroy:
|
||||||
return (&DestroyPlanGraphBuilder{
|
return (&DestroyPlanGraphBuilder{
|
||||||
|
@ -233,6 +254,15 @@ func (c *Context) Graph(typ GraphType, opts *ContextGraphOpts) (*Graph, error) {
|
||||||
Validate: opts.Validate,
|
Validate: opts.Validate,
|
||||||
}).Build(RootModulePath)
|
}).Build(RootModulePath)
|
||||||
|
|
||||||
|
case GraphTypeRefresh:
|
||||||
|
return (&RefreshGraphBuilder{
|
||||||
|
Module: c.module,
|
||||||
|
State: c.state,
|
||||||
|
Providers: c.components.ResourceProviders(),
|
||||||
|
Targets: c.targets,
|
||||||
|
Validate: opts.Validate,
|
||||||
|
}).Build(RootModulePath)
|
||||||
|
|
||||||
case GraphTypeLegacy:
|
case GraphTypeLegacy:
|
||||||
return c.graphBuilder(opts).Build(RootModulePath)
|
return c.graphBuilder(opts).Build(RootModulePath)
|
||||||
}
|
}
|
||||||
|
@ -402,7 +432,7 @@ func (c *Context) Input(mode InputMode) error {
|
||||||
|
|
||||||
if mode&InputModeProvider != 0 {
|
if mode&InputModeProvider != 0 {
|
||||||
// Build the graph
|
// Build the graph
|
||||||
graph, err := c.Graph(GraphTypeLegacy, nil)
|
graph, err := c.Graph(GraphTypeInput, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -569,8 +599,15 @@ func (c *Context) Refresh() (*State, error) {
|
||||||
// Copy our own state
|
// Copy our own state
|
||||||
c.state = c.state.DeepCopy()
|
c.state = c.state.DeepCopy()
|
||||||
|
|
||||||
// Build the graph
|
// Used throughout below
|
||||||
graph, err := c.Graph(GraphTypeLegacy, nil)
|
X_legacyGraph := experiment.Enabled(experiment.X_legacyGraph)
|
||||||
|
|
||||||
|
// Build the graph.
|
||||||
|
graphType := GraphTypeLegacy
|
||||||
|
if !X_legacyGraph {
|
||||||
|
graphType = GraphTypeRefresh
|
||||||
|
}
|
||||||
|
graph, err := c.Graph(graphType, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -641,7 +678,7 @@ func (c *Context) Validate() ([]string, []error) {
|
||||||
// We also validate the graph generated here, but this graph doesn't
|
// 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
|
// necessarily match the graph that Plan will generate, so we'll validate the
|
||||||
// graph again later after Planning.
|
// graph again later after Planning.
|
||||||
graph, err := c.Graph(GraphTypeLegacy, nil)
|
graph, err := c.Graph(GraphTypeValidate, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, []error{err}
|
return nil, []error{err}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,12 @@ type GraphType byte
|
||||||
const (
|
const (
|
||||||
GraphTypeInvalid GraphType = 0
|
GraphTypeInvalid GraphType = 0
|
||||||
GraphTypeLegacy GraphType = iota
|
GraphTypeLegacy GraphType = iota
|
||||||
|
GraphTypeRefresh
|
||||||
GraphTypePlan
|
GraphTypePlan
|
||||||
GraphTypePlanDestroy
|
GraphTypePlanDestroy
|
||||||
GraphTypeApply
|
GraphTypeApply
|
||||||
|
GraphTypeInput
|
||||||
|
GraphTypeValidate
|
||||||
)
|
)
|
||||||
|
|
||||||
// GraphTypeMap is a mapping of human-readable string to GraphType. This
|
// GraphTypeMap is a mapping of human-readable string to GraphType. This
|
||||||
|
@ -20,7 +23,10 @@ const (
|
||||||
// graph types.
|
// graph types.
|
||||||
var GraphTypeMap = map[string]GraphType{
|
var GraphTypeMap = map[string]GraphType{
|
||||||
"apply": GraphTypeApply,
|
"apply": GraphTypeApply,
|
||||||
|
"input": GraphTypeInput,
|
||||||
"plan": GraphTypePlan,
|
"plan": GraphTypePlan,
|
||||||
"plan-destroy": GraphTypePlanDestroy,
|
"plan-destroy": GraphTypePlanDestroy,
|
||||||
|
"refresh": GraphTypeRefresh,
|
||||||
"legacy": GraphTypeLegacy,
|
"legacy": GraphTypeLegacy,
|
||||||
|
"validate": GraphTypeValidate,
|
||||||
}
|
}
|
||||||
|
|
|
@ -520,7 +520,7 @@ func TestContext2Refresh_outputPartial(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContext2Refresh_state(t *testing.T) {
|
func TestContext2Refresh_stateBasic(t *testing.T) {
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
m := testModule(t, "refresh-basic")
|
m := testModule(t, "refresh-basic")
|
||||||
state := &State{
|
state := &State{
|
||||||
|
@ -529,6 +529,7 @@ func TestContext2Refresh_state(t *testing.T) {
|
||||||
Path: rootModulePath,
|
Path: rootModulePath,
|
||||||
Resources: map[string]*ResourceState{
|
Resources: map[string]*ResourceState{
|
||||||
"aws_instance.web": &ResourceState{
|
"aws_instance.web": &ResourceState{
|
||||||
|
Type: "aws_instance",
|
||||||
Primary: &InstanceState{
|
Primary: &InstanceState{
|
||||||
ID: "bar",
|
ID: "bar",
|
||||||
},
|
},
|
||||||
|
@ -737,6 +738,21 @@ func TestContext2Refresh_unknownProvider(t *testing.T) {
|
||||||
ctx := testContext2(t, &ContextOpts{
|
ctx := testContext2(t, &ContextOpts{
|
||||||
Module: m,
|
Module: m,
|
||||||
Providers: map[string]ResourceProviderFactory{},
|
Providers: map[string]ResourceProviderFactory{},
|
||||||
|
State: &State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.web": &ResourceState{
|
||||||
|
Type: "aws_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if _, err := ctx.Refresh(); err == nil {
|
if _, err := ctx.Refresh(); err == nil {
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EvalValidateResourceSelfRef is an EvalNode implementation that validates that
|
||||||
|
// a configuration doesn't contain a reference to the resource itself.
|
||||||
|
//
|
||||||
|
// This must be done prior to interpolating configuration in order to avoid
|
||||||
|
// any infinite loop scenarios.
|
||||||
|
type EvalValidateResourceSelfRef struct {
|
||||||
|
Addr **ResourceAddress
|
||||||
|
Config **config.RawConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *EvalValidateResourceSelfRef) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
|
addr := *n.Addr
|
||||||
|
conf := *n.Config
|
||||||
|
|
||||||
|
// Go through the variables and find self references
|
||||||
|
var errs []error
|
||||||
|
for k, raw := range conf.Variables {
|
||||||
|
rv, ok := raw.(*config.ResourceVariable)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build an address from the variable
|
||||||
|
varAddr := &ResourceAddress{
|
||||||
|
Path: addr.Path,
|
||||||
|
Mode: rv.Mode,
|
||||||
|
Type: rv.Type,
|
||||||
|
Name: rv.Name,
|
||||||
|
Index: rv.Index,
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the variable access is a multi-access (*), then we just
|
||||||
|
// match the index so that we'll match our own addr if everything
|
||||||
|
// else matches.
|
||||||
|
if rv.Multi && rv.Index == -1 {
|
||||||
|
varAddr.Index = addr.Index
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a weird thing where ResourceAddres has index "-1" when
|
||||||
|
// index isn't set at all. This means index "0" for resource access.
|
||||||
|
// So, if we have this scenario, just set our varAddr to -1 so it
|
||||||
|
// matches.
|
||||||
|
if addr.Index == -1 && varAddr.Index == 0 {
|
||||||
|
varAddr.Index = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the addresses match, then this is a self reference
|
||||||
|
if varAddr.Equals(addr) && varAddr.Index == addr.Index {
|
||||||
|
errs = append(errs, fmt.Errorf(
|
||||||
|
"%s: self reference not allowed: %q",
|
||||||
|
addr, k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no errors, no errors!
|
||||||
|
if len(errs) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the errors in the proper wrapper so we can handle validation
|
||||||
|
// formatting properly upstream.
|
||||||
|
return nil, &EvalValidateError{
|
||||||
|
Errors: errs,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEvalValidateResourceSelfRef(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Name string
|
||||||
|
Addr string
|
||||||
|
Config map[string]interface{}
|
||||||
|
Err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"no interpolations",
|
||||||
|
"aws_instance.foo",
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"non self reference",
|
||||||
|
"aws_instance.foo",
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": "${aws_instance.bar.id}",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"self reference",
|
||||||
|
"aws_instance.foo",
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": "hello ${aws_instance.foo.id}",
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"self reference other index",
|
||||||
|
"aws_instance.foo",
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": "hello ${aws_instance.foo.4.id}",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"self reference same index",
|
||||||
|
"aws_instance.foo[4]",
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": "hello ${aws_instance.foo.4.id}",
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"self reference multi",
|
||||||
|
"aws_instance.foo[4]",
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": "hello ${aws_instance.foo.*.id}",
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"self reference multi single",
|
||||||
|
"aws_instance.foo",
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": "hello ${aws_instance.foo.*.id}",
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
|
||||||
|
addr, err := ParseResourceAddress(tc.Addr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
conf := config.TestRawConfig(t, tc.Config)
|
||||||
|
|
||||||
|
n := &EvalValidateResourceSelfRef{Addr: &addr, Config: &conf}
|
||||||
|
result, err := n.Eval(nil)
|
||||||
|
if result != nil {
|
||||||
|
t.Fatal("result should always be nil")
|
||||||
|
}
|
||||||
|
if (err != nil) != tc.Err {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config/module"
|
"github.com/hashicorp/terraform/config/module"
|
||||||
"github.com/hashicorp/terraform/dag"
|
"github.com/hashicorp/terraform/dag"
|
||||||
)
|
)
|
||||||
|
@ -26,6 +28,9 @@ type PlanGraphBuilder struct {
|
||||||
// Providers is the list of providers supported.
|
// Providers is the list of providers supported.
|
||||||
Providers []string
|
Providers []string
|
||||||
|
|
||||||
|
// Provisioners is the list of provisioners supported.
|
||||||
|
Provisioners []string
|
||||||
|
|
||||||
// Targets are resources to target
|
// Targets are resources to target
|
||||||
Targets []string
|
Targets []string
|
||||||
|
|
||||||
|
@ -34,6 +39,16 @@ type PlanGraphBuilder struct {
|
||||||
|
|
||||||
// Validate will do structural validation of the graph.
|
// Validate will do structural validation of the graph.
|
||||||
Validate bool
|
Validate 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
|
// See GraphBuilder
|
||||||
|
@ -47,29 +62,12 @@ func (b *PlanGraphBuilder) Build(path []string) (*Graph, error) {
|
||||||
|
|
||||||
// See GraphBuilder
|
// See GraphBuilder
|
||||||
func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
||||||
// Custom factory for creating providers.
|
b.once.Do(b.init)
|
||||||
concreteProvider := func(a *NodeAbstractProvider) dag.Vertex {
|
|
||||||
return &NodeApplyableProvider{
|
|
||||||
NodeAbstractProvider: a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
concreteResource := func(a *NodeAbstractResource) dag.Vertex {
|
|
||||||
return &NodePlannableResource{
|
|
||||||
NodeAbstractResource: a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
concreteResourceOrphan := func(a *NodeAbstractResource) dag.Vertex {
|
|
||||||
return &NodePlannableResourceOrphan{
|
|
||||||
NodeAbstractResource: a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
steps := []GraphTransformer{
|
steps := []GraphTransformer{
|
||||||
// Creates all the resources represented in the config
|
// Creates all the resources represented in the config
|
||||||
&ConfigTransformer{
|
&ConfigTransformer{
|
||||||
Concrete: concreteResource,
|
Concrete: b.ConcreteResource,
|
||||||
Module: b.Module,
|
Module: b.Module,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -78,7 +76,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
||||||
|
|
||||||
// Add orphan resources
|
// Add orphan resources
|
||||||
&OrphanResourceTransformer{
|
&OrphanResourceTransformer{
|
||||||
Concrete: concreteResourceOrphan,
|
Concrete: b.ConcreteResourceOrphan,
|
||||||
State: b.State,
|
State: b.State,
|
||||||
Module: b.Module,
|
Module: b.Module,
|
||||||
},
|
},
|
||||||
|
@ -93,12 +91,21 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
||||||
&RootVariableTransformer{Module: b.Module},
|
&RootVariableTransformer{Module: b.Module},
|
||||||
|
|
||||||
// Create all the providers
|
// Create all the providers
|
||||||
&MissingProviderTransformer{Providers: b.Providers, Concrete: concreteProvider},
|
&MissingProviderTransformer{Providers: b.Providers, Concrete: b.ConcreteProvider},
|
||||||
&ProviderTransformer{},
|
&ProviderTransformer{},
|
||||||
&DisableProviderTransformer{},
|
&DisableProviderTransformer{},
|
||||||
&ParentProviderTransformer{},
|
&ParentProviderTransformer{},
|
||||||
&AttachProviderConfigTransformer{Module: b.Module},
|
&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
|
// Add module variables
|
||||||
&ModuleVariableTransformer{Module: b.Module},
|
&ModuleVariableTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
@ -121,3 +128,30 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
||||||
|
|
||||||
return steps
|
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{
|
||||||
|
NodeAbstractCountResource: &NodeAbstractCountResource{
|
||||||
|
NodeAbstractResource: a,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ConcreteResourceOrphan = func(a *NodeAbstractResource) dag.Vertex {
|
||||||
|
return &NodePlannableResourceOrphan{
|
||||||
|
NodeAbstractResource: a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RefreshGraphBuilder implements GraphBuilder and is responsible for building
|
||||||
|
// a graph for refreshing (updating the Terraform state).
|
||||||
|
//
|
||||||
|
// The primary difference between this graph and others:
|
||||||
|
//
|
||||||
|
// * Based on the state since it represents the only resources that
|
||||||
|
// need to be refreshed.
|
||||||
|
//
|
||||||
|
// * Ignores lifecycle options since no lifecycle events occur here. This
|
||||||
|
// simplifies the graph significantly since complex transforms such as
|
||||||
|
// create-before-destroy can be completely ignored.
|
||||||
|
//
|
||||||
|
type RefreshGraphBuilder struct {
|
||||||
|
// Module is the root module for the graph to build.
|
||||||
|
Module *module.Tree
|
||||||
|
|
||||||
|
// State is the current state
|
||||||
|
State *State
|
||||||
|
|
||||||
|
// Providers is the list of providers supported.
|
||||||
|
Providers []string
|
||||||
|
|
||||||
|
// Targets are resources to target
|
||||||
|
Targets []string
|
||||||
|
|
||||||
|
// DisableReduce, if true, will not reduce the graph. Great for testing.
|
||||||
|
DisableReduce bool
|
||||||
|
|
||||||
|
// Validate will do structural validation of the graph.
|
||||||
|
Validate bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// See GraphBuilder
|
||||||
|
func (b *RefreshGraphBuilder) Build(path []string) (*Graph, error) {
|
||||||
|
return (&BasicGraphBuilder{
|
||||||
|
Steps: b.Steps(),
|
||||||
|
Validate: b.Validate,
|
||||||
|
Name: "RefreshGraphBuilder",
|
||||||
|
}).Build(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See GraphBuilder
|
||||||
|
func (b *RefreshGraphBuilder) Steps() []GraphTransformer {
|
||||||
|
// Custom factory for creating providers.
|
||||||
|
concreteProvider := func(a *NodeAbstractProvider) dag.Vertex {
|
||||||
|
return &NodeApplyableProvider{
|
||||||
|
NodeAbstractProvider: a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
concreteResource := func(a *NodeAbstractResource) dag.Vertex {
|
||||||
|
return &NodeRefreshableResource{
|
||||||
|
NodeAbstractResource: a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
concreteDataResource := func(a *NodeAbstractResource) dag.Vertex {
|
||||||
|
return &NodeRefreshableDataResource{
|
||||||
|
NodeAbstractCountResource: &NodeAbstractCountResource{
|
||||||
|
NodeAbstractResource: a,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
steps := []GraphTransformer{
|
||||||
|
// Creates all the resources represented in the state
|
||||||
|
&StateTransformer{
|
||||||
|
Concrete: concreteResource,
|
||||||
|
State: b.State,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Creates all the data resources that aren't in the state
|
||||||
|
&ConfigTransformer{
|
||||||
|
Concrete: concreteDataResource,
|
||||||
|
Module: b.Module,
|
||||||
|
Unique: true,
|
||||||
|
ModeFilter: true,
|
||||||
|
Mode: config.DataResourceMode,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Attach the state
|
||||||
|
&AttachStateTransformer{State: b.State},
|
||||||
|
|
||||||
|
// Attach the configuration to any resources
|
||||||
|
&AttachResourceConfigTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
// Add root variables
|
||||||
|
&RootVariableTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
// Create all the providers
|
||||||
|
&MissingProviderTransformer{Providers: b.Providers, Concrete: concreteProvider},
|
||||||
|
&ProviderTransformer{},
|
||||||
|
&DisableProviderTransformer{},
|
||||||
|
&ParentProviderTransformer{},
|
||||||
|
&AttachProviderConfigTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
// Add the outputs
|
||||||
|
&OutputTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
// Add module variables
|
||||||
|
&ModuleVariableTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
// Connect so that the references are ready for targeting. We'll
|
||||||
|
// have to connect again later for providers and so on.
|
||||||
|
&ReferenceTransformer{},
|
||||||
|
|
||||||
|
// Target
|
||||||
|
&TargetsTransformer{Targets: b.Targets},
|
||||||
|
|
||||||
|
// Single root
|
||||||
|
&RootTransformer{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !b.DisableReduce {
|
||||||
|
// Perform the transitive reduction to make our graph a bit
|
||||||
|
// more sane if possible (it usually is possible).
|
||||||
|
steps = append(steps, &TransitiveReductionTransformer{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return steps
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
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{
|
||||||
|
NodeAbstractCountResource: &NodeAbstractCountResource{
|
||||||
|
NodeAbstractResource: a,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We purposely don't set any other concrete types since they don't
|
||||||
|
// require validation.
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
|
@ -4,9 +4,9 @@ package terraform
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
const _GraphType_name = "GraphTypeInvalidGraphTypeLegacyGraphTypePlanGraphTypePlanDestroyGraphTypeApply"
|
const _GraphType_name = "GraphTypeInvalidGraphTypeLegacyGraphTypeRefreshGraphTypePlanGraphTypePlanDestroyGraphTypeApply"
|
||||||
|
|
||||||
var _GraphType_index = [...]uint8{0, 16, 31, 44, 64, 78}
|
var _GraphType_index = [...]uint8{0, 16, 31, 47, 60, 80, 94}
|
||||||
|
|
||||||
func (i GraphType) String() string {
|
func (i GraphType) String() string {
|
||||||
if i >= GraphType(len(_GraphType_index)-1) {
|
if i >= GraphType(len(_GraphType_index)-1) {
|
||||||
|
|
|
@ -0,0 +1,209 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeRefreshableDataResource represents a resource that is "plannable":
|
||||||
|
// it is ready to be planned in order to create a diff.
|
||||||
|
type NodeRefreshableDataResource struct {
|
||||||
|
*NodeAbstractCountResource
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeDynamicExpandable
|
||||||
|
func (n *NodeRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
||||||
|
// Grab the state which we read
|
||||||
|
state, lock := ctx.State()
|
||||||
|
lock.RLock()
|
||||||
|
defer lock.RUnlock()
|
||||||
|
|
||||||
|
// Expand the resource count which must be available by now from EvalTree
|
||||||
|
count, err := n.Config.Count()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The concrete resource factory we'll use
|
||||||
|
concreteResource := func(a *NodeAbstractResource) dag.Vertex {
|
||||||
|
// Add the config and state since we don't do that via transforms
|
||||||
|
a.Config = n.Config
|
||||||
|
|
||||||
|
return &NodeRefreshableDataResourceInstance{
|
||||||
|
NodeAbstractResource: a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start creating the steps
|
||||||
|
steps := []GraphTransformer{
|
||||||
|
// Expand the count.
|
||||||
|
&ResourceCountTransformer{
|
||||||
|
Concrete: concreteResource,
|
||||||
|
Count: count,
|
||||||
|
Addr: n.ResourceAddr(),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Attach the state
|
||||||
|
&AttachStateTransformer{State: state},
|
||||||
|
|
||||||
|
// Targeting
|
||||||
|
&TargetsTransformer{ParsedTargets: 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: "NodeRefreshableDataResource",
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Build(ctx.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeRefreshableDataResourceInstance represents a _single_ resource instance
|
||||||
|
// that is refreshable.
|
||||||
|
type NodeRefreshableDataResourceInstance struct {
|
||||||
|
*NodeAbstractResource
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable
|
||||||
|
func (n *NodeRefreshableDataResourceInstance) EvalTree() EvalNode {
|
||||||
|
addr := n.NodeAbstractResource.Addr
|
||||||
|
|
||||||
|
// stateId is the ID to put into the state
|
||||||
|
stateId := addr.stateId()
|
||||||
|
|
||||||
|
// Build the instance info. More of this will be populated during eval
|
||||||
|
info := &InstanceInfo{
|
||||||
|
Id: stateId,
|
||||||
|
Type: addr.Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the state if we have it, if not we build it
|
||||||
|
rs := n.ResourceState
|
||||||
|
if rs == nil {
|
||||||
|
rs = &ResourceState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the config isn't empty we update the state
|
||||||
|
if n.Config != nil {
|
||||||
|
// Determine the dependencies for the state. We use some older
|
||||||
|
// code for this that we've used for a long time.
|
||||||
|
var stateDeps []string
|
||||||
|
{
|
||||||
|
oldN := &graphNodeExpandedResource{
|
||||||
|
Resource: n.Config,
|
||||||
|
Index: addr.Index,
|
||||||
|
}
|
||||||
|
stateDeps = oldN.StateDependencies()
|
||||||
|
}
|
||||||
|
|
||||||
|
rs = &ResourceState{
|
||||||
|
Type: n.Config.Type,
|
||||||
|
Provider: n.Config.Provider,
|
||||||
|
Dependencies: stateDeps,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 diff *InstanceDiff
|
||||||
|
var provider ResourceProvider
|
||||||
|
var state *InstanceState
|
||||||
|
|
||||||
|
return &EvalSequence{
|
||||||
|
Nodes: []EvalNode{
|
||||||
|
// Always destroy the existing state first, since we must
|
||||||
|
// make sure that values from a previous read will not
|
||||||
|
// get interpolated if we end up needing to defer our
|
||||||
|
// loading until apply time.
|
||||||
|
&EvalWriteState{
|
||||||
|
Name: stateId,
|
||||||
|
ResourceType: rs.Type,
|
||||||
|
Provider: rs.Provider,
|
||||||
|
Dependencies: rs.Dependencies,
|
||||||
|
State: &state, // state is nil here
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalInterpolate{
|
||||||
|
Config: n.Config.RawConfig.Copy(),
|
||||||
|
Resource: resource,
|
||||||
|
Output: &config,
|
||||||
|
},
|
||||||
|
|
||||||
|
// The rest of this pass can proceed only if there are no
|
||||||
|
// computed values in our config.
|
||||||
|
// (If there are, we'll deal with this during the plan and
|
||||||
|
// apply phases.)
|
||||||
|
&EvalIf{
|
||||||
|
If: func(ctx EvalContext) (bool, error) {
|
||||||
|
if config.ComputedKeys != nil && len(config.ComputedKeys) > 0 {
|
||||||
|
return true, EvalEarlyExitError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the config explicitly has a depends_on for this
|
||||||
|
// data source, assume the intention is to prevent
|
||||||
|
// refreshing ahead of that dependency.
|
||||||
|
if len(n.Config.DependsOn) > 0 {
|
||||||
|
return true, EvalEarlyExitError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
},
|
||||||
|
|
||||||
|
Then: EvalNoop{},
|
||||||
|
},
|
||||||
|
|
||||||
|
// The remainder of this pass is the same as running
|
||||||
|
// a "plan" pass immediately followed by an "apply" pass,
|
||||||
|
// populating the state early so it'll be available to
|
||||||
|
// provider configurations that need this data during
|
||||||
|
// refresh/plan.
|
||||||
|
&EvalGetProvider{
|
||||||
|
Name: n.ProvidedBy()[0],
|
||||||
|
Output: &provider,
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalReadDataDiff{
|
||||||
|
Info: info,
|
||||||
|
Config: &config,
|
||||||
|
Provider: &provider,
|
||||||
|
Output: &diff,
|
||||||
|
OutputState: &state,
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalReadDataApply{
|
||||||
|
Info: info,
|
||||||
|
Diff: &diff,
|
||||||
|
Provider: &provider,
|
||||||
|
Output: &state,
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalWriteState{
|
||||||
|
Name: stateId,
|
||||||
|
ResourceType: rs.Type,
|
||||||
|
Provider: rs.Provider,
|
||||||
|
Dependencies: rs.Dependencies,
|
||||||
|
State: &state,
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalUpdateStateHook{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
// NodeAbstractCountResource should be embedded instead of NodeAbstractResource
|
||||||
|
// if the resource has a `count` value that needs to be expanded.
|
||||||
|
//
|
||||||
|
// The embedder should implement `DynamicExpand` to process the count.
|
||||||
|
type NodeAbstractCountResource struct {
|
||||||
|
*NodeAbstractResource
|
||||||
|
|
||||||
|
// Validate, if true, will perform the validation for the count.
|
||||||
|
// This should only be turned on for the "validate" operation.
|
||||||
|
Validate bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable
|
||||||
|
func (n *NodeAbstractCountResource) EvalTree() EvalNode {
|
||||||
|
return &EvalSequence{
|
||||||
|
Nodes: []EvalNode{
|
||||||
|
// The EvalTree for a plannable resource primarily involves
|
||||||
|
// interpolating the count since it can contain variables
|
||||||
|
// we only just received access to.
|
||||||
|
//
|
||||||
|
// With the interpolated count, we can then DynamicExpand
|
||||||
|
// into the proper number of instances.
|
||||||
|
&EvalInterpolate{Config: n.Config.RawCount},
|
||||||
|
|
||||||
|
&EvalCountCheckComputed{Resource: n.Config},
|
||||||
|
|
||||||
|
// If validation is enabled, perform the validation
|
||||||
|
&EvalIf{
|
||||||
|
If: func(ctx EvalContext) (bool, error) {
|
||||||
|
return n.Validate, nil
|
||||||
|
},
|
||||||
|
|
||||||
|
Then: &EvalValidateCount{Resource: n.Config},
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalCountFixZeroOneBoundary{Resource: n.Config},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,34 +7,7 @@ import (
|
||||||
// NodePlannableResource represents a resource that is "plannable":
|
// NodePlannableResource represents a resource that is "plannable":
|
||||||
// it is ready to be planned in order to create a diff.
|
// it is ready to be planned in order to create a diff.
|
||||||
type NodePlannableResource struct {
|
type NodePlannableResource struct {
|
||||||
*NodeAbstractResource
|
*NodeAbstractCountResource
|
||||||
|
|
||||||
// Set by GraphNodeTargetable and used during DynamicExpand to
|
|
||||||
// forward targets downwards.
|
|
||||||
targets []ResourceAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeTargetable
|
|
||||||
func (n *NodePlannableResource) SetTargets(targets []ResourceAddress) {
|
|
||||||
n.targets = targets
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeEvalable
|
|
||||||
func (n *NodePlannableResource) EvalTree() EvalNode {
|
|
||||||
return &EvalSequence{
|
|
||||||
Nodes: []EvalNode{
|
|
||||||
// The EvalTree for a plannable resource primarily involves
|
|
||||||
// interpolating the count since it can contain variables
|
|
||||||
// we only just received access to.
|
|
||||||
//
|
|
||||||
// With the interpolated count, we can then DynamicExpand
|
|
||||||
// into the proper number of instances.
|
|
||||||
&EvalInterpolate{Config: n.Config.RawCount},
|
|
||||||
|
|
||||||
&EvalCountCheckComputed{Resource: n.Config},
|
|
||||||
&EvalCountFixZeroOneBoundary{Resource: n.Config},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphNodeDynamicExpandable
|
// GraphNodeDynamicExpandable
|
||||||
|
@ -91,7 +64,7 @@ func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
||||||
&AttachStateTransformer{State: state},
|
&AttachStateTransformer{State: state},
|
||||||
|
|
||||||
// Targeting
|
// Targeting
|
||||||
&TargetsTransformer{ParsedTargets: n.targets},
|
&TargetsTransformer{ParsedTargets: n.Targets},
|
||||||
|
|
||||||
// Connect references so ordering is correct
|
// Connect references so ordering is correct
|
||||||
&ReferenceTransformer{},
|
&ReferenceTransformer{},
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeRefreshableResource represents a resource that is "applyable":
|
||||||
|
// it is ready to be applied and is represented by a diff.
|
||||||
|
type NodeRefreshableResource struct {
|
||||||
|
*NodeAbstractResource
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeDestroyer
|
||||||
|
func (n *NodeRefreshableResource) DestroyAddr() *ResourceAddress {
|
||||||
|
return n.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable
|
||||||
|
func (n *NodeRefreshableResource) EvalTree() EvalNode {
|
||||||
|
// Eval info is different depending on what kind of resource this is
|
||||||
|
switch mode := n.Addr.Mode; mode {
|
||||||
|
case config.ManagedResourceMode:
|
||||||
|
return n.evalTreeManagedResource()
|
||||||
|
case config.DataResourceMode:
|
||||||
|
dn := &NodeRefreshableDataResourceInstance{
|
||||||
|
NodeAbstractResource: n.NodeAbstractResource,
|
||||||
|
}
|
||||||
|
|
||||||
|
return dn.EvalTree()
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unsupported resource mode %s", mode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeRefreshableResource) evalTreeManagedResource() EvalNode {
|
||||||
|
addr := n.NodeAbstractResource.Addr
|
||||||
|
|
||||||
|
// stateId is the ID to put into the state
|
||||||
|
stateId := addr.stateId()
|
||||||
|
|
||||||
|
// Build the instance info. More of this will be populated during eval
|
||||||
|
info := &InstanceInfo{
|
||||||
|
Id: stateId,
|
||||||
|
Type: addr.Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare a bunch of variables that are used for state during
|
||||||
|
// evaluation. Most of this are written to by-address below.
|
||||||
|
var provider ResourceProvider
|
||||||
|
var state *InstanceState
|
||||||
|
|
||||||
|
// This happened during initial development. All known cases were
|
||||||
|
// fixed and tested but as a sanity check let's assert here.
|
||||||
|
if n.ResourceState == nil {
|
||||||
|
err := fmt.Errorf(
|
||||||
|
"No resource state attached for addr: %s\n\n"+
|
||||||
|
"This is a bug. Please report this to Terraform with your configuration\n"+
|
||||||
|
"and state attached. Please be careful to scrub any sensitive information.",
|
||||||
|
addr)
|
||||||
|
return &EvalReturnError{Error: &err}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &EvalSequence{
|
||||||
|
Nodes: []EvalNode{
|
||||||
|
&EvalGetProvider{
|
||||||
|
Name: n.ProvidedBy()[0],
|
||||||
|
Output: &provider,
|
||||||
|
},
|
||||||
|
&EvalReadState{
|
||||||
|
Name: stateId,
|
||||||
|
Output: &state,
|
||||||
|
},
|
||||||
|
&EvalRefresh{
|
||||||
|
Info: info,
|
||||||
|
Provider: &provider,
|
||||||
|
State: &state,
|
||||||
|
Output: &state,
|
||||||
|
},
|
||||||
|
&EvalWriteState{
|
||||||
|
Name: stateId,
|
||||||
|
ResourceType: n.ResourceState.Type,
|
||||||
|
Provider: n.ResourceState.Provider,
|
||||||
|
Dependencies: n.ResourceState.Dependencies,
|
||||||
|
State: &state,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeValidatableResource represents a resource that is used for validation
|
||||||
|
// only.
|
||||||
|
type NodeValidatableResource struct {
|
||||||
|
*NodeAbstractCountResource
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable
|
||||||
|
func (n *NodeValidatableResource) EvalTree() EvalNode {
|
||||||
|
// Ensure we're validating
|
||||||
|
c := n.NodeAbstractCountResource
|
||||||
|
c.Validate = true
|
||||||
|
return c.EvalTree()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeDynamicExpandable
|
||||||
|
func (n *NodeValidatableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
||||||
|
// Grab the state which we read
|
||||||
|
state, lock := ctx.State()
|
||||||
|
lock.RLock()
|
||||||
|
defer lock.RUnlock()
|
||||||
|
|
||||||
|
// Expand the resource count which must be available by now from EvalTree
|
||||||
|
count, err := n.Config.Count()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The concrete resource factory we'll use
|
||||||
|
concreteResource := func(a *NodeAbstractResource) dag.Vertex {
|
||||||
|
// Add the config and state since we don't do that via transforms
|
||||||
|
a.Config = n.Config
|
||||||
|
|
||||||
|
return &NodeValidatableResourceInstance{
|
||||||
|
NodeAbstractResource: a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start creating the steps
|
||||||
|
steps := []GraphTransformer{
|
||||||
|
// Expand the count.
|
||||||
|
&ResourceCountTransformer{
|
||||||
|
Concrete: concreteResource,
|
||||||
|
Count: count,
|
||||||
|
Addr: n.ResourceAddr(),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Attach the state
|
||||||
|
&AttachStateTransformer{State: state},
|
||||||
|
|
||||||
|
// Targeting
|
||||||
|
&TargetsTransformer{ParsedTargets: 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",
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Build(ctx.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
// This represents a _single_ resource instance to validate.
|
||||||
|
type NodeValidatableResourceInstance struct {
|
||||||
|
*NodeAbstractResource
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable
|
||||||
|
func (n *NodeValidatableResourceInstance) 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{
|
||||||
|
&EvalValidateResourceSelfRef{
|
||||||
|
Addr: &addr,
|
||||||
|
Config: &n.Config.RawConfig,
|
||||||
|
},
|
||||||
|
&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
|
||||||
|
}
|
|
@ -85,15 +85,22 @@ func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult {
|
||||||
// the modules to find relevant resources.
|
// the modules to find relevant resources.
|
||||||
for _, m := range modules {
|
for _, m := range modules {
|
||||||
for n, r := range m.Resources {
|
for n, r := range m.Resources {
|
||||||
if f.relevant(a, r) {
|
// The name in the state contains valuable information. Parse.
|
||||||
// The name in the state contains valuable information. Parse.
|
key, err := ParseResourceStateKey(n)
|
||||||
key, err := ParseResourceStateKey(n)
|
if err != nil {
|
||||||
if err != nil {
|
// If we get an error parsing, then just ignore it
|
||||||
// If we get an error parsing, then just ignore it
|
// out of the state.
|
||||||
// out of the state.
|
continue
|
||||||
continue
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
// Older states and test fixtures often don't contain the
|
||||||
|
// type directly on the ResourceState. We add this so StateFilter
|
||||||
|
// is a bit more robust.
|
||||||
|
if r.Type == "" {
|
||||||
|
r.Type = key.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.relevant(a, r) {
|
||||||
if a.Name != "" && a.Name != key.Name {
|
if a.Name != "" && a.Name != key.Name {
|
||||||
// Name doesn't match
|
// Name doesn't match
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -38,6 +38,15 @@ func TestStateFilterFilter(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"single resource from minimal state": {
|
||||||
|
"single-minimal-resource.tfstate",
|
||||||
|
[]string{"aws_instance.web"},
|
||||||
|
[]string{
|
||||||
|
"*terraform.ResourceState: aws_instance.web",
|
||||||
|
"*terraform.InstanceState: aws_instance.web",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
"single resource with similar names": {
|
"single resource with similar names": {
|
||||||
"small_test_instance.tfstate",
|
"small_test_instance.tfstate",
|
||||||
[]string{"test_instance.foo"},
|
[]string{"test_instance.foo"},
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"serial": 12,
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"path": [
|
||||||
|
"root"
|
||||||
|
],
|
||||||
|
"resources": {
|
||||||
|
"aws_instance.web": {
|
||||||
|
"primary": {
|
||||||
|
"id": "onprem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
data "aws_ami" "foo" {}
|
||||||
|
|
||||||
|
resource "aws_instance" "web" {}
|
|
@ -45,8 +45,10 @@ func (t *AttachStateTransformer) Transform(g *Graph) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach the first resource state we get
|
// Attach the first resource state we get
|
||||||
|
log.Printf("SEARCH: %s", addr)
|
||||||
found := false
|
found := false
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
|
log.Printf("WTF: %s %#v", addr, result)
|
||||||
if rs, ok := result.Value.(*ResourceState); ok {
|
if rs, ok := result.Value.(*ResourceState); ok {
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"[DEBUG] Attaching resource state to %q: %s",
|
"[DEBUG] Attaching resource state to %q: %s",
|
||||||
|
|
|
@ -4,7 +4,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/config/module"
|
"github.com/hashicorp/terraform/config/module"
|
||||||
"github.com/hashicorp/terraform/dag"
|
"github.com/hashicorp/terraform/dag"
|
||||||
)
|
)
|
||||||
|
@ -23,10 +25,25 @@ import (
|
||||||
type ConfigTransformer struct {
|
type ConfigTransformer struct {
|
||||||
Concrete ConcreteResourceNodeFunc
|
Concrete ConcreteResourceNodeFunc
|
||||||
|
|
||||||
|
// Module is the module to add resources from.
|
||||||
Module *module.Tree
|
Module *module.Tree
|
||||||
|
|
||||||
|
// Unique will only add resources that aren't already present in the graph.
|
||||||
|
Unique bool
|
||||||
|
|
||||||
|
// Mode will only add resources that match the given mode
|
||||||
|
ModeFilter bool
|
||||||
|
Mode config.ResourceMode
|
||||||
|
|
||||||
|
l sync.Mutex
|
||||||
|
uniqueMap map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ConfigTransformer) Transform(g *Graph) error {
|
func (t *ConfigTransformer) Transform(g *Graph) error {
|
||||||
|
// Lock since we use some internal state
|
||||||
|
t.l.Lock()
|
||||||
|
defer t.l.Unlock()
|
||||||
|
|
||||||
// If no module is given, we don't do anything
|
// If no module is given, we don't do anything
|
||||||
if t.Module == nil {
|
if t.Module == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -37,6 +54,18 @@ func (t *ConfigTransformer) Transform(g *Graph) error {
|
||||||
return errors.New("module must be loaded for ConfigTransformer")
|
return errors.New("module must be loaded for ConfigTransformer")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the uniqueness map. If we're tracking uniques, then populate
|
||||||
|
// it with addresses.
|
||||||
|
t.uniqueMap = make(map[string]struct{})
|
||||||
|
defer func() { t.uniqueMap = nil }()
|
||||||
|
if t.Unique {
|
||||||
|
for _, v := range g.Vertices() {
|
||||||
|
if rn, ok := v.(GraphNodeResource); ok {
|
||||||
|
t.uniqueMap[rn.ResourceAddr().String()] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Start the transformation process
|
// Start the transformation process
|
||||||
return t.transform(g, t.Module)
|
return t.transform(g, t.Module)
|
||||||
}
|
}
|
||||||
|
@ -66,13 +95,13 @@ func (t *ConfigTransformer) transformSingle(g *Graph, m *module.Tree) error {
|
||||||
log.Printf("[TRACE] ConfigTransformer: Starting for path: %v", m.Path())
|
log.Printf("[TRACE] ConfigTransformer: Starting for path: %v", m.Path())
|
||||||
|
|
||||||
// Get the configuration for this module
|
// Get the configuration for this module
|
||||||
config := m.Config()
|
conf := m.Config()
|
||||||
|
|
||||||
// Build the path we're at
|
// Build the path we're at
|
||||||
path := m.Path()
|
path := m.Path()
|
||||||
|
|
||||||
// Write all the resources out
|
// Write all the resources out
|
||||||
for _, r := range config.Resources {
|
for _, r := range conf.Resources {
|
||||||
// Build the resource address
|
// Build the resource address
|
||||||
addr, err := parseResourceAddressConfig(r)
|
addr, err := parseResourceAddressConfig(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -81,6 +110,16 @@ func (t *ConfigTransformer) transformSingle(g *Graph, m *module.Tree) error {
|
||||||
}
|
}
|
||||||
addr.Path = path
|
addr.Path = path
|
||||||
|
|
||||||
|
// If this is already in our uniqueness map, don't add it again
|
||||||
|
if _, ok := t.uniqueMap[addr.String()]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove non-matching modes
|
||||||
|
if t.ModeFilter && addr.Mode != t.Mode {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Build the abstract node and the concrete one
|
// Build the abstract node and the concrete one
|
||||||
abstract := &NodeAbstractResource{Addr: addr}
|
abstract := &NodeAbstractResource{Addr: addr}
|
||||||
var node dag.Vertex = abstract
|
var node dag.Vertex = abstract
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/config/module"
|
"github.com/hashicorp/terraform/config/module"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,6 +49,80 @@ func TestConfigTransformer(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigTransformer_mode(t *testing.T) {
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
tf := &ConfigTransformer{
|
||||||
|
Module: testModule(t, "transform-config-mode-data"),
|
||||||
|
ModeFilter: true,
|
||||||
|
Mode: config.DataResourceMode,
|
||||||
|
}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(`
|
||||||
|
data.aws_ami.foo
|
||||||
|
`)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigTransformer_nonUnique(t *testing.T) {
|
||||||
|
addr, err := ParseResourceAddress("aws_instance.web")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
g.Add(&NodeAbstractResource{Addr: addr})
|
||||||
|
tf := &ConfigTransformer{Module: testModule(t, "graph-basic")}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(`
|
||||||
|
aws_instance.web
|
||||||
|
aws_instance.web
|
||||||
|
aws_load_balancer.weblb
|
||||||
|
aws_security_group.firewall
|
||||||
|
openstack_floating_ip.random
|
||||||
|
`)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigTransformer_unique(t *testing.T) {
|
||||||
|
addr, err := ParseResourceAddress("aws_instance.web")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
g.Add(&NodeAbstractResource{Addr: addr})
|
||||||
|
tf := &ConfigTransformer{
|
||||||
|
Module: testModule(t, "graph-basic"),
|
||||||
|
Unique: true,
|
||||||
|
}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(`
|
||||||
|
aws_instance.web
|
||||||
|
aws_load_balancer.weblb
|
||||||
|
aws_security_group.firewall
|
||||||
|
openstack_floating_ip.random
|
||||||
|
`)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const testConfigTransformerGraphBasicStr = `
|
const testConfigTransformerGraphBasicStr = `
|
||||||
aws_instance.web
|
aws_instance.web
|
||||||
aws_load_balancer.weblb
|
aws_load_balancer.weblb
|
||||||
|
|
Loading…
Reference in New Issue