Merge pull request #9527 from hashicorp/f-destroy-builder2
terraform: destroy graph builder based on state
This commit is contained in:
commit
aed23a0a31
|
@ -115,6 +115,26 @@ func (c *ApplyCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for the new destroy
|
||||||
|
if terraform.X_newDestroy {
|
||||||
|
desc := "Experimental new destroy graph has been enabled. This may still\n" +
|
||||||
|
"have bugs, and should be used with care. If you'd like to continue,\n" +
|
||||||
|
"you must enter exactly 'yes' as a response."
|
||||||
|
v, err := c.UIInput().Input(&terraform.InputOpts{
|
||||||
|
Id: "Xnew-destroy",
|
||||||
|
Query: "Experimental feature enabled: new destroy graph. Continue?",
|
||||||
|
Description: desc,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if v != "yes" {
|
||||||
|
c.Ui.Output("Apply cancelled.")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build the context based on the arguments given
|
// Build the context based on the arguments given
|
||||||
ctx, planned, err := c.Context(contextOpts{
|
ctx, planned, err := c.Context(contextOpts{
|
||||||
Destroy: c.Destroy,
|
Destroy: c.Destroy,
|
||||||
|
|
|
@ -337,6 +337,7 @@ func (m *Meta) flagSet(n string) *flag.FlagSet {
|
||||||
|
|
||||||
// Experimental features
|
// Experimental features
|
||||||
f.BoolVar(&terraform.X_newApply, "Xnew-apply", false, "experiment: new apply")
|
f.BoolVar(&terraform.X_newApply, "Xnew-apply", false, "experiment: new apply")
|
||||||
|
f.BoolVar(&terraform.X_newDestroy, "Xnew-destroy", false, "experiment: new destroy")
|
||||||
|
|
||||||
// Create an io.Writer that writes to our Ui properly for errors.
|
// Create an io.Writer that writes to our Ui properly for errors.
|
||||||
// This is kind of a hack, but it does the job. Basically: create
|
// This is kind of a hack, but it does the job. Basically: create
|
||||||
|
|
2
go.sh
2
go.sh
|
@ -1 +1 @@
|
||||||
go test ./terraform | grep -E '(FAIL|panic)' | tee /dev/tty | wc -l
|
go test ./terraform -Xnew-apply -Xnew-destroy | grep -E '(FAIL|panic)' | tee /dev/tty | wc -l
|
||||||
|
|
|
@ -21,6 +21,10 @@ var (
|
||||||
// X_newApply will enable the new apply graph. This will be removed
|
// X_newApply will enable the new apply graph. This will be removed
|
||||||
// and be on by default in 0.8.0.
|
// and be on by default in 0.8.0.
|
||||||
X_newApply = false
|
X_newApply = false
|
||||||
|
|
||||||
|
// X_newDestroy will enable the new destroy graph. This will be removed
|
||||||
|
// and be on by default in 0.8.0.
|
||||||
|
X_newDestroy = false
|
||||||
)
|
)
|
||||||
|
|
||||||
// InputMode defines what sort of input will be asked for when Input
|
// InputMode defines what sort of input will be asked for when Input
|
||||||
|
@ -371,6 +375,8 @@ func (c *Context) Apply() (*State, error) {
|
||||||
// Copy our own state
|
// Copy our own state
|
||||||
c.state = c.state.DeepCopy()
|
c.state = c.state.DeepCopy()
|
||||||
|
|
||||||
|
newGraphEnabled := (c.destroy && X_newDestroy) || (!c.destroy && X_newApply)
|
||||||
|
|
||||||
// Build the original graph. This is before the new graph builders
|
// Build the original graph. This is before the new graph builders
|
||||||
// coming in 0.8. We do this for shadow graphing.
|
// coming in 0.8. We do this for shadow graphing.
|
||||||
oldGraph, err := c.Graph(&ContextGraphOpts{Validate: true})
|
oldGraph, err := c.Graph(&ContextGraphOpts{Validate: true})
|
||||||
|
@ -392,12 +398,13 @@ func (c *Context) Apply() (*State, error) {
|
||||||
State: c.state,
|
State: c.state,
|
||||||
Providers: c.components.ResourceProviders(),
|
Providers: c.components.ResourceProviders(),
|
||||||
Provisioners: c.components.ResourceProvisioners(),
|
Provisioners: c.components.ResourceProvisioners(),
|
||||||
|
Destroy: c.destroy,
|
||||||
}).Build(RootModulePath)
|
}).Build(RootModulePath)
|
||||||
if err != nil && !X_newApply {
|
if err != nil && !newGraphEnabled {
|
||||||
// If we had an error graphing but we're not using this graph, just
|
// If we had an error graphing but we're not using this graph, just
|
||||||
// set it to nil and record it as a shadow error.
|
// set it to nil and record it as a shadow error.
|
||||||
c.shadowErr = multierror.Append(c.shadowErr, fmt.Errorf(
|
c.shadowErr = multierror.Append(c.shadowErr, fmt.Errorf(
|
||||||
"Error building new apply graph: %s", err))
|
"Error building new graph: %s", err))
|
||||||
|
|
||||||
newGraph = nil
|
newGraph = nil
|
||||||
err = nil
|
err = nil
|
||||||
|
@ -418,16 +425,11 @@ func (c *Context) Apply() (*State, error) {
|
||||||
//
|
//
|
||||||
real := oldGraph
|
real := oldGraph
|
||||||
shadow := newGraph
|
shadow := newGraph
|
||||||
if c.destroy {
|
if newGraphEnabled {
|
||||||
log.Printf("[WARN] terraform: real graph is original, shadow is nil")
|
log.Printf("[WARN] terraform: real graph is experiment, shadow is experiment")
|
||||||
shadow = nil
|
real = shadow
|
||||||
} else {
|
} else {
|
||||||
if X_newApply {
|
log.Printf("[WARN] terraform: real graph is original, shadow is experiment")
|
||||||
log.Printf("[WARN] terraform: real graph is Xnew-apply, shadow is Xnew-apply")
|
|
||||||
real = shadow
|
|
||||||
} else {
|
|
||||||
log.Printf("[WARN] terraform: real graph is original, shadow is Xnew-apply")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For now, always shadow with the real graph for verification. We don't
|
// For now, always shadow with the real graph for verification. We don't
|
||||||
|
@ -505,8 +507,20 @@ func (c *Context) Plan() (*Plan, error) {
|
||||||
c.diff.init()
|
c.diff.init()
|
||||||
c.diffLock.Unlock()
|
c.diffLock.Unlock()
|
||||||
|
|
||||||
// Build the graph
|
// Build the graph. We have a branch here since for the pure-destroy
|
||||||
graph, err := c.Graph(&ContextGraphOpts{Validate: true})
|
// plan (c.destroy) we use a much simpler graph builder that simply
|
||||||
|
// walks the state and reverses edges.
|
||||||
|
var graph *Graph
|
||||||
|
var err error
|
||||||
|
if c.destroy && X_newDestroy {
|
||||||
|
graph, err = (&DestroyPlanGraphBuilder{
|
||||||
|
Module: c.module,
|
||||||
|
State: c.state,
|
||||||
|
Targets: c.targets,
|
||||||
|
}).Build(RootModulePath)
|
||||||
|
} else {
|
||||||
|
graph, err = c.Graph(&ContextGraphOpts{Validate: true})
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -529,11 +543,16 @@ func (c *Context) Plan() (*Plan, error) {
|
||||||
p.Diff.DeepCopy()
|
p.Diff.DeepCopy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we have a diff, we can build the exact graph that Apply will use
|
// We don't do the reverification during the new destroy plan because
|
||||||
// and catch any possible cycles during the Plan phase.
|
// it will use a different apply process.
|
||||||
if _, err := c.Graph(&ContextGraphOpts{Validate: true}); err != nil {
|
if !(c.destroy && X_newDestroy) {
|
||||||
return nil, err
|
// Now that we have a diff, we can build the exact graph that Apply will use
|
||||||
|
// and catch any possible cycles during the Plan phase.
|
||||||
|
if _, err := c.Graph(&ContextGraphOpts{Validate: true}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var errs error
|
var errs error
|
||||||
if len(walker.ValidationErrors) > 0 {
|
if len(walker.ValidationErrors) > 0 {
|
||||||
errs = multierror.Append(errs, walker.ValidationErrors...)
|
errs = multierror.Append(errs, walker.ValidationErrors...)
|
||||||
|
|
|
@ -889,7 +889,7 @@ func getContextForApply_destroyCrossProviders(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&ModuleState{
|
&ModuleState{
|
||||||
Path: []string{"root", "example"},
|
Path: []string{"root", "child"},
|
||||||
Resources: map[string]*ResourceState{
|
Resources: map[string]*ResourceState{
|
||||||
"aws_vpc.bar": &ResourceState{
|
"aws_vpc.bar": &ResourceState{
|
||||||
Type: "aws_vpc",
|
Type: "aws_vpc",
|
||||||
|
|
|
@ -1582,7 +1582,7 @@ func TestContext2Plan_moduleDestroy(t *testing.T) {
|
||||||
actual := strings.TrimSpace(plan.String())
|
actual := strings.TrimSpace(plan.String())
|
||||||
expected := strings.TrimSpace(testTerraformPlanModuleDestroyStr)
|
expected := strings.TrimSpace(testTerraformPlanModuleDestroyStr)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("bad:\n%s", actual)
|
t.Fatalf("bad:\n%s\n\nexpected:\n\n%s", actual, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1634,7 +1634,7 @@ func TestContext2Plan_moduleDestroyCycle(t *testing.T) {
|
||||||
actual := strings.TrimSpace(plan.String())
|
actual := strings.TrimSpace(plan.String())
|
||||||
expected := strings.TrimSpace(testTerraformPlanModuleDestroyCycleStr)
|
expected := strings.TrimSpace(testTerraformPlanModuleDestroyCycleStr)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("bad:\n%s", actual)
|
t.Fatalf("bad:\n%s\n\nexpected:\n\n%s", actual, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1684,7 +1684,7 @@ func TestContext2Plan_moduleDestroyMultivar(t *testing.T) {
|
||||||
actual := strings.TrimSpace(plan.String())
|
actual := strings.TrimSpace(plan.String())
|
||||||
expected := strings.TrimSpace(testTerraformPlanModuleDestroyMultivarStr)
|
expected := strings.TrimSpace(testTerraformPlanModuleDestroyMultivarStr)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("bad:\n%s", actual)
|
t.Fatalf("bad:\n%s\n\nexpected:\n\n%s", actual, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -100,8 +100,20 @@ func (d *Diff) Equal(d2 *Diff) bool {
|
||||||
sort.Sort(moduleDiffSort(d.Modules))
|
sort.Sort(moduleDiffSort(d.Modules))
|
||||||
sort.Sort(moduleDiffSort(d2.Modules))
|
sort.Sort(moduleDiffSort(d2.Modules))
|
||||||
|
|
||||||
|
// Copy since we have to modify the module destroy flag to false so
|
||||||
|
// we don't compare that. TODO: delete this when we get rid of the
|
||||||
|
// destroy flag on modules.
|
||||||
|
dCopy := d.DeepCopy()
|
||||||
|
d2Copy := d2.DeepCopy()
|
||||||
|
for _, m := range dCopy.Modules {
|
||||||
|
m.Destroy = false
|
||||||
|
}
|
||||||
|
for _, m := range d2Copy.Modules {
|
||||||
|
m.Destroy = false
|
||||||
|
}
|
||||||
|
|
||||||
// Use DeepEqual
|
// Use DeepEqual
|
||||||
return reflect.DeepEqual(d, d2)
|
return reflect.DeepEqual(dCopy, d2Copy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy performs a deep copy of all parts of the Diff, making the
|
// DeepCopy performs a deep copy of all parts of the Diff, making the
|
||||||
|
@ -238,10 +250,6 @@ func (d *ModuleDiff) IsRoot() bool {
|
||||||
func (d *ModuleDiff) String() string {
|
func (d *ModuleDiff) String() string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
if d.Destroy {
|
|
||||||
buf.WriteString("DESTROY MODULE\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
names := make([]string, 0, len(d.Resources))
|
names := make([]string, 0, len(d.Resources))
|
||||||
for name, _ := range d.Resources {
|
for name, _ := range d.Resources {
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
|
|
|
@ -77,6 +77,20 @@ func TestDiffEqual(t *testing.T) {
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"different module diff destroys": {
|
||||||
|
&Diff{
|
||||||
|
Modules: []*ModuleDiff{
|
||||||
|
&ModuleDiff{Path: []string{"root", "foo"}, Destroy: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&Diff{
|
||||||
|
Modules: []*ModuleDiff{
|
||||||
|
&ModuleDiff{Path: []string{"root", "foo"}, Destroy: false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, tc := range cases {
|
for name, tc := range cases {
|
||||||
|
|
|
@ -26,6 +26,10 @@ type BasicGraphBuilder struct {
|
||||||
func (b *BasicGraphBuilder) Build(path []string) (*Graph, error) {
|
func (b *BasicGraphBuilder) Build(path []string) (*Graph, error) {
|
||||||
g := &Graph{Path: path}
|
g := &Graph{Path: path}
|
||||||
for _, step := range b.Steps {
|
for _, step := range b.Steps {
|
||||||
|
if step == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if err := step.Transform(g); err != nil {
|
if err := step.Transform(g); err != nil {
|
||||||
return g, err
|
return g, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,9 @@ type ApplyGraphBuilder struct {
|
||||||
|
|
||||||
// DisableReduce, if true, will not reduce the graph. Great for testing.
|
// DisableReduce, if true, will not reduce the graph. Great for testing.
|
||||||
DisableReduce bool
|
DisableReduce bool
|
||||||
|
|
||||||
|
// Destroy, if true, represents a pure destroy operation
|
||||||
|
Destroy bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// See GraphBuilder
|
// See GraphBuilder
|
||||||
|
@ -77,7 +80,10 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
|
||||||
|
|
||||||
// Destruction ordering
|
// Destruction ordering
|
||||||
&DestroyEdgeTransformer{Module: b.Module, State: b.State},
|
&DestroyEdgeTransformer{Module: b.Module, State: b.State},
|
||||||
&CBDEdgeTransformer{Module: b.Module, State: b.State},
|
GraphTransformIf(
|
||||||
|
func() bool { return !b.Destroy },
|
||||||
|
&CBDEdgeTransformer{Module: b.Module, State: b.State},
|
||||||
|
),
|
||||||
|
|
||||||
// Create all the providers
|
// Create all the providers
|
||||||
&MissingProviderTransformer{Providers: b.Providers, Factory: providerFactory},
|
&MissingProviderTransformer{Providers: b.Providers, Factory: providerFactory},
|
||||||
|
@ -87,8 +93,13 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
|
||||||
&AttachProviderConfigTransformer{Module: b.Module},
|
&AttachProviderConfigTransformer{Module: b.Module},
|
||||||
|
|
||||||
// Provisioner-related transformations
|
// Provisioner-related transformations
|
||||||
&MissingProvisionerTransformer{Provisioners: b.Provisioners},
|
GraphTransformIf(
|
||||||
&ProvisionerTransformer{},
|
func() bool { return !b.Destroy },
|
||||||
|
GraphTransformMulti(
|
||||||
|
&MissingProvisionerTransformer{Provisioners: b.Provisioners},
|
||||||
|
&ProvisionerTransformer{},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// Add root variables
|
// Add root variables
|
||||||
&RootVariableTransformer{Module: b.Module},
|
&RootVariableTransformer{Module: b.Module},
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DestroyPlanGraphBuilder implements GraphBuilder and is responsible for
|
||||||
|
// planning a pure-destroy.
|
||||||
|
//
|
||||||
|
// Planning a pure destroy operation is simple because we can ignore most
|
||||||
|
// ordering configuration and simply reverse the state.
|
||||||
|
type DestroyPlanGraphBuilder struct {
|
||||||
|
// Module is the root module for the graph to build.
|
||||||
|
Module *module.Tree
|
||||||
|
|
||||||
|
// State is the current state
|
||||||
|
State *State
|
||||||
|
|
||||||
|
// Targets are resources to target
|
||||||
|
Targets []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// See GraphBuilder
|
||||||
|
func (b *DestroyPlanGraphBuilder) Build(path []string) (*Graph, error) {
|
||||||
|
return (&BasicGraphBuilder{
|
||||||
|
Steps: b.Steps(),
|
||||||
|
Validate: true,
|
||||||
|
}).Build(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See GraphBuilder
|
||||||
|
func (b *DestroyPlanGraphBuilder) Steps() []GraphTransformer {
|
||||||
|
concreteResource := func(a *NodeAbstractResource) dag.Vertex {
|
||||||
|
return &NodePlanDestroyableResource{
|
||||||
|
NodeAbstractResource: a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
steps := []GraphTransformer{
|
||||||
|
// Creates all the nodes represented in the state.
|
||||||
|
&StateTransformer{
|
||||||
|
Concrete: concreteResource,
|
||||||
|
State: b.State,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Target
|
||||||
|
&TargetsTransformer{Targets: b.Targets},
|
||||||
|
|
||||||
|
// Attach the configuration to any resources
|
||||||
|
&AttachResourceConfigTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
// Single root
|
||||||
|
&RootTransformer{},
|
||||||
|
}
|
||||||
|
|
||||||
|
return steps
|
||||||
|
}
|
|
@ -24,8 +24,6 @@ type graphNodeConfig interface {
|
||||||
// configuration graph need to implement in order to be be addressed / targeted
|
// configuration graph need to implement in order to be be addressed / targeted
|
||||||
// properly.
|
// properly.
|
||||||
type GraphNodeAddressable interface {
|
type GraphNodeAddressable interface {
|
||||||
graphNodeConfig
|
|
||||||
|
|
||||||
ResourceAddress() *ResourceAddress
|
ResourceAddress() *ResourceAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +33,5 @@ type GraphNodeAddressable interface {
|
||||||
// provided will contain every target provided, and each implementing graph
|
// provided will contain every target provided, and each implementing graph
|
||||||
// node must filter this list to targets considered relevant.
|
// node must filter this list to targets considered relevant.
|
||||||
type GraphNodeTargetable interface {
|
type GraphNodeTargetable interface {
|
||||||
GraphNodeAddressable
|
|
||||||
|
|
||||||
SetTargets([]ResourceAddress)
|
SetTargets([]ResourceAddress)
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ func (n *GraphNodeConfigModule) Expand(b GraphBuilder) (GraphNodeSubgraph, error
|
||||||
|
|
||||||
{
|
{
|
||||||
// Add the destroy marker to the graph
|
// Add the destroy marker to the graph
|
||||||
t := &ModuleDestroyTransformer{}
|
t := &ModuleDestroyTransformerOld{}
|
||||||
if err := t.Transform(graph); err != nil {
|
if err := t.Transform(graph); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeDestroyableModule represents a module destruction.
|
||||||
|
type NodeDestroyableModuleVariable struct {
|
||||||
|
PathValue []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeDestroyableModuleVariable) Name() string {
|
||||||
|
result := "plan-destroy"
|
||||||
|
if len(n.PathValue) > 1 {
|
||||||
|
result = fmt.Sprintf("%s.%s", modulePrefixStr(n.PathValue), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeSubPath
|
||||||
|
func (n *NodeDestroyableModuleVariable) Path() []string {
|
||||||
|
return n.PathValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable
|
||||||
|
func (n *NodeDestroyableModuleVariable) EvalTree() EvalNode {
|
||||||
|
return &EvalDiffDestroyModule{Path: n.PathValue}
|
||||||
|
}
|
|
@ -28,6 +28,8 @@ type NodeAbstractResource struct {
|
||||||
|
|
||||||
Config *config.Resource // Config is the resource in the config
|
Config *config.Resource // Config is the resource in the config
|
||||||
ResourceState *ResourceState // ResourceState is the ResourceState for this
|
ResourceState *ResourceState // ResourceState is the ResourceState for this
|
||||||
|
|
||||||
|
Targets []ResourceAddress // Set from GraphNodeTargetable
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NodeAbstractResource) Name() string {
|
func (n *NodeAbstractResource) Name() string {
|
||||||
|
@ -111,6 +113,16 @@ func (n *NodeAbstractResource) ResourceAddr() *ResourceAddress {
|
||||||
return n.Addr
|
return n.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GraphNodeAddressable, TODO: remove, used by target, should unify
|
||||||
|
func (n *NodeAbstractResource) ResourceAddress() *ResourceAddress {
|
||||||
|
return n.ResourceAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeTargetable
|
||||||
|
func (n *NodeAbstractResource) SetTargets(targets []ResourceAddress) {
|
||||||
|
n.Targets = targets
|
||||||
|
}
|
||||||
|
|
||||||
// GraphNodeAttachResourceState
|
// GraphNodeAttachResourceState
|
||||||
func (n *NodeAbstractResource) AttachResourceState(s *ResourceState) {
|
func (n *NodeAbstractResource) AttachResourceState(s *ResourceState) {
|
||||||
n.ResourceState = s
|
n.ResourceState = s
|
||||||
|
|
|
@ -2,11 +2,13 @@ package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NodeDestroyResource represents a resource that is to be destroyed.
|
// NodeDestroyResource represents a resource that is to be destroyed.
|
||||||
type NodeDestroyResource struct {
|
type NodeDestroyResource struct {
|
||||||
NodeAbstractResource
|
*NodeAbstractResource
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NodeDestroyResource) Name() string {
|
func (n *NodeDestroyResource) Name() string {
|
||||||
|
@ -63,6 +65,11 @@ func (n *NodeDestroyResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
||||||
View: n.Config.Id(),
|
View: n.Config.Id(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Target
|
||||||
|
steps = append(steps, &TargetsTransformer{
|
||||||
|
ParsedTargets: n.Targets,
|
||||||
|
})
|
||||||
|
|
||||||
// Always end with the root being added
|
// Always end with the root being added
|
||||||
steps = append(steps, &RootTransformer{})
|
steps = append(steps, &RootTransformer{})
|
||||||
|
|
||||||
|
@ -142,11 +149,13 @@ func (n *NodeDestroyResource) EvalTree() EvalNode {
|
||||||
// Make sure we handle data sources properly.
|
// Make sure we handle data sources properly.
|
||||||
&EvalIf{
|
&EvalIf{
|
||||||
If: func(ctx EvalContext) (bool, error) {
|
If: func(ctx EvalContext) (bool, error) {
|
||||||
/* TODO: data source
|
if n.Addr == nil {
|
||||||
if n.Resource.Mode == config.DataResourceMode {
|
return false, fmt.Errorf("nil address")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.Addr.Mode == config.DataResourceMode {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodePlanDestroyableResource represents a resource that is "applyable":
|
||||||
|
// it is ready to be applied and is represented by a diff.
|
||||||
|
type NodePlanDestroyableResource struct {
|
||||||
|
*NodeAbstractResource
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable
|
||||||
|
func (n *NodePlanDestroyableResource) EvalTree() EvalNode {
|
||||||
|
addr := n.NodeAbstractResource.Addr
|
||||||
|
|
||||||
|
// stateId is the ID to put into the state
|
||||||
|
stateId := addr.stateId()
|
||||||
|
if addr.Index > -1 {
|
||||||
|
stateId = fmt.Sprintf("%s.%d", stateId, addr.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 diff *InstanceDiff
|
||||||
|
var state *InstanceState
|
||||||
|
|
||||||
|
return &EvalSequence{
|
||||||
|
Nodes: []EvalNode{
|
||||||
|
&EvalReadState{
|
||||||
|
Name: stateId,
|
||||||
|
Output: &state,
|
||||||
|
},
|
||||||
|
&EvalDiffDestroy{
|
||||||
|
Info: info,
|
||||||
|
State: &state,
|
||||||
|
Output: &diff,
|
||||||
|
},
|
||||||
|
&EvalCheckPreventDestroy{
|
||||||
|
Resource: n.Config,
|
||||||
|
Diff: &diff,
|
||||||
|
},
|
||||||
|
&EvalWriteDiff{
|
||||||
|
Name: stateId,
|
||||||
|
Diff: &diff,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ const fixtureDir = "./test-fixtures"
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
// Experimental features
|
// Experimental features
|
||||||
xNewApply := flag.Bool("Xnew-apply", false, "Experiment: new apply graph")
|
xNewApply := flag.Bool("Xnew-apply", false, "Experiment: new apply graph")
|
||||||
|
xNewDestroy := flag.Bool("Xnew-destroy", false, "Experiment: new destroy graph")
|
||||||
|
|
||||||
// Normal features
|
// Normal features
|
||||||
shadow := flag.Bool("shadow", true, "Enable shadow graph")
|
shadow := flag.Bool("shadow", true, "Enable shadow graph")
|
||||||
|
@ -32,6 +33,7 @@ func TestMain(m *testing.M) {
|
||||||
|
|
||||||
// Setup experimental features
|
// Setup experimental features
|
||||||
X_newApply = *xNewApply
|
X_newApply = *xNewApply
|
||||||
|
X_newDestroy = *xNewDestroy
|
||||||
|
|
||||||
if testing.Verbose() {
|
if testing.Verbose() {
|
||||||
// if we're verbose, use the logging requested by TF_LOG
|
// if we're verbose, use the logging requested by TF_LOG
|
||||||
|
@ -1137,7 +1139,6 @@ DIFF:
|
||||||
DESTROY: aws_instance.foo
|
DESTROY: aws_instance.foo
|
||||||
|
|
||||||
module.child:
|
module.child:
|
||||||
DESTROY MODULE
|
|
||||||
DESTROY: aws_instance.foo
|
DESTROY: aws_instance.foo
|
||||||
|
|
||||||
STATE:
|
STATE:
|
||||||
|
@ -1154,10 +1155,8 @@ const testTerraformPlanModuleDestroyCycleStr = `
|
||||||
DIFF:
|
DIFF:
|
||||||
|
|
||||||
module.a_module:
|
module.a_module:
|
||||||
DESTROY MODULE
|
|
||||||
DESTROY: aws_instance.a
|
DESTROY: aws_instance.a
|
||||||
module.b_module:
|
module.b_module:
|
||||||
DESTROY MODULE
|
|
||||||
DESTROY: aws_instance.b
|
DESTROY: aws_instance.b
|
||||||
|
|
||||||
STATE:
|
STATE:
|
||||||
|
@ -1174,7 +1173,6 @@ const testTerraformPlanModuleDestroyMultivarStr = `
|
||||||
DIFF:
|
DIFF:
|
||||||
|
|
||||||
module.child:
|
module.child:
|
||||||
DESTROY MODULE
|
|
||||||
DESTROY: aws_instance.foo.0
|
DESTROY: aws_instance.foo.0
|
||||||
DESTROY: aws_instance.foo.1
|
DESTROY: aws_instance.foo.1
|
||||||
|
|
||||||
|
|
|
@ -19,3 +19,34 @@ type GraphTransformer interface {
|
||||||
type GraphVertexTransformer interface {
|
type GraphVertexTransformer interface {
|
||||||
Transform(dag.Vertex) (dag.Vertex, error)
|
Transform(dag.Vertex) (dag.Vertex, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GraphTransformIf is a helper function that conditionally returns a
|
||||||
|
// GraphTransformer given. This is useful for calling inline a sequence
|
||||||
|
// of transforms without having to split it up into multiple append() calls.
|
||||||
|
func GraphTransformIf(f func() bool, then GraphTransformer) GraphTransformer {
|
||||||
|
if f() {
|
||||||
|
return then
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type graphTransformerMulti struct {
|
||||||
|
Transforms []GraphTransformer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *graphTransformerMulti) Transform(g *Graph) error {
|
||||||
|
for _, t := range t.Transforms {
|
||||||
|
if err := t.Transform(g); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphTransformMulti combines multiple graph transformers into a single
|
||||||
|
// GraphTransformer that runs all the individual graph transformers.
|
||||||
|
func GraphTransformMulti(ts ...GraphTransformer) GraphTransformer {
|
||||||
|
return &graphTransformerMulti{Transforms: ts}
|
||||||
|
}
|
||||||
|
|
|
@ -41,7 +41,9 @@ func (t *AttachResourceConfigTransformer) Transform(g *Graph) error {
|
||||||
|
|
||||||
// Determine what we're looking for
|
// Determine what we're looking for
|
||||||
addr := arn.ResourceAddr()
|
addr := arn.ResourceAddr()
|
||||||
log.Printf("[TRACE] AttachResourceConfigTransformer: Attach resource request: %s", addr)
|
log.Printf(
|
||||||
|
"[TRACE] AttachResourceConfigTransformer: Attach resource "+
|
||||||
|
"config request: %s", addr)
|
||||||
|
|
||||||
// Get the configuration.
|
// Get the configuration.
|
||||||
path := normalizeModulePath(addr.Path)
|
path := normalizeModulePath(addr.Path)
|
||||||
|
|
|
@ -59,7 +59,7 @@ func (t *DiffTransformer) Transform(g *Graph) error {
|
||||||
|
|
||||||
// If we're destroying, add the destroy node
|
// If we're destroying, add the destroy node
|
||||||
if inst.Destroy {
|
if inst.Destroy {
|
||||||
abstract := NodeAbstractResource{Addr: addr}
|
abstract := &NodeAbstractResource{Addr: addr}
|
||||||
g.Add(&NodeDestroyResource{NodeAbstractResource: abstract})
|
g.Add(&NodeDestroyResource{NodeAbstractResource: abstract})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,9 @@ import (
|
||||||
// ModuleDestroyTransformer is a GraphTransformer that adds a node
|
// ModuleDestroyTransformer is a GraphTransformer that adds a node
|
||||||
// to the graph that will just mark the full module for destroy in
|
// to the graph that will just mark the full module for destroy in
|
||||||
// the destroy scenario.
|
// the destroy scenario.
|
||||||
type ModuleDestroyTransformer struct{}
|
type ModuleDestroyTransformerOld struct{}
|
||||||
|
|
||||||
func (t *ModuleDestroyTransformer) Transform(g *Graph) error {
|
func (t *ModuleDestroyTransformerOld) Transform(g *Graph) error {
|
||||||
// Create the node
|
// Create the node
|
||||||
n := &graphNodeModuleDestroy{Path: g.Path}
|
n := &graphNodeModuleDestroy{Path: g.Path}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StateTransformer is a GraphTransformer that adds the elements of
|
||||||
|
// the state to the graph.
|
||||||
|
//
|
||||||
|
// This transform is used for example by the DestroyPlanGraphBuilder to ensure
|
||||||
|
// that only resources that are in the state are represented in the graph.
|
||||||
|
type StateTransformer struct {
|
||||||
|
Concrete ConcreteResourceNodeFunc
|
||||||
|
|
||||||
|
State *State
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *StateTransformer) Transform(g *Graph) error {
|
||||||
|
// If the state is nil or empty (nil is empty) then do nothing
|
||||||
|
if t.State.Empty() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through all the modules in the diff.
|
||||||
|
log.Printf("[TRACE] StateTransformer: starting")
|
||||||
|
var nodes []dag.Vertex
|
||||||
|
for _, ms := range t.State.Modules {
|
||||||
|
log.Printf("[TRACE] StateTransformer: Module: %v", ms.Path)
|
||||||
|
|
||||||
|
// Go through all the resources in this module.
|
||||||
|
for name, rs := range ms.Resources {
|
||||||
|
log.Printf("[TRACE] StateTransformer: Resource %q: %#v", name, rs)
|
||||||
|
|
||||||
|
// Add the resource to the graph
|
||||||
|
addr, err := parseResourceAddressInternal(name)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf(
|
||||||
|
"Error parsing internal name, this is a bug: %q", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Very important: add the module path for this resource to
|
||||||
|
// the address. Remove "root" from it.
|
||||||
|
addr.Path = ms.Path[1:]
|
||||||
|
|
||||||
|
// Add the resource to the graph
|
||||||
|
abstract := &NodeAbstractResource{Addr: addr}
|
||||||
|
var node dag.Vertex = abstract
|
||||||
|
if f := t.Concrete; f != nil {
|
||||||
|
node = f(abstract)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes = append(nodes, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all the nodes to the graph
|
||||||
|
for _, n := range nodes {
|
||||||
|
g.Add(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -28,8 +28,10 @@ func (t *TargetsTransformer) Transform(g *Graph) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
t.ParsedTargets = addrs
|
t.ParsedTargets = addrs
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(t.ParsedTargets) > 0 {
|
if len(t.ParsedTargets) > 0 {
|
||||||
targetedNodes, err := t.selectTargetedNodes(g, t.ParsedTargets)
|
targetedNodes, err := t.selectTargetedNodes(g, t.ParsedTargets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -50,6 +52,7 @@ func (t *TargetsTransformer) Transform(g *Graph) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +65,7 @@ func (t *TargetsTransformer) parseTargetAddresses() ([]ResourceAddress, error) {
|
||||||
}
|
}
|
||||||
addrs[i] = *ta
|
addrs[i] = *ta
|
||||||
}
|
}
|
||||||
|
|
||||||
return addrs, nil
|
return addrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +111,7 @@ func (t *TargetsTransformer) selectTargetedNodes(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return targetedNodes, nil
|
return targetedNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,12 +121,14 @@ func (t *TargetsTransformer) nodeIsTarget(
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := r.ResourceAddress()
|
addr := r.ResourceAddress()
|
||||||
for _, targetAddr := range addrs {
|
for _, targetAddr := range addrs {
|
||||||
if targetAddr.Equals(addr) {
|
if targetAddr.Equals(addr) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue