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:
parent
c0d2493156
commit
19350d617d
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"})
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue