terraform: transform to attach resource configs

This commit is contained in:
Mitchell Hashimoto 2016-09-20 09:32:34 -07:00
parent ceb5c53d56
commit bd5d97f9f5
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
6 changed files with 206 additions and 29 deletions

View File

@ -2789,7 +2789,7 @@ func TestContext2Apply_Provisioner_ConnInfo(t *testing.T) {
}
}
func TestContext2Apply_destroy(t *testing.T) {
func TestContext2Apply_destroyX(t *testing.T) {
m := testModule(t, "apply-destroy")
h := new(HookRecordApplyOrder)
p := testProvider("aws")
@ -2849,6 +2849,65 @@ func TestContext2Apply_destroy(t *testing.T) {
}
}
func TestContext2Apply_destroyOrder(t *testing.T) {
m := testModule(t, "apply-destroy")
h := new(HookRecordApplyOrder)
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Module: m,
Hooks: []Hook{h},
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})
// First plan and apply a create operation
if _, err := ctx.Plan(); err != nil {
t.Fatalf("err: %s", err)
}
state, err := ctx.Apply()
if err != nil {
t.Fatalf("err: %s", err)
}
// Next, plan and apply config-less to force a destroy with "apply"
h.Active = true
ctx = testContext2(t, &ContextOpts{
State: state,
Module: module.NewEmptyTree(),
Hooks: []Hook{h},
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})
if _, err := ctx.Plan(); err != nil {
t.Fatalf("err: %s", err)
}
state, err = ctx.Apply()
if err != nil {
t.Fatalf("err: %s", err)
}
// Test that things were destroyed
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyDestroyStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
// Test that things were destroyed _in the right order_
expected2 := []string{"aws_instance.bar", "aws_instance.foo"}
actual2 := h.IDs
if !reflect.DeepEqual(actual2, expected2) {
t.Fatalf("expected: %#v\n\ngot:%#v", expected2, actual2)
}
}
// https://github.com/hashicorp/terraform/issues/2767
func TestContext2Apply_destroyModulePrefix(t *testing.T) {
m := testModule(t, "apply-destroy-module-resource-prefix")

View File

@ -54,6 +54,9 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
State: b.State,
},
// Attach the configuration to any resources
&AttachResourceConfigTransformer{Module: b.Module},
// Create orphan output nodes
&OrphanOutputTransformer{Module: b.Module, State: b.State},

View File

@ -91,6 +91,11 @@ func (n *NodeApplyableResource) ResourceAddr() *ResourceAddress {
return n.Addr
}
// GraphNodeAttachResource
func (n *NodeApplyableResource) AttachResourceConfig(c *config.Resource) {
n.Config = c
}
// GraphNodeAttachResourceState
func (n *NodeApplyableResource) AttachResourceState(s *ResourceState) {
n.ResourceState = s

View File

@ -0,0 +1,74 @@
package terraform
import (
"fmt"
"log"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
)
// GraphNodeAttachResourceConfig is an interface that must be implemented by nodes
// that want resource configurations attached.
type GraphNodeAttachResourceConfig interface {
// ResourceAddr is the address to the resource
ResourceAddr() *ResourceAddress
// Sets the configuration
AttachResourceConfig(*config.Resource)
}
// AttachResourceConfigTransformer goes through the graph and attaches
// resource configuration structures to nodes that implement the interfaces
// above.
//
// The attached configuration structures are directly from the configuration.
// If they're going to be modified, a copy should be made.
type AttachResourceConfigTransformer struct {
Module *module.Tree // Module is the root module for the config
}
func (t *AttachResourceConfigTransformer) Transform(g *Graph) error {
// Go through and find GraphNodeAttachResource
for _, v := range g.Vertices() {
// Only care about GraphNodeAttachResource implementations
arn, ok := v.(GraphNodeAttachResourceConfig)
if !ok {
continue
}
// Determine what we're looking for
addr := arn.ResourceAddr()
log.Printf("[TRACE] Attach resource request: %s", addr)
// Get the configuration.
path := normalizeModulePath(addr.Path)
path = path[1:]
tree := t.Module.Child(path)
if tree == nil {
continue
}
// Go through the resource configs to find the matching config
for _, r := range tree.Config().Resources {
// Get a resource address so we can compare
a, err := parseResourceAddressConfig(r)
if err != nil {
panic(fmt.Sprintf(
"Error parsing config address, this is a bug: %#v", r))
}
a.Path = addr.Path
// If this is not the same resource, then continue
if !a.Equals(addr) {
continue
}
log.Printf("[TRACE] Attaching resource config: %#v", r)
arn.AttachResourceConfig(r)
break
}
}
return nil
}

View File

@ -0,0 +1,64 @@
package terraform
// GraphNodeDestroyer must be implemented by nodes that destroy resources.
type GraphNodeDestroyer interface {
// ResourceAddr is the address of the resource that is being
// destroyed by this node. If this returns nil, then this node
// is not destroying anything.
DestroyAddr() *ResourceAddress
}
// DestroyEdgeTransformer is a GraphTransformer that creates the proper
// references for destroy resources. Destroy resources are more complex
// in that they must be depend on the destruction of resources that
// in turn depend on the CREATION of the node being destroy.
//
// That is complicated. Visually:
//
// B_d -> A_d -> A -> B
//
// Notice that A destroy depends on B destroy, while B create depends on
// A create. They're inverted. This must be done for example because often
// dependent resources will block parent resources from deleting. Concrete
// example: VPC with subnets, the VPC can't be deleted while there are
// still subnets.
type DestroyEdgeTransformer struct{}
func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
// Build a map of what is being destroyed (by address string) to
// the list of destroyers. In general there will only be one destroyer
// but to make it more robust we support multiple.
destroyers := make(map[string][]GraphNodeDestroyer)
for _, v := range g.Vertices() {
dn, ok := v.(GraphNodeDestroyer)
if !ok {
continue
}
addr := dn.DestroyAddr()
if addr == nil {
continue
}
key := addr.String()
destroyers[key] = append(destroyers[key], dn)
}
// If we aren't destroying anything, there will be no edges to make
// so just exit early and avoid future work.
if len(destroyers) == 0 {
return nil
}
// Go through the all destroyers and find what they're destroying.
// Use this to find the dependencies, look up if any of them are being
// destroyed, and to make the proper edge.
for _, ds := range destroyers {
for _, d := range ds {
// TODO
println(d)
}
}
return nil
}

View File

@ -69,34 +69,6 @@ func (t *DiffTransformer) Transform(g *Graph) error {
}
}
// NOTE: Lots of room for performance optimizations below. For
// resource-heavy diffs this part alone is probably pretty slow.
// Annotate all nodes with their config and state
for _, n := range nodes {
// Grab the configuration at this path.
if t := t.Module.Child(n.Addr.Path); t != nil {
for _, r := range t.Config().Resources {
// Get a resource address so we can compare
addr, err := parseResourceAddressConfig(r)
if err != nil {
panic(fmt.Sprintf(
"Error parsing config address, this is a bug: %#v", r))
}
addr.Path = n.Addr.Path
// If this is not the same resource, then continue
if !addr.Equals(n.Addr) {
continue
}
// Same resource! Mark it and exit
n.Config = r
break
}
}
}
// Add all the nodes to the graph
for _, n := range nodes {
g.Add(n)