terraform: references can have backups

terraform: more specific resource references

terraform: outputs need to know about the new reference format

terraform: resources w/o a config still have a referencable name
This commit is contained in:
Mitchell Hashimoto 2016-11-08 09:35:57 -08:00
parent c0d2493156
commit 19350d617d
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
8 changed files with 229 additions and 52 deletions

View File

@ -3489,6 +3489,8 @@ func TestContext2Apply_destroyOrder(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
t.Logf("State 1: %s", state)
// Next, plan and apply config-less to force a destroy with "apply" // Next, plan and apply config-less to force a destroy with "apply"
h.Active = true h.Active = true
ctx = testContext2(t, &ContextOpts{ ctx = testContext2(t, &ContextOpts{
@ -3698,8 +3700,10 @@ func TestContext2Apply_destroyModuleWithAttrsReferencingResource(t *testing.T) {
}) })
// First plan and apply a create operation // First plan and apply a create operation
if _, err := ctx.Plan(); err != nil { if p, err := ctx.Plan(); err != nil {
t.Fatalf("plan err: %s", err) t.Fatalf("plan err: %s", err)
} else {
t.Logf("Step 1 plan: %s", p)
} }
state, err = ctx.Apply() state, err = ctx.Apply()
@ -3733,6 +3737,8 @@ func TestContext2Apply_destroyModuleWithAttrsReferencingResource(t *testing.T) {
t.Fatalf("destroy plan err: %s", err) t.Fatalf("destroy plan err: %s", err)
} }
t.Logf("Step 2 plan: %s", plan)
var buf bytes.Buffer var buf bytes.Buffer
if err := WritePlan(plan, &buf); err != nil { if err := WritePlan(plan, &buf); err != nil {
t.Fatalf("plan write err: %s", err) t.Fatalf("plan write err: %s", err)
@ -3756,6 +3762,8 @@ func TestContext2Apply_destroyModuleWithAttrsReferencingResource(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("destroy apply err: %s", err) t.Fatalf("destroy apply err: %s", err)
} }
t.Logf("Step 2 state: %s", state)
} }
//Test that things were destroyed //Test that things were destroyed
@ -3766,7 +3774,7 @@ module.child:
<no state> <no state>
`) `)
if actual != expected { if actual != expected {
t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual) t.Fatalf("expected:\n\n%s\n\nactual:\n\n%s", expected, actual)
} }
} }

View File

@ -2,6 +2,7 @@ package terraform
import ( import (
"fmt" "fmt"
"strings"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
) )
@ -38,7 +39,12 @@ func (n *NodeApplyableOutput) References() []string {
var result []string var result []string
result = append(result, ReferencesFromConfig(n.Config.RawConfig)...) result = append(result, ReferencesFromConfig(n.Config.RawConfig)...)
for _, v := range result { for _, v := range result {
result = append(result, v+".destroy") split := strings.Split(v, "/")
for i, s := range split {
split[i] = s + ".destroy"
}
result = append(result, strings.Join(split, "/"))
} }
return result return result

View File

@ -1,6 +1,8 @@
package terraform package terraform
import ( import (
"fmt"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/dag"
) )
@ -43,11 +45,43 @@ func (n *NodeAbstractResource) Path() []string {
// GraphNodeReferenceable // GraphNodeReferenceable
func (n *NodeAbstractResource) ReferenceableName() []string { func (n *NodeAbstractResource) ReferenceableName() []string {
if n.Config == nil { // We always are referenceable as "type.name" as long as
// we have a config or address. Determine what that value is.
var id string
if n.Config != nil {
id = n.Config.Id()
} else if n.Addr != nil {
addrCopy := n.Addr.Copy()
addrCopy.Index = -1
id = addrCopy.String()
} else {
// No way to determine our type.name, just return
return nil return nil
} }
return []string{n.Config.Id()} var result []string
// Always include our own ID. This is primarily for backwards
// compatibility with states that didn't yet support the more
// specific dep string.
result = append(result, id)
// We represent all multi-access
result = append(result, fmt.Sprintf("%s.*", id))
// We represent either a specific number, or all numbers
suffix := "N"
if n.Addr != nil {
idx := n.Addr.Index
if idx == -1 {
idx = 0
}
suffix = fmt.Sprintf("%d", idx)
}
result = append(result, fmt.Sprintf("%s.%s", id, suffix))
return result
} }
// GraphNodeReferencer // GraphNodeReferencer

View File

@ -29,6 +29,10 @@ type ResourceAddress struct {
// Copy returns a copy of this ResourceAddress // Copy returns a copy of this ResourceAddress
func (r *ResourceAddress) Copy() *ResourceAddress { func (r *ResourceAddress) Copy() *ResourceAddress {
if r == nil {
return nil
}
n := &ResourceAddress{ n := &ResourceAddress{
Path: make([]string, 0, len(r.Path)), Path: make([]string, 0, len(r.Path)),
Index: r.Index, Index: r.Index,

View File

@ -120,10 +120,14 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
&AttachStateTransformer{State: t.State}, &AttachStateTransformer{State: t.State},
} }
// Go through the all destroyers and find what they're destroying. // Go through all the nodes being destroyed and create a graph.
// Use this to find the dependencies, look up if any of them are being // The resulting graph is only of things being CREATED. For example,
// destroyed, and to make the proper edge. // following our example, the resulting graph would be:
for d, dns := range destroyers { //
// A, B (with no edges)
//
var tempG Graph
for d, _ := range destroyers {
// d is what is being destroyed. We parse the resource address // d is what is being destroyed. We parse the resource address
// which it came from it is a panic if this fails. // which it came from it is a panic if this fails.
addr, err := ParseResourceAddress(d) addr, err := ParseResourceAddress(d)
@ -135,27 +139,48 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
// find the dependencies we need to: build a graph and use the // find the dependencies we need to: build a graph and use the
// attach config and state transformers then ask for references. // attach config and state transformers then ask for references.
node := &NodeAbstractResource{Addr: addr} node := &NodeAbstractResource{Addr: addr}
{ tempG.Add(node)
var g Graph }
g.Add(node)
for _, s := range steps {
if err := s.Transform(&g); err != nil {
return err
}
}
}
// Get the references of the creation node. If it has none, // Run the graph transforms so we have the information we need to
// then there are no edges to make here. // build references.
prefix := modulePrefixStr(normalizeModulePath(addr.Path)) for _, s := range steps {
deps := modulePrefixList(node.References(), prefix) if err := s.Transform(&tempG); err != nil {
return err
}
}
// Create a reference map for easy lookup
refMap := NewReferenceMap(tempG.Vertices())
// Go through all the nodes in the graph and determine what they
// depend on.
for _, v := range tempG.Vertices() {
// Find all the references
refs, _ := refMap.References(v)
log.Printf( log.Printf(
"[TRACE] DestroyEdgeTransformer: creation of %q depends on %#v", "[TRACE] DestroyEdgeTransformer: creation node %q references %v",
d, deps) dag.VertexName(v), refs)
if len(deps) == 0 {
// If we have no references, then we won't need to do anything
if len(refs) == 0 {
continue continue
} }
// Get the destroy node for this. In the example of our struct,
// we are currently at B and we're looking for B_d.
rn, ok := v.(GraphNodeResource)
if !ok {
continue
}
addr := rn.ResourceAddr()
if addr == nil {
continue
}
dns := destroyers[addr.String()]
// We have dependencies, check if any are being destroyed // We have dependencies, check if any are being destroyed
// to build the list of things that we must depend on! // to build the list of things that we must depend on!
// //
@ -163,17 +188,28 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
// //
// B_d => A_d => A => B // B_d => A_d => A => B
// //
// Then at this point in the algorithm we started with A_d, // Then at this point in the algorithm we started with B_d,
// we built A (to get dependencies), and we found B. We're now looking // we built B (to get dependencies), and we found A. We're now looking
// to see if B_d exists. // to see if A_d exists.
var depDestroyers []dag.Vertex var depDestroyers []dag.Vertex
for _, d := range deps { for _, v := range refs {
if ds, ok := destroyers[d]; ok { rn, ok := v.(GraphNodeResource)
if !ok {
continue
}
addr := rn.ResourceAddr()
if addr == nil {
continue
}
key := addr.String()
if ds, ok := destroyers[key]; ok {
for _, d := range ds { for _, d := range ds {
depDestroyers = append(depDestroyers, d.(dag.Vertex)) depDestroyers = append(depDestroyers, d.(dag.Vertex))
log.Printf( log.Printf(
"[TRACE] DestroyEdgeTransformer: destruction of %q depends on %s", "[TRACE] DestroyEdgeTransformer: destruction of %q depends on %s",
addr.String(), dag.VertexName(d)) key, dag.VertexName(d))
} }
} }
} }

View File

@ -5,7 +5,7 @@ import (
"testing" "testing"
) )
func TestDestroyEdgeTransformer(t *testing.T) { func TestDestroyEdgeTransformer_basic(t *testing.T) {
g := Graph{Path: RootModulePath} g := Graph{Path: RootModulePath}
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"}) g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
g.Add(&graphNodeDestroyerTest{AddrString: "test.B"}) g.Add(&graphNodeDestroyerTest{AddrString: "test.B"})

View File

@ -3,6 +3,7 @@ package terraform
import ( import (
"fmt" "fmt"
"log" "log"
"strings"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/dag"
@ -90,27 +91,37 @@ func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []string) {
var matches []dag.Vertex var matches []dag.Vertex
var missing []string var missing []string
prefix := m.prefix(v) prefix := m.prefix(v)
for _, n := range rn.References() { for _, ns := range rn.References() {
n = prefix + n found := false
parents, ok := m.references[n] for _, n := range strings.Split(ns, "/") {
if !ok { n = prefix + n
missing = append(missing, n) parents, ok := m.references[n]
continue if !ok {
} continue
// Make sure this isn't a self reference, which isn't included
selfRef := false
for _, p := range parents {
if p == v {
selfRef = true
break
} }
}
if selfRef { // Mark that we found a match
continue found = true
// Make sure this isn't a self reference, which isn't included
selfRef := false
for _, p := range parents {
if p == v {
selfRef = true
break
}
}
if selfRef {
continue
}
matches = append(matches, parents...)
break
} }
matches = append(matches, parents...) if !found {
missing = append(missing, ns)
}
} }
return matches, missing return matches, missing
@ -233,8 +244,23 @@ func ReferenceFromInterpolatedVar(v config.InterpolatedVariable) []string {
case *config.ModuleVariable: case *config.ModuleVariable:
return []string{fmt.Sprintf("module.%s.output.%s", v.Name, v.Field)} return []string{fmt.Sprintf("module.%s.output.%s", v.Name, v.Field)}
case *config.ResourceVariable: case *config.ResourceVariable:
result := []string{v.ResourceId()} id := v.ResourceId()
return result
// If we have a multi-reference (splat), then we depend on ALL
// resources with this type/name.
if v.Multi && v.Index == -1 {
return []string{fmt.Sprintf("%s.*", id)}
}
// Otherwise, we depend on a specific index.
idx := v.Index
if !v.Multi || v.Index == -1 {
idx = 0
}
// Depend on the index, as well as "N" which represents the
// un-expanded set of resources.
return []string{fmt.Sprintf("%s.%d/%s.N", id, idx, id)}
case *config.UserVariable: case *config.UserVariable:
return []string{fmt.Sprintf("var.%s", v.Name)} return []string{fmt.Sprintf("var.%s", v.Name)}
default: default:

View File

@ -88,6 +88,56 @@ func TestReferenceTransformer_path(t *testing.T) {
} }
} }
func TestReferenceTransformer_backup(t *testing.T) {
g := Graph{Path: RootModulePath}
g.Add(&graphNodeRefParentTest{
NameValue: "A",
Names: []string{"A"},
})
g.Add(&graphNodeRefChildTest{
NameValue: "B",
Refs: []string{"C/A"},
})
tf := &ReferenceTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformRefBackupStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestReferenceTransformer_backupPrimary(t *testing.T) {
g := Graph{Path: RootModulePath}
g.Add(&graphNodeRefParentTest{
NameValue: "A",
Names: []string{"A"},
})
g.Add(&graphNodeRefChildTest{
NameValue: "B",
Refs: []string{"C/A"},
})
g.Add(&graphNodeRefParentTest{
NameValue: "C",
Names: []string{"C"},
})
tf := &ReferenceTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformRefBackupPrimaryStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestReferenceMapReferences(t *testing.T) { func TestReferenceMapReferences(t *testing.T) {
cases := map[string]struct { cases := map[string]struct {
Nodes []dag.Vertex Nodes []dag.Vertex
@ -202,6 +252,19 @@ B
A A
` `
const testTransformRefBackupStr = `
A
B
A
`
const testTransformRefBackupPrimaryStr = `
A
B
C
C
`
const testTransformRefPathStr = ` const testTransformRefPathStr = `
A A
B B