Merge pull request #9388 from hashicorp/f-apply-builder
terraform: new apply graph builder based on the "diff"
This commit is contained in:
commit
14cff93b67
|
@ -10,6 +10,7 @@ install:
|
||||||
- bash scripts/gogetcookie.sh
|
- bash scripts/gogetcookie.sh
|
||||||
script:
|
script:
|
||||||
- make test vet
|
- make test vet
|
||||||
|
- make test TEST=./terraform TESTARGS=-Xnew-apply
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
|
|
|
@ -95,6 +95,26 @@ func (c *ApplyCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for the new apply
|
||||||
|
if terraform.X_newApply {
|
||||||
|
desc := "Experimental new apply 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-apply",
|
||||||
|
Query: "Experimental feature enabled: new apply 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,
|
||||||
|
|
|
@ -328,6 +328,9 @@ func (m *Meta) flagSet(n string) *flag.FlagSet {
|
||||||
f.Var((*FlagKVFile)(&m.autoVariables), m.autoKey, "variable file")
|
f.Var((*FlagKVFile)(&m.autoVariables), m.autoKey, "variable file")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Experimental features
|
||||||
|
f.BoolVar(&terraform.X_newApply, "Xnew-apply", false, "experiment: new apply")
|
||||||
|
|
||||||
// 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
|
||||||
// a pipe, use a scanner to break it into lines, and output each line
|
// a pipe, use a scanner to break it into lines, and output each line
|
||||||
|
|
|
@ -66,6 +66,10 @@ func (t *Tree) Config() *config.Config {
|
||||||
|
|
||||||
// Child returns the child with the given path (by name).
|
// Child returns the child with the given path (by name).
|
||||||
func (t *Tree) Child(path []string) *Tree {
|
func (t *Tree) Child(path []string) *Tree {
|
||||||
|
if t == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTreeChild(t *testing.T) {
|
func TestTreeChild(t *testing.T) {
|
||||||
|
var nilTree *Tree
|
||||||
|
if nilTree.Child(nil) != nil {
|
||||||
|
t.Fatal("child should be nil")
|
||||||
|
}
|
||||||
|
|
||||||
storage := testStorage(t)
|
storage := testStorage(t)
|
||||||
tree := NewTree("", testConfig(t, "child"))
|
tree := NewTree("", testConfig(t, "child"))
|
||||||
if err := tree.Load(storage, GetModeGet); err != nil {
|
if err := tree.Load(storage, GetModeGet); err != nil {
|
||||||
|
|
|
@ -191,17 +191,19 @@ func (r *RawConfig) Merge(other *RawConfig) *RawConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the unknown keys
|
// Build the unknown keys
|
||||||
unknownKeys := make(map[string]struct{})
|
if len(r.unknownKeys) > 0 || len(other.unknownKeys) > 0 {
|
||||||
for _, k := range r.unknownKeys {
|
unknownKeys := make(map[string]struct{})
|
||||||
unknownKeys[k] = struct{}{}
|
for _, k := range r.unknownKeys {
|
||||||
}
|
unknownKeys[k] = struct{}{}
|
||||||
for _, k := range other.unknownKeys {
|
}
|
||||||
unknownKeys[k] = struct{}{}
|
for _, k := range other.unknownKeys {
|
||||||
}
|
unknownKeys[k] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
result.unknownKeys = make([]string, 0, len(unknownKeys))
|
result.unknownKeys = make([]string, 0, len(unknownKeys))
|
||||||
for k, _ := range unknownKeys {
|
for k, _ := range unknownKeys {
|
||||||
result.unknownKeys = append(result.unknownKeys, k)
|
result.unknownKeys = append(result.unknownKeys, k)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -27,7 +27,7 @@ func TestNewRawConfig(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRawConfig(t *testing.T) {
|
func TestRawConfig_basic(t *testing.T) {
|
||||||
raw := map[string]interface{}{
|
raw := map[string]interface{}{
|
||||||
"foo": "${var.bar}",
|
"foo": "${var.bar}",
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestRawConfig is used to create a RawConfig for testing.
|
||||||
|
func TestRawConfig(t *testing.T, c map[string]interface{}) *RawConfig {
|
||||||
|
cfg, err := NewRawConfig(c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
26
dag/graph.go
26
dag/graph.go
|
@ -48,6 +48,32 @@ func (g *Graph) Edges() []Edge {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EdgesFrom returns the list of edges from the given source.
|
||||||
|
func (g *Graph) EdgesFrom(v Vertex) []Edge {
|
||||||
|
var result []Edge
|
||||||
|
from := hashcode(v)
|
||||||
|
for _, e := range g.Edges() {
|
||||||
|
if hashcode(e.Source()) == from {
|
||||||
|
result = append(result, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// EdgesTo returns the list of edges to the given target.
|
||||||
|
func (g *Graph) EdgesTo(v Vertex) []Edge {
|
||||||
|
var result []Edge
|
||||||
|
search := hashcode(v)
|
||||||
|
for _, e := range g.Edges() {
|
||||||
|
if hashcode(e.Target()) == search {
|
||||||
|
result = append(result, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// HasVertex checks if the given Vertex is present in the graph.
|
// HasVertex checks if the given Vertex is present in the graph.
|
||||||
func (g *Graph) HasVertex(v Vertex) bool {
|
func (g *Graph) HasVertex(v Vertex) bool {
|
||||||
return g.vertices.Include(v)
|
return g.vertices.Include(v)
|
||||||
|
|
|
@ -124,6 +124,52 @@ func TestGraphHasEdge(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGraphEdgesFrom(t *testing.T) {
|
||||||
|
var g Graph
|
||||||
|
g.Add(1)
|
||||||
|
g.Add(2)
|
||||||
|
g.Add(3)
|
||||||
|
g.Connect(BasicEdge(1, 3))
|
||||||
|
g.Connect(BasicEdge(2, 3))
|
||||||
|
|
||||||
|
edges := g.EdgesFrom(1)
|
||||||
|
|
||||||
|
var expected Set
|
||||||
|
expected.Add(BasicEdge(1, 3))
|
||||||
|
|
||||||
|
var s Set
|
||||||
|
for _, e := range edges {
|
||||||
|
s.Add(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Intersection(&expected).Len() != expected.Len() {
|
||||||
|
t.Fatalf("bad: %#v", edges)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGraphEdgesTo(t *testing.T) {
|
||||||
|
var g Graph
|
||||||
|
g.Add(1)
|
||||||
|
g.Add(2)
|
||||||
|
g.Add(3)
|
||||||
|
g.Connect(BasicEdge(1, 3))
|
||||||
|
g.Connect(BasicEdge(1, 2))
|
||||||
|
|
||||||
|
edges := g.EdgesTo(3)
|
||||||
|
|
||||||
|
var expected Set
|
||||||
|
expected.Add(BasicEdge(1, 3))
|
||||||
|
|
||||||
|
var s Set
|
||||||
|
for _, e := range edges {
|
||||||
|
s.Add(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Intersection(&expected).Len() != expected.Len() {
|
||||||
|
t.Fatalf("bad: %#v", edges)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type hashVertex struct {
|
type hashVertex struct {
|
||||||
code interface{}
|
code interface{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
go test ./terraform | grep -E '(FAIL|panic)' | tee /dev/tty | wc -l
|
|
@ -13,6 +13,16 @@ import (
|
||||||
"github.com/hashicorp/terraform/config/module"
|
"github.com/hashicorp/terraform/config/module"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Variables prefixed with X_ are experimental features. They can be enabled
|
||||||
|
// by setting them to true. This should be done before any API is called.
|
||||||
|
// These should be expected to be removed at some point in the future; each
|
||||||
|
// option should mention a schedule.
|
||||||
|
var (
|
||||||
|
// X_newApply will enable the new apply graph. This will be removed
|
||||||
|
// and be on by default in 0.8.0.
|
||||||
|
X_newApply = 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
|
||||||
// is called on Context.
|
// is called on Context.
|
||||||
type InputMode byte
|
type InputMode byte
|
||||||
|
@ -353,21 +363,79 @@ func (c *Context) Apply() (*State, error) {
|
||||||
// Copy our own state
|
// Copy our own state
|
||||||
c.state = c.state.DeepCopy()
|
c.state = c.state.DeepCopy()
|
||||||
|
|
||||||
// Build the graph
|
// Build the original graph. This is before the new graph builders
|
||||||
graph, err := c.Graph(&ContextGraphOpts{Validate: true})
|
// coming in 0.8. We do this for shadow graphing.
|
||||||
|
oldGraph, err := c.Graph(&ContextGraphOpts{Validate: true})
|
||||||
|
if err != nil && X_newApply {
|
||||||
|
// If we had an error graphing but we're using the new graph,
|
||||||
|
// just set it to nil and let it go. There are some features that
|
||||||
|
// may work with the new graph that don't with the old.
|
||||||
|
oldGraph = nil
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the walk
|
// Build the new graph. We do this no matter what so we can shadow it.
|
||||||
var walker *ContextGraphWalker
|
newGraph, err := (&ApplyGraphBuilder{
|
||||||
if c.destroy {
|
Module: c.module,
|
||||||
walker, err = c.walk(graph, graph, walkDestroy)
|
Diff: c.diff,
|
||||||
} else {
|
State: c.state,
|
||||||
//walker, err = c.walk(graph, nil, walkApply)
|
Providers: c.components.ResourceProviders(),
|
||||||
walker, err = c.walk(graph, graph, walkApply)
|
Provisioners: c.components.ResourceProvisioners(),
|
||||||
|
}).Build(RootModulePath)
|
||||||
|
if err != nil && !X_newApply {
|
||||||
|
// 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.
|
||||||
|
c.shadowErr = multierror.Append(c.shadowErr, fmt.Errorf(
|
||||||
|
"Error building new apply graph: %s", err))
|
||||||
|
|
||||||
|
newGraph = nil
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine what is the real and what is the shadow. The logic here
|
||||||
|
// is straightforward though the if statements are not:
|
||||||
|
//
|
||||||
|
// * Destroy mode - always use original, shadow with nothing because
|
||||||
|
// we're only testing the new APPLY graph.
|
||||||
|
// * Apply with new apply - use new graph, shadow is new graph. We can't
|
||||||
|
// shadow with the old graph because the old graph does a lot more
|
||||||
|
// that it shouldn't.
|
||||||
|
// * Apply with old apply - use old graph, shadow with new graph.
|
||||||
|
//
|
||||||
|
real := oldGraph
|
||||||
|
shadow := newGraph
|
||||||
|
if c.destroy {
|
||||||
|
log.Printf("[WARN] terraform: real graph is original, shadow is nil")
|
||||||
|
shadow = nil
|
||||||
|
} else {
|
||||||
|
if X_newApply {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the operation
|
||||||
|
operation := walkApply
|
||||||
|
if c.destroy {
|
||||||
|
operation = walkDestroy
|
||||||
|
}
|
||||||
|
|
||||||
|
// This shouldn't happen, so assert it. This is before any state changes
|
||||||
|
// so it is safe to crash here.
|
||||||
|
if real == nil {
|
||||||
|
panic("nil real graph")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk the graph
|
||||||
|
walker, err := c.walk(real, shadow, operation)
|
||||||
if len(walker.ValidationErrors) > 0 {
|
if len(walker.ValidationErrors) > 0 {
|
||||||
err = multierror.Append(err, walker.ValidationErrors...)
|
err = multierror.Append(err, walker.ValidationErrors...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -835,17 +835,21 @@ func TestContext2Apply_cancel(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the Apply in a goroutine
|
// Start the Apply in a goroutine
|
||||||
|
var applyErr error
|
||||||
stateCh := make(chan *State)
|
stateCh := make(chan *State)
|
||||||
go func() {
|
go func() {
|
||||||
state, err := ctx.Apply()
|
state, err := ctx.Apply()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
applyErr = err
|
||||||
}
|
}
|
||||||
|
|
||||||
stateCh <- state
|
stateCh <- state
|
||||||
}()
|
}()
|
||||||
|
|
||||||
state := <-stateCh
|
state := <-stateCh
|
||||||
|
if applyErr != nil {
|
||||||
|
t.Fatalf("err: %s", applyErr)
|
||||||
|
}
|
||||||
|
|
||||||
mod := state.RootModule()
|
mod := state.RootModule()
|
||||||
if len(mod.Resources) != 1 {
|
if len(mod.Resources) != 1 {
|
||||||
|
@ -956,7 +960,7 @@ func TestContext2Apply_countDecrease(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContext2Apply_countDecreaseToOne(t *testing.T) {
|
func TestContext2Apply_countDecreaseToOneX(t *testing.T) {
|
||||||
m := testModule(t, "apply-count-dec-one")
|
m := testModule(t, "apply-count-dec-one")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
p.ApplyFn = testApplyFn
|
p.ApplyFn = testApplyFn
|
||||||
|
@ -1228,7 +1232,7 @@ aws_instance.foo:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContext2Apply_module(t *testing.T) {
|
func TestContext2Apply_moduleBasic(t *testing.T) {
|
||||||
m := testModule(t, "apply-module")
|
m := testModule(t, "apply-module")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
p.ApplyFn = testApplyFn
|
p.ApplyFn = testApplyFn
|
||||||
|
@ -1252,7 +1256,7 @@ func TestContext2Apply_module(t *testing.T) {
|
||||||
actual := strings.TrimSpace(state.String())
|
actual := strings.TrimSpace(state.String())
|
||||||
expected := strings.TrimSpace(testTerraformApplyModuleStr)
|
expected := strings.TrimSpace(testTerraformApplyModuleStr)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("bad: \n%s", actual)
|
t.Fatalf("bad, expected:\n%s\n\nactual:\n%s", expected, actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1751,10 +1755,12 @@ func TestContext2Apply_multiVar(t *testing.T) {
|
||||||
|
|
||||||
actual := state.RootModule().Outputs["output"]
|
actual := state.RootModule().Outputs["output"]
|
||||||
expected := "bar0,bar1,bar2"
|
expected := "bar0,bar1,bar2"
|
||||||
if actual.Value != expected {
|
if actual == nil || actual.Value != expected {
|
||||||
t.Fatalf("bad: \n%s", actual)
|
t.Fatalf("bad: \n%s", actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Logf("Initial state: %s", state.String())
|
||||||
|
|
||||||
// Apply again, reduce the count to 1
|
// Apply again, reduce the count to 1
|
||||||
{
|
{
|
||||||
ctx := testContext2(t, &ContextOpts{
|
ctx := testContext2(t, &ContextOpts{
|
||||||
|
@ -1777,7 +1783,13 @@ func TestContext2Apply_multiVar(t *testing.T) {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Logf("End state: %s", state.String())
|
||||||
|
|
||||||
actual := state.RootModule().Outputs["output"]
|
actual := state.RootModule().Outputs["output"]
|
||||||
|
if actual == nil {
|
||||||
|
t.Fatal("missing output")
|
||||||
|
}
|
||||||
|
|
||||||
expected := "bar0"
|
expected := "bar0"
|
||||||
if actual.Value != expected {
|
if actual.Value != expected {
|
||||||
t.Fatalf("bad: \n%s", actual)
|
t.Fatalf("bad: \n%s", actual)
|
||||||
|
@ -1903,6 +1915,43 @@ func TestContext2Apply_providerComputedVar(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContext2Apply_providerConfigureDisabled(t *testing.T) {
|
||||||
|
m := testModule(t, "apply-provider-configure-disabled")
|
||||||
|
p := testProvider("aws")
|
||||||
|
p.ApplyFn = testApplyFn
|
||||||
|
p.DiffFn = testDiffFn
|
||||||
|
|
||||||
|
called := false
|
||||||
|
p.ConfigureFn = func(c *ResourceConfig) error {
|
||||||
|
called = true
|
||||||
|
|
||||||
|
if _, ok := c.Get("value"); !ok {
|
||||||
|
return fmt.Errorf("value is not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := testContext2(t, &ContextOpts{
|
||||||
|
Module: m,
|
||||||
|
Providers: map[string]ResourceProviderFactory{
|
||||||
|
"aws": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if _, err := ctx.Plan(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := ctx.Apply(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !called {
|
||||||
|
t.Fatal("configure never called")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContext2Apply_Provisioner_compute(t *testing.T) {
|
func TestContext2Apply_Provisioner_compute(t *testing.T) {
|
||||||
m := testModule(t, "apply-provisioner-compute")
|
m := testModule(t, "apply-provisioner-compute")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
@ -2779,7 +2828,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")
|
m := testModule(t, "apply-destroy")
|
||||||
h := new(HookRecordApplyOrder)
|
h := new(HookRecordApplyOrder)
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
@ -2839,6 +2888,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
|
// https://github.com/hashicorp/terraform/issues/2767
|
||||||
func TestContext2Apply_destroyModulePrefix(t *testing.T) {
|
func TestContext2Apply_destroyModulePrefix(t *testing.T) {
|
||||||
m := testModule(t, "apply-destroy-module-resource-prefix")
|
m := testModule(t, "apply-destroy-module-resource-prefix")
|
||||||
|
@ -3021,6 +3129,8 @@ func TestContext2Apply_destroyModuleWithAttrsReferencingResource(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("apply err: %s", err)
|
t.Fatalf("apply err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Logf("Step 1 state: %s", state)
|
||||||
}
|
}
|
||||||
|
|
||||||
h := new(HookRecordApplyOrder)
|
h := new(HookRecordApplyOrder)
|
||||||
|
@ -3753,7 +3863,7 @@ func TestContext2Apply_idAttr(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContext2Apply_output(t *testing.T) {
|
func TestContext2Apply_outputBasic(t *testing.T) {
|
||||||
m := testModule(t, "apply-output")
|
m := testModule(t, "apply-output")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
p.ApplyFn = testApplyFn
|
p.ApplyFn = testApplyFn
|
||||||
|
@ -3935,7 +4045,7 @@ func TestContext2Apply_outputMultiIndex(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContext2Apply_taint(t *testing.T) {
|
func TestContext2Apply_taintX(t *testing.T) {
|
||||||
m := testModule(t, "apply-taint")
|
m := testModule(t, "apply-taint")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
|
||||||
|
@ -3983,8 +4093,10 @@ func TestContext2Apply_taint(t *testing.T) {
|
||||||
State: s,
|
State: s,
|
||||||
})
|
})
|
||||||
|
|
||||||
if _, err := ctx.Plan(); err != nil {
|
if p, err := ctx.Plan(); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
} else {
|
||||||
|
t.Logf("plan: %s", p)
|
||||||
}
|
}
|
||||||
|
|
||||||
state, err := ctx.Apply()
|
state, err := ctx.Apply()
|
||||||
|
@ -4483,8 +4595,8 @@ func TestContext2Apply_targetedModuleResource(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod := state.ModuleByPath([]string{"root", "child"})
|
mod := state.ModuleByPath([]string{"root", "child"})
|
||||||
if len(mod.Resources) != 1 {
|
if mod == nil || len(mod.Resources) != 1 {
|
||||||
t.Fatalf("expected 1 resource, got: %#v", mod.Resources)
|
t.Fatalf("expected 1 resource, got: %#v", mod)
|
||||||
}
|
}
|
||||||
|
|
||||||
checkStateString(t, state, `
|
checkStateString(t, state, `
|
||||||
|
@ -4675,8 +4787,10 @@ func TestContext2Apply_createBefore_depends(t *testing.T) {
|
||||||
State: state,
|
State: state,
|
||||||
})
|
})
|
||||||
|
|
||||||
if _, err := ctx.Plan(); err != nil {
|
if p, err := ctx.Plan(); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
} else {
|
||||||
|
t.Logf("plan: %s", p)
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Active = true
|
h.Active = true
|
||||||
|
@ -4693,7 +4807,7 @@ func TestContext2Apply_createBefore_depends(t *testing.T) {
|
||||||
actual := strings.TrimSpace(state.String())
|
actual := strings.TrimSpace(state.String())
|
||||||
expected := strings.TrimSpace(testTerraformApplyDependsCreateBeforeStr)
|
expected := strings.TrimSpace(testTerraformApplyDependsCreateBeforeStr)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("bad: \n%s\n%s", actual, expected)
|
t.Fatalf("bad: \n%s\n\n%s", actual, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that things were managed _in the right order_
|
// Test that things were managed _in the right order_
|
||||||
|
|
|
@ -72,6 +72,10 @@ func (d *Diff) RootModule() *ModuleDiff {
|
||||||
|
|
||||||
// Empty returns true if the diff has no changes.
|
// Empty returns true if the diff has no changes.
|
||||||
func (d *Diff) Empty() bool {
|
func (d *Diff) Empty() bool {
|
||||||
|
if d == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
for _, m := range d.Modules {
|
for _, m := range d.Modules {
|
||||||
if !m.Empty() {
|
if !m.Empty() {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -7,7 +7,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDiffEmpty(t *testing.T) {
|
func TestDiffEmpty(t *testing.T) {
|
||||||
diff := new(Diff)
|
var diff *Diff
|
||||||
|
if !diff.Empty() {
|
||||||
|
t.Fatal("should be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
diff = new(Diff)
|
||||||
if !diff.Empty() {
|
if !diff.Empty() {
|
||||||
t.Fatal("should be empty")
|
t.Fatal("should be empty")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DestroyEdge is an edge that represents a standard "destroy" relationship:
|
||||||
|
// Target depends on Source because Source is destroying.
|
||||||
|
type DestroyEdge struct {
|
||||||
|
S, T dag.Vertex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DestroyEdge) Hashcode() interface{} { return fmt.Sprintf("%p-%p", e.S, e.T) }
|
||||||
|
func (e *DestroyEdge) Source() dag.Vertex { return e.S }
|
||||||
|
func (e *DestroyEdge) Target() dag.Vertex { return e.T }
|
|
@ -0,0 +1,78 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EvalCountFixZeroOneBoundaryGlobal is an EvalNode that fixes up the state
|
||||||
|
// when there is a resource count with zero/one boundary, i.e. fixing
|
||||||
|
// a resource named "aws_instance.foo" to "aws_instance.foo.0" and vice-versa.
|
||||||
|
//
|
||||||
|
// This works on the global state.
|
||||||
|
type EvalCountFixZeroOneBoundaryGlobal struct{}
|
||||||
|
|
||||||
|
// TODO: test
|
||||||
|
func (n *EvalCountFixZeroOneBoundaryGlobal) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
|
// Get the state and lock it since we'll potentially modify it
|
||||||
|
state, lock := ctx.State()
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
|
||||||
|
// Prune the state since we require a clean state to work
|
||||||
|
state.prune()
|
||||||
|
|
||||||
|
// Go through each modules since the boundaries are restricted to a
|
||||||
|
// module scope.
|
||||||
|
for _, m := range state.Modules {
|
||||||
|
if err := n.fixModule(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *EvalCountFixZeroOneBoundaryGlobal) fixModule(m *ModuleState) error {
|
||||||
|
// Counts keeps track of keys and their counts
|
||||||
|
counts := make(map[string]int)
|
||||||
|
for k, _ := range m.Resources {
|
||||||
|
// Parse the key
|
||||||
|
key, err := ParseResourceStateKey(k)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the index to -1 so that we can keep count
|
||||||
|
key.Index = -1
|
||||||
|
|
||||||
|
// Increment
|
||||||
|
counts[key.String()]++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through the counts and do the fixup for each resource
|
||||||
|
for raw, count := range counts {
|
||||||
|
// Search and replace this resource
|
||||||
|
search := raw
|
||||||
|
replace := raw + ".0"
|
||||||
|
if count < 2 {
|
||||||
|
search, replace = replace, search
|
||||||
|
}
|
||||||
|
log.Printf("[TRACE] EvalCountFixZeroOneBoundaryGlobal: count %d, search %q, replace %q", count, search, replace)
|
||||||
|
|
||||||
|
// Look for the resource state. If we don't have one, then it is okay.
|
||||||
|
rs, ok := m.Resources[search]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the replacement key exists, we just keep both
|
||||||
|
if _, ok := m.Resources[replace]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Resources[replace] = rs
|
||||||
|
delete(m.Resources, search)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -28,6 +28,11 @@ type Graph struct {
|
||||||
// RootModuleName
|
// RootModuleName
|
||||||
Path []string
|
Path []string
|
||||||
|
|
||||||
|
// annotations are the annotations that are added to vertices. Annotations
|
||||||
|
// are arbitrary metadata taht is used for various logic. Annotations
|
||||||
|
// should have unique keys that are referenced via constants.
|
||||||
|
annotations map[dag.Vertex]map[string]interface{}
|
||||||
|
|
||||||
// dependableMap is a lookaside table for fast lookups for connecting
|
// dependableMap is a lookaside table for fast lookups for connecting
|
||||||
// dependencies by their GraphNodeDependable value to avoid O(n^3)-like
|
// dependencies by their GraphNodeDependable value to avoid O(n^3)-like
|
||||||
// situations and turn them into O(1) with respect to the number of new
|
// situations and turn them into O(1) with respect to the number of new
|
||||||
|
@ -37,6 +42,29 @@ type Graph struct {
|
||||||
once sync.Once
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Annotations returns the annotations that are configured for the
|
||||||
|
// given vertex. The map is guaranteed to be non-nil but may be empty.
|
||||||
|
//
|
||||||
|
// The returned map may be modified to modify the annotations of the
|
||||||
|
// vertex.
|
||||||
|
func (g *Graph) Annotations(v dag.Vertex) map[string]interface{} {
|
||||||
|
g.once.Do(g.init)
|
||||||
|
|
||||||
|
// If this vertex isn't in the graph, then just return an empty map
|
||||||
|
if !g.HasVertex(v) {
|
||||||
|
return map[string]interface{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the map, if it doesn't exist yet then initialize it
|
||||||
|
m, ok := g.annotations[v]
|
||||||
|
if !ok {
|
||||||
|
m = make(map[string]interface{})
|
||||||
|
g.annotations[v] = m
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
// Add is the same as dag.Graph.Add.
|
// Add is the same as dag.Graph.Add.
|
||||||
func (g *Graph) Add(v dag.Vertex) dag.Vertex {
|
func (g *Graph) Add(v dag.Vertex) dag.Vertex {
|
||||||
g.once.Do(g.init)
|
g.once.Do(g.init)
|
||||||
|
@ -51,6 +79,14 @@ func (g *Graph) Add(v dag.Vertex) dag.Vertex {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this initializes annotations, then do that
|
||||||
|
if av, ok := v.(GraphNodeAnnotationInit); ok {
|
||||||
|
as := g.Annotations(v)
|
||||||
|
for k, v := range av.AnnotationInit() {
|
||||||
|
as[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,12 +101,17 @@ func (g *Graph) Remove(v dag.Vertex) dag.Vertex {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the annotations
|
||||||
|
delete(g.annotations, v)
|
||||||
|
|
||||||
// Call upwards to remove it from the actual graph
|
// Call upwards to remove it from the actual graph
|
||||||
return g.Graph.Remove(v)
|
return g.Graph.Remove(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace is the same as dag.Graph.Replace
|
// Replace is the same as dag.Graph.Replace
|
||||||
func (g *Graph) Replace(o, n dag.Vertex) bool {
|
func (g *Graph) Replace(o, n dag.Vertex) bool {
|
||||||
|
g.once.Do(g.init)
|
||||||
|
|
||||||
// Go through and update our lookaside to point to the new vertex
|
// Go through and update our lookaside to point to the new vertex
|
||||||
for k, v := range g.dependableMap {
|
for k, v := range g.dependableMap {
|
||||||
if v == o {
|
if v == o {
|
||||||
|
@ -82,6 +123,12 @@ func (g *Graph) Replace(o, n dag.Vertex) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move the annotation if it exists
|
||||||
|
if m, ok := g.annotations[o]; ok {
|
||||||
|
g.annotations[n] = m
|
||||||
|
delete(g.annotations, o)
|
||||||
|
}
|
||||||
|
|
||||||
return g.Graph.Replace(o, n)
|
return g.Graph.Replace(o, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,6 +200,10 @@ func (g *Graph) Walk(walker GraphWalker) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Graph) init() {
|
func (g *Graph) init() {
|
||||||
|
if g.annotations == nil {
|
||||||
|
g.annotations = make(map[dag.Vertex]map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
if g.dependableMap == nil {
|
if g.dependableMap == nil {
|
||||||
g.dependableMap = make(map[string]dag.Vertex)
|
g.dependableMap = make(map[string]dag.Vertex)
|
||||||
}
|
}
|
||||||
|
@ -179,7 +230,7 @@ func (g *Graph) walk(walker GraphWalker) error {
|
||||||
// with a GraphNodeSubPath impl.
|
// with a GraphNodeSubPath impl.
|
||||||
vertexCtx := ctx
|
vertexCtx := ctx
|
||||||
if pn, ok := v.(GraphNodeSubPath); ok && len(pn.Path()) > 0 {
|
if pn, ok := v.(GraphNodeSubPath); ok && len(pn.Path()) > 0 {
|
||||||
vertexCtx = walker.EnterPath(pn.Path())
|
vertexCtx = walker.EnterPath(normalizeModulePath(pn.Path()))
|
||||||
defer walker.ExitPath(pn.Path())
|
defer walker.ExitPath(pn.Path())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,10 +263,11 @@ func (g *Graph) walk(walker GraphWalker) error {
|
||||||
rerr = err
|
rerr = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if g != nil {
|
||||||
// Walk the subgraph
|
// Walk the subgraph
|
||||||
if rerr = g.walk(walker); rerr != nil {
|
if rerr = g.walk(walker); rerr != nil {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,6 +289,16 @@ func (g *Graph) walk(walker GraphWalker) error {
|
||||||
return g.AcyclicGraph.Walk(walkFn)
|
return g.AcyclicGraph.Walk(walkFn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GraphNodeAnnotationInit is an interface that allows a node to
|
||||||
|
// initialize it's annotations.
|
||||||
|
//
|
||||||
|
// AnnotationInit will be called _once_ when the node is added to a
|
||||||
|
// graph for the first time and is expected to return it's initial
|
||||||
|
// annotations.
|
||||||
|
type GraphNodeAnnotationInit interface {
|
||||||
|
AnnotationInit() map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
// GraphNodeDependable is an interface which says that a node can be
|
// GraphNodeDependable is an interface which says that a node can be
|
||||||
// depended on (an edge can be placed between this node and another) according
|
// depended on (an edge can be placed between this node and another) according
|
||||||
// to the well-known name returned by DependableName.
|
// to the well-known name returned by DependableName.
|
||||||
|
|
|
@ -115,7 +115,7 @@ func (b *BuiltinGraphBuilder) Steps(path []string) []GraphTransformer {
|
||||||
// Provider-related transformations
|
// Provider-related transformations
|
||||||
&MissingProviderTransformer{Providers: b.Providers},
|
&MissingProviderTransformer{Providers: b.Providers},
|
||||||
&ProviderTransformer{},
|
&ProviderTransformer{},
|
||||||
&DisableProviderTransformer{},
|
&DisableProviderTransformerOld{},
|
||||||
|
|
||||||
// Provisioner-related transformations
|
// Provisioner-related transformations
|
||||||
&MissingProvisionerTransformer{Provisioners: b.Provisioners},
|
&MissingProvisionerTransformer{Provisioners: b.Provisioners},
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ApplyGraphBuilder implements GraphBuilder and is responsible for building
|
||||||
|
// a graph for applying a Terraform diff.
|
||||||
|
//
|
||||||
|
// Because the graph is built from the diff (vs. the config or state),
|
||||||
|
// this helps ensure that the apply-time graph doesn't modify any resources
|
||||||
|
// that aren't explicitly in the diff. There are other scenarios where the
|
||||||
|
// diff can be deviated, so this is just one layer of protection.
|
||||||
|
type ApplyGraphBuilder struct {
|
||||||
|
// Module is the root module for the graph to build.
|
||||||
|
Module *module.Tree
|
||||||
|
|
||||||
|
// Diff is the diff to apply.
|
||||||
|
Diff *Diff
|
||||||
|
|
||||||
|
// State is the current state
|
||||||
|
State *State
|
||||||
|
|
||||||
|
// Providers is the list of providers supported.
|
||||||
|
Providers []string
|
||||||
|
|
||||||
|
// Provisioners is the list of provisioners supported.
|
||||||
|
Provisioners []string
|
||||||
|
|
||||||
|
// DisableReduce, if true, will not reduce the graph. Great for testing.
|
||||||
|
DisableReduce bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// See GraphBuilder
|
||||||
|
func (b *ApplyGraphBuilder) Build(path []string) (*Graph, error) {
|
||||||
|
return (&BasicGraphBuilder{
|
||||||
|
Steps: b.Steps(),
|
||||||
|
Validate: true,
|
||||||
|
}).Build(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See GraphBuilder
|
||||||
|
func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
|
||||||
|
// Custom factory for creating providers.
|
||||||
|
providerFactory := func(name string, path []string) GraphNodeProvider {
|
||||||
|
return &NodeApplyableProvider{
|
||||||
|
NameValue: name,
|
||||||
|
PathValue: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
concreteResource := func(a *NodeAbstractResource) dag.Vertex {
|
||||||
|
return &NodeApplyableResource{
|
||||||
|
NodeAbstractResource: a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
steps := []GraphTransformer{
|
||||||
|
// Creates all the nodes represented in the diff.
|
||||||
|
&DiffTransformer{
|
||||||
|
Concrete: concreteResource,
|
||||||
|
|
||||||
|
Diff: b.Diff,
|
||||||
|
Module: b.Module,
|
||||||
|
State: b.State,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Create orphan output nodes
|
||||||
|
&OrphanOutputTransformer{Module: b.Module, State: b.State},
|
||||||
|
|
||||||
|
// Attach the configuration to any resources
|
||||||
|
&AttachResourceConfigTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
// Attach the state
|
||||||
|
&AttachStateTransformer{State: b.State},
|
||||||
|
|
||||||
|
// Destruction ordering
|
||||||
|
&DestroyEdgeTransformer{Module: b.Module, State: b.State},
|
||||||
|
&CBDEdgeTransformer{Module: b.Module, State: b.State},
|
||||||
|
|
||||||
|
// Create all the providers
|
||||||
|
&MissingProviderTransformer{Providers: b.Providers, Factory: providerFactory},
|
||||||
|
&ProviderTransformer{},
|
||||||
|
&DisableProviderTransformer{},
|
||||||
|
&ParentProviderTransformer{},
|
||||||
|
&AttachProviderConfigTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
// Provisioner-related transformations
|
||||||
|
&MissingProvisionerTransformer{Provisioners: b.Provisioners},
|
||||||
|
&ProvisionerTransformer{},
|
||||||
|
|
||||||
|
// Add root variables
|
||||||
|
&RootVariableTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
// Add module variables
|
||||||
|
&ModuleVariableTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
// Add the outputs
|
||||||
|
&OutputTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
// Connect references so ordering is correct
|
||||||
|
&ReferenceTransformer{},
|
||||||
|
|
||||||
|
// Add the node to fix the state count boundaries
|
||||||
|
&CountBoundaryTransformer{},
|
||||||
|
|
||||||
|
// 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,115 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestApplyGraphBuilder_impl(t *testing.T) {
|
||||||
|
var _ GraphBuilder = new(ApplyGraphBuilder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyGraphBuilder(t *testing.T) {
|
||||||
|
diff := &Diff{
|
||||||
|
Modules: []*ModuleDiff{
|
||||||
|
&ModuleDiff{
|
||||||
|
Path: []string{"root"},
|
||||||
|
Resources: map[string]*InstanceDiff{
|
||||||
|
// Verify noop doesn't show up in graph
|
||||||
|
"aws_instance.noop": &InstanceDiff{},
|
||||||
|
|
||||||
|
"aws_instance.create": &InstanceDiff{
|
||||||
|
Attributes: map[string]*ResourceAttrDiff{
|
||||||
|
"name": &ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"aws_instance.other": &InstanceDiff{
|
||||||
|
Attributes: map[string]*ResourceAttrDiff{
|
||||||
|
"name": &ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
&ModuleDiff{
|
||||||
|
Path: []string{"root", "child"},
|
||||||
|
Resources: map[string]*InstanceDiff{
|
||||||
|
"aws_instance.create": &InstanceDiff{
|
||||||
|
Attributes: map[string]*ResourceAttrDiff{
|
||||||
|
"name": &ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"aws_instance.other": &InstanceDiff{
|
||||||
|
Attributes: map[string]*ResourceAttrDiff{
|
||||||
|
"name": &ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b := &ApplyGraphBuilder{
|
||||||
|
Module: testModule(t, "graph-builder-apply-basic"),
|
||||||
|
Diff: diff,
|
||||||
|
Providers: []string{"aws"},
|
||||||
|
Provisioners: []string{"exec"},
|
||||||
|
DisableReduce: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
g, err := b.Build(RootModulePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(g.Path, RootModulePath) {
|
||||||
|
t.Fatalf("bad: %#v", g.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(testApplyGraphBuilderStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad: %s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testApplyGraphBuilderStr = `
|
||||||
|
aws_instance.create
|
||||||
|
provider.aws
|
||||||
|
aws_instance.other
|
||||||
|
aws_instance.create
|
||||||
|
provider.aws
|
||||||
|
meta.count-boundary (count boundary fixup)
|
||||||
|
aws_instance.create
|
||||||
|
aws_instance.other
|
||||||
|
module.child.aws_instance.create
|
||||||
|
module.child.aws_instance.other
|
||||||
|
module.child.provider.aws
|
||||||
|
provider.aws
|
||||||
|
provisioner.exec
|
||||||
|
module.child.aws_instance.create
|
||||||
|
module.child.provider.aws
|
||||||
|
provisioner.exec
|
||||||
|
module.child.aws_instance.other
|
||||||
|
module.child.aws_instance.create
|
||||||
|
module.child.provider.aws
|
||||||
|
module.child.provider.aws
|
||||||
|
provider.aws
|
||||||
|
provider.aws
|
||||||
|
provisioner.exec
|
||||||
|
`
|
|
@ -46,7 +46,7 @@ func (b *ImportGraphBuilder) Steps() []GraphTransformer {
|
||||||
// Provider-related transformations
|
// Provider-related transformations
|
||||||
&MissingProviderTransformer{Providers: b.Providers},
|
&MissingProviderTransformer{Providers: b.Providers},
|
||||||
&ProviderTransformer{},
|
&ProviderTransformer{},
|
||||||
&DisableProviderTransformer{},
|
&DisableProviderTransformerOld{},
|
||||||
&PruneProviderTransformer{},
|
&PruneProviderTransformer{},
|
||||||
|
|
||||||
// Single root
|
// Single root
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
// NodeCountBoundary fixes any "count boundarie" in the state: resources
|
||||||
|
// that are named "foo.0" when they should be named "foo"
|
||||||
|
type NodeCountBoundary struct{}
|
||||||
|
|
||||||
|
func (n *NodeCountBoundary) Name() string {
|
||||||
|
return "meta.count-boundary (count boundary fixup)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable
|
||||||
|
func (n *NodeCountBoundary) EvalTree() EvalNode {
|
||||||
|
return &EvalCountFixZeroOneBoundaryGlobal{}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeApplyableModuleVariable represents a module variable input during
|
||||||
|
// the apply step.
|
||||||
|
type NodeApplyableModuleVariable struct {
|
||||||
|
PathValue []string
|
||||||
|
Config *config.Variable // Config is the var in the config
|
||||||
|
Value *config.RawConfig // Value is the value that is set
|
||||||
|
|
||||||
|
Module *module.Tree // Antiquated, want to remove
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeApplyableModuleVariable) Name() string {
|
||||||
|
result := fmt.Sprintf("var.%s", n.Config.Name)
|
||||||
|
if len(n.PathValue) > 1 {
|
||||||
|
result = fmt.Sprintf("%s.%s", modulePrefixStr(n.PathValue), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeSubPath
|
||||||
|
func (n *NodeApplyableModuleVariable) Path() []string {
|
||||||
|
// We execute in the parent scope (above our own module) so that
|
||||||
|
// we can access the proper interpolations.
|
||||||
|
if len(n.PathValue) > 2 {
|
||||||
|
return n.PathValue[:len(n.PathValue)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootModulePath
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeReferenceGlobal
|
||||||
|
func (n *NodeApplyableModuleVariable) ReferenceGlobal() bool {
|
||||||
|
// We have to create fully qualified references because we cross
|
||||||
|
// boundaries here: our ReferenceableName is in one path and our
|
||||||
|
// References are from another path.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeReferenceable
|
||||||
|
func (n *NodeApplyableModuleVariable) ReferenceableName() []string {
|
||||||
|
return []string{n.Name()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeReferencer
|
||||||
|
func (n *NodeApplyableModuleVariable) References() []string {
|
||||||
|
// If we have no value set, we depend on nothing
|
||||||
|
if n.Value == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't depend on anything if we're in the root
|
||||||
|
if len(n.PathValue) < 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we depend on anything that is in our value, but
|
||||||
|
// specifically in the namespace of the parent path.
|
||||||
|
// Create the prefix based on the path
|
||||||
|
var prefix string
|
||||||
|
if p := n.Path(); len(p) > 0 {
|
||||||
|
prefix = modulePrefixStr(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ReferencesFromConfig(n.Value)
|
||||||
|
return modulePrefixList(result, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable
|
||||||
|
func (n *NodeApplyableModuleVariable) EvalTree() EvalNode {
|
||||||
|
// If we have no value, do nothing
|
||||||
|
if n.Value == nil {
|
||||||
|
return &EvalNoop{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, interpolate the value of this variable and set it
|
||||||
|
// within the variables mapping.
|
||||||
|
var config *ResourceConfig
|
||||||
|
variables := make(map[string]interface{})
|
||||||
|
return &EvalSequence{
|
||||||
|
Nodes: []EvalNode{
|
||||||
|
&EvalInterpolate{
|
||||||
|
Config: n.Value,
|
||||||
|
Output: &config,
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalVariableBlock{
|
||||||
|
Config: &config,
|
||||||
|
VariableValues: variables,
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalCoerceMapVariable{
|
||||||
|
Variables: variables,
|
||||||
|
ModulePath: n.PathValue,
|
||||||
|
ModuleTree: n.Module,
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalTypeCheckVariable{
|
||||||
|
Variables: variables,
|
||||||
|
ModulePath: n.PathValue,
|
||||||
|
ModuleTree: n.Module,
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalSetVariables{
|
||||||
|
Module: &n.PathValue[len(n.PathValue)-1],
|
||||||
|
Variables: variables,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNodeApplyableModuleVariablePath(t *testing.T) {
|
||||||
|
n := &NodeApplyableModuleVariable{
|
||||||
|
PathValue: []string{"root", "child"},
|
||||||
|
Config: &config.Variable{Name: "foo"},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{"root"}
|
||||||
|
actual := n.Path()
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("%#v != %#v", actual, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNodeApplyableModuleVariableReferenceableName(t *testing.T) {
|
||||||
|
n := &NodeApplyableModuleVariable{
|
||||||
|
PathValue: []string{"root", "child"},
|
||||||
|
Config: &config.Variable{Name: "foo"},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{"module.child.var.foo"}
|
||||||
|
actual := n.ReferenceableName()
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("%#v != %#v", actual, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNodeApplyableModuleVariableReference(t *testing.T) {
|
||||||
|
n := &NodeApplyableModuleVariable{
|
||||||
|
PathValue: []string{"root", "child"},
|
||||||
|
Config: &config.Variable{Name: "foo"},
|
||||||
|
Value: config.TestRawConfig(t, map[string]interface{}{
|
||||||
|
"foo": `${var.foo}`,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{"var.foo"}
|
||||||
|
actual := n.References()
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("%#v != %#v", actual, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNodeApplyableModuleVariableReference_grandchild(t *testing.T) {
|
||||||
|
n := &NodeApplyableModuleVariable{
|
||||||
|
PathValue: []string{"root", "child", "grandchild"},
|
||||||
|
Config: &config.Variable{Name: "foo"},
|
||||||
|
Value: config.TestRawConfig(t, map[string]interface{}{
|
||||||
|
"foo": `${var.foo}`,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{"module.child.var.foo"}
|
||||||
|
actual := n.References()
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("%#v != %#v", actual, expected)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeApplyableOutput represents an output that is "applyable":
|
||||||
|
// it is ready to be applied.
|
||||||
|
type NodeApplyableOutput struct {
|
||||||
|
PathValue []string
|
||||||
|
Config *config.Output // Config is the output in the config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeApplyableOutput) Name() string {
|
||||||
|
result := fmt.Sprintf("output.%s", n.Config.Name)
|
||||||
|
if len(n.PathValue) > 1 {
|
||||||
|
result = fmt.Sprintf("%s.%s", modulePrefixStr(n.PathValue), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeSubPath
|
||||||
|
func (n *NodeApplyableOutput) Path() []string {
|
||||||
|
return n.PathValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeReferenceable
|
||||||
|
func (n *NodeApplyableOutput) ReferenceableName() []string {
|
||||||
|
name := fmt.Sprintf("output.%s", n.Config.Name)
|
||||||
|
return []string{name}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeReferencer
|
||||||
|
func (n *NodeApplyableOutput) References() []string {
|
||||||
|
var result []string
|
||||||
|
result = append(result, ReferencesFromConfig(n.Config.RawConfig)...)
|
||||||
|
for _, v := range result {
|
||||||
|
result = append(result, v+".destroy")
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable
|
||||||
|
func (n *NodeApplyableOutput) EvalTree() EvalNode {
|
||||||
|
return &EvalOpFilter{
|
||||||
|
Ops: []walkOperation{walkRefresh, walkPlan, walkApply,
|
||||||
|
walkDestroy, walkInput, walkValidate},
|
||||||
|
Node: &EvalSequence{
|
||||||
|
Nodes: []EvalNode{
|
||||||
|
&EvalWriteOutput{
|
||||||
|
Name: n.Config.Name,
|
||||||
|
Sensitive: n.Config.Sensitive,
|
||||||
|
Value: n.Config.RawConfig,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeOutputOrphan represents an output that is an orphan.
|
||||||
|
type NodeOutputOrphan struct {
|
||||||
|
OutputName string
|
||||||
|
PathValue []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeOutputOrphan) Name() string {
|
||||||
|
result := fmt.Sprintf("output.%s (orphan)", n.OutputName)
|
||||||
|
if len(n.PathValue) > 1 {
|
||||||
|
result = fmt.Sprintf("%s.%s", modulePrefixStr(n.PathValue), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeSubPath
|
||||||
|
func (n *NodeOutputOrphan) Path() []string {
|
||||||
|
return n.PathValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable
|
||||||
|
func (n *NodeOutputOrphan) EvalTree() EvalNode {
|
||||||
|
return &EvalDeleteOutput{
|
||||||
|
Name: n.OutputName,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeApplyableProvider represents a provider during an apply.
|
||||||
|
//
|
||||||
|
// NOTE: There is a lot of logic here that will be shared with non-Apply.
|
||||||
|
// The plan is to abstract that eventually into an embedded abstract struct.
|
||||||
|
type NodeApplyableProvider struct {
|
||||||
|
NameValue string
|
||||||
|
PathValue []string
|
||||||
|
Config *config.ProviderConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeApplyableProvider) Name() string {
|
||||||
|
result := fmt.Sprintf("provider.%s", n.NameValue)
|
||||||
|
if len(n.PathValue) > 1 {
|
||||||
|
result = fmt.Sprintf("%s.%s", modulePrefixStr(n.PathValue), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeSubPath
|
||||||
|
func (n *NodeApplyableProvider) Path() []string {
|
||||||
|
return n.PathValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeReferencer
|
||||||
|
func (n *NodeApplyableProvider) References() []string {
|
||||||
|
if n.Config == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReferencesFromConfig(n.Config.RawConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeProvider
|
||||||
|
func (n *NodeApplyableProvider) ProviderName() string {
|
||||||
|
return n.NameValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeProvider
|
||||||
|
func (n *NodeApplyableProvider) ProviderConfig() *config.RawConfig {
|
||||||
|
if n.Config == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return n.Config.RawConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeAttachProvider
|
||||||
|
func (n *NodeApplyableProvider) AttachProvider(c *config.ProviderConfig) {
|
||||||
|
n.Config = c
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable
|
||||||
|
func (n *NodeApplyableProvider) EvalTree() EvalNode {
|
||||||
|
return ProviderEvalTree(n.NameValue, n.ProviderConfig())
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeAbstractProvider represents a provider that has no associated operations.
|
||||||
|
// It registers all the common interfaces across operations for providers.
|
||||||
|
type NodeAbstractProvider struct {
|
||||||
|
NameValue string
|
||||||
|
PathValue []string
|
||||||
|
|
||||||
|
// The fields below will be automatically set using the Attach
|
||||||
|
// interfaces if you're running those transforms, but also be explicitly
|
||||||
|
// set if you already have that information.
|
||||||
|
|
||||||
|
Config *config.ProviderConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeAbstractProvider) Name() string {
|
||||||
|
result := fmt.Sprintf("provider.%s", n.NameValue)
|
||||||
|
if len(n.PathValue) > 1 {
|
||||||
|
result = fmt.Sprintf("%s.%s", modulePrefixStr(n.PathValue), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeSubPath
|
||||||
|
func (n *NodeAbstractProvider) Path() []string {
|
||||||
|
return n.PathValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeReferencer
|
||||||
|
func (n *NodeAbstractProvider) References() []string {
|
||||||
|
if n.Config == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReferencesFromConfig(n.Config.RawConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeProvider
|
||||||
|
func (n *NodeAbstractProvider) ProviderName() string {
|
||||||
|
return n.NameValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeProvider
|
||||||
|
func (n *NodeAbstractProvider) ProviderConfig() *config.RawConfig {
|
||||||
|
if n.Config == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return n.Config.RawConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeAttachProvider
|
||||||
|
func (n *NodeAbstractProvider) AttachProvider(c *config.ProviderConfig) {
|
||||||
|
n.Config = c
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeDisabledProvider represents a provider that is disabled. A disabled
|
||||||
|
// provider does nothing. It exists to properly set inheritance information
|
||||||
|
// for child providers.
|
||||||
|
type NodeDisabledProvider struct {
|
||||||
|
*NodeAbstractProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeDisabledProvider) Name() string {
|
||||||
|
return fmt.Sprintf("%s (disabled)", n.NodeAbstractProvider.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable
|
||||||
|
func (n *NodeDisabledProvider) EvalTree() EvalNode {
|
||||||
|
var resourceConfig *ResourceConfig
|
||||||
|
return &EvalSequence{
|
||||||
|
Nodes: []EvalNode{
|
||||||
|
&EvalInterpolate{
|
||||||
|
Config: n.ProviderConfig(),
|
||||||
|
Output: &resourceConfig,
|
||||||
|
},
|
||||||
|
&EvalBuildProviderConfig{
|
||||||
|
Provider: n.ProviderName(),
|
||||||
|
Config: &resourceConfig,
|
||||||
|
Output: &resourceConfig,
|
||||||
|
},
|
||||||
|
&EvalSetProviderConfig{
|
||||||
|
Provider: n.ProviderName(),
|
||||||
|
Config: &resourceConfig,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConcreteResourceNodeFunc is a callback type used to convert an
|
||||||
|
// abstract resource to a concrete one of some type.
|
||||||
|
type ConcreteResourceNodeFunc func(*NodeAbstractResource) dag.Vertex
|
||||||
|
|
||||||
|
// GraphNodeResource is implemented by any nodes that represent a resource.
|
||||||
|
// The type of operation cannot be assumed, only that this node represents
|
||||||
|
// the given resource.
|
||||||
|
type GraphNodeResource interface {
|
||||||
|
ResourceAddr() *ResourceAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeAbstractResource represents a resource that has no associated
|
||||||
|
// operations. It registers all the interfaces for a resource that common
|
||||||
|
// across multiple operation types.
|
||||||
|
type NodeAbstractResource struct {
|
||||||
|
Addr *ResourceAddress // Addr is the address for this resource
|
||||||
|
|
||||||
|
// The fields below will be automatically set using the Attach
|
||||||
|
// interfaces if you're running those transforms, but also be explicitly
|
||||||
|
// set if you already have that information.
|
||||||
|
|
||||||
|
Config *config.Resource // Config is the resource in the config
|
||||||
|
ResourceState *ResourceState // ResourceState is the ResourceState for this
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeAbstractResource) Name() string {
|
||||||
|
return n.Addr.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeSubPath
|
||||||
|
func (n *NodeAbstractResource) Path() []string {
|
||||||
|
return n.Addr.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeReferenceable
|
||||||
|
func (n *NodeAbstractResource) ReferenceableName() []string {
|
||||||
|
if n.Config == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{n.Config.Id()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeReferencer
|
||||||
|
func (n *NodeAbstractResource) References() []string {
|
||||||
|
// If we have a config, that is our source of truth
|
||||||
|
if c := n.Config; c != nil {
|
||||||
|
// Grab all the references
|
||||||
|
var result []string
|
||||||
|
result = append(result, c.DependsOn...)
|
||||||
|
result = append(result, ReferencesFromConfig(c.RawCount)...)
|
||||||
|
result = append(result, ReferencesFromConfig(c.RawConfig)...)
|
||||||
|
for _, p := range c.Provisioners {
|
||||||
|
result = append(result, ReferencesFromConfig(p.ConnInfo)...)
|
||||||
|
result = append(result, ReferencesFromConfig(p.RawConfig)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have state, that is our next source
|
||||||
|
if s := n.ResourceState; s != nil {
|
||||||
|
return s.Dependencies
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeProviderConsumer
|
||||||
|
func (n *NodeAbstractResource) ProvidedBy() []string {
|
||||||
|
// If we have a config we prefer that above all else
|
||||||
|
if n.Config != nil {
|
||||||
|
return []string{resourceProvider(n.Config.Type, n.Config.Provider)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have state, then we will use the provider from there
|
||||||
|
if n.ResourceState != nil && n.ResourceState.Provider != "" {
|
||||||
|
return []string{n.ResourceState.Provider}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use our type
|
||||||
|
return []string{resourceProvider(n.Addr.Type, "")}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeProvisionerConsumer
|
||||||
|
func (n *NodeAbstractResource) ProvisionedBy() []string {
|
||||||
|
// If we have no configuration, then we have no provisioners
|
||||||
|
if n.Config == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the list of provisioners we need based on the configuration.
|
||||||
|
// It is okay to have duplicates here.
|
||||||
|
result := make([]string, len(n.Config.Provisioners))
|
||||||
|
for i, p := range n.Config.Provisioners {
|
||||||
|
result[i] = p.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeResource, GraphNodeAttachResourceState
|
||||||
|
func (n *NodeAbstractResource) ResourceAddr() *ResourceAddress {
|
||||||
|
return n.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeAttachResourceState
|
||||||
|
func (n *NodeAbstractResource) AttachResourceState(s *ResourceState) {
|
||||||
|
n.ResourceState = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeAttachResourceConfig
|
||||||
|
func (n *NodeAbstractResource) AttachResourceConfig(c *config.Resource) {
|
||||||
|
n.Config = c
|
||||||
|
}
|
|
@ -0,0 +1,221 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeApplyableResource represents a resource that is "applyable":
|
||||||
|
// it is ready to be applied and is represented by a diff.
|
||||||
|
type NodeApplyableResource struct {
|
||||||
|
*NodeAbstractResource
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeCreator
|
||||||
|
func (n *NodeApplyableResource) CreateAddr() *ResourceAddress {
|
||||||
|
return n.NodeAbstractResource.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable
|
||||||
|
func (n *NodeApplyableResource) 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the resource for eval
|
||||||
|
resource := &Resource{
|
||||||
|
Name: addr.Name,
|
||||||
|
Type: addr.Type,
|
||||||
|
CountIndex: addr.Index,
|
||||||
|
}
|
||||||
|
if resource.CountIndex < 0 {
|
||||||
|
resource.CountIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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}
|
||||||
|
stateDeps = oldN.StateDependencies()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 diff, diffApply *InstanceDiff
|
||||||
|
var state *InstanceState
|
||||||
|
var resourceConfig *ResourceConfig
|
||||||
|
var err error
|
||||||
|
var createNew bool
|
||||||
|
var createBeforeDestroyEnabled bool
|
||||||
|
|
||||||
|
return &EvalSequence{
|
||||||
|
Nodes: []EvalNode{
|
||||||
|
// Build the instance info
|
||||||
|
&EvalInstanceInfo{
|
||||||
|
Info: info,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get the saved diff for apply
|
||||||
|
&EvalReadDiff{
|
||||||
|
Name: stateId,
|
||||||
|
Diff: &diffApply,
|
||||||
|
},
|
||||||
|
|
||||||
|
// We don't want to do any destroys
|
||||||
|
&EvalIf{
|
||||||
|
If: func(ctx EvalContext) (bool, error) {
|
||||||
|
if diffApply == nil {
|
||||||
|
return true, EvalEarlyExitError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 {
|
||||||
|
return true, EvalEarlyExitError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
diffApply.SetDestroy(false)
|
||||||
|
return true, nil
|
||||||
|
},
|
||||||
|
Then: EvalNoop{},
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalIf{
|
||||||
|
If: func(ctx EvalContext) (bool, error) {
|
||||||
|
destroy := false
|
||||||
|
if diffApply != nil {
|
||||||
|
destroy = diffApply.GetDestroy() || diffApply.RequiresNew()
|
||||||
|
}
|
||||||
|
|
||||||
|
createBeforeDestroyEnabled =
|
||||||
|
n.Config.Lifecycle.CreateBeforeDestroy &&
|
||||||
|
destroy
|
||||||
|
|
||||||
|
return createBeforeDestroyEnabled, nil
|
||||||
|
},
|
||||||
|
Then: &EvalDeposeState{
|
||||||
|
Name: stateId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalInterpolate{
|
||||||
|
Config: n.Config.RawConfig.Copy(),
|
||||||
|
Resource: resource,
|
||||||
|
Output: &resourceConfig,
|
||||||
|
},
|
||||||
|
&EvalGetProvider{
|
||||||
|
Name: n.ProvidedBy()[0],
|
||||||
|
Output: &provider,
|
||||||
|
},
|
||||||
|
&EvalReadState{
|
||||||
|
Name: stateId,
|
||||||
|
Output: &state,
|
||||||
|
},
|
||||||
|
// Re-run validation to catch any errors we missed, e.g. type
|
||||||
|
// mismatches on computed values.
|
||||||
|
&EvalValidateResource{
|
||||||
|
Provider: &provider,
|
||||||
|
Config: &resourceConfig,
|
||||||
|
ResourceName: n.Config.Name,
|
||||||
|
ResourceType: n.Config.Type,
|
||||||
|
ResourceMode: n.Config.Mode,
|
||||||
|
IgnoreWarnings: true,
|
||||||
|
},
|
||||||
|
&EvalDiff{
|
||||||
|
Info: info,
|
||||||
|
Config: &resourceConfig,
|
||||||
|
Resource: n.Config,
|
||||||
|
Provider: &provider,
|
||||||
|
Diff: &diffApply,
|
||||||
|
State: &state,
|
||||||
|
OutputDiff: &diffApply,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get the saved diff
|
||||||
|
&EvalReadDiff{
|
||||||
|
Name: stateId,
|
||||||
|
Diff: &diff,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Compare the diffs
|
||||||
|
&EvalCompareDiff{
|
||||||
|
Info: info,
|
||||||
|
One: &diff,
|
||||||
|
Two: &diffApply,
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalGetProvider{
|
||||||
|
Name: n.ProvidedBy()[0],
|
||||||
|
Output: &provider,
|
||||||
|
},
|
||||||
|
&EvalReadState{
|
||||||
|
Name: stateId,
|
||||||
|
Output: &state,
|
||||||
|
},
|
||||||
|
&EvalApply{
|
||||||
|
Info: info,
|
||||||
|
State: &state,
|
||||||
|
Diff: &diffApply,
|
||||||
|
Provider: &provider,
|
||||||
|
Output: &state,
|
||||||
|
Error: &err,
|
||||||
|
CreateNew: &createNew,
|
||||||
|
},
|
||||||
|
&EvalWriteState{
|
||||||
|
Name: stateId,
|
||||||
|
ResourceType: n.Config.Type,
|
||||||
|
Provider: n.Config.Provider,
|
||||||
|
Dependencies: stateDeps,
|
||||||
|
State: &state,
|
||||||
|
},
|
||||||
|
&EvalApplyProvisioners{
|
||||||
|
Info: info,
|
||||||
|
State: &state,
|
||||||
|
Resource: n.Config,
|
||||||
|
InterpResource: resource,
|
||||||
|
CreateNew: &createNew,
|
||||||
|
Error: &err,
|
||||||
|
},
|
||||||
|
&EvalIf{
|
||||||
|
If: func(ctx EvalContext) (bool, error) {
|
||||||
|
return createBeforeDestroyEnabled && err != nil, nil
|
||||||
|
},
|
||||||
|
Then: &EvalUndeposeState{
|
||||||
|
Name: stateId,
|
||||||
|
State: &state,
|
||||||
|
},
|
||||||
|
Else: &EvalWriteState{
|
||||||
|
Name: stateId,
|
||||||
|
ResourceType: n.Config.Type,
|
||||||
|
Provider: n.Config.Provider,
|
||||||
|
Dependencies: stateDeps,
|
||||||
|
State: &state,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// We clear the diff out here so that future nodes
|
||||||
|
// don't see a diff that is already complete. There
|
||||||
|
// is no longer a diff!
|
||||||
|
&EvalWriteDiff{
|
||||||
|
Name: stateId,
|
||||||
|
Diff: nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalApplyPost{
|
||||||
|
Info: info,
|
||||||
|
State: &state,
|
||||||
|
Error: &err,
|
||||||
|
},
|
||||||
|
&EvalUpdateStateHook{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,185 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeDestroyResource represents a resource that is to be destroyed.
|
||||||
|
type NodeDestroyResource struct {
|
||||||
|
NodeAbstractResource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeDestroyResource) Name() string {
|
||||||
|
return n.NodeAbstractResource.Name() + " (destroy)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeDestroyer
|
||||||
|
func (n *NodeDestroyResource) DestroyAddr() *ResourceAddress {
|
||||||
|
return n.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeDestroyerCBD
|
||||||
|
func (n *NodeDestroyResource) CreateBeforeDestroy() bool {
|
||||||
|
// If we have no config, we just assume no
|
||||||
|
if n.Config == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return n.Config.Lifecycle.CreateBeforeDestroy
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeReferenceable, overriding NodeAbstractResource
|
||||||
|
func (n *NodeDestroyResource) ReferenceableName() []string {
|
||||||
|
result := n.NodeAbstractResource.ReferenceableName()
|
||||||
|
for i, v := range result {
|
||||||
|
result[i] = v + ".destroy"
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeReferencer, overriding NodeAbstractResource
|
||||||
|
func (n *NodeDestroyResource) References() []string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeDynamicExpandable
|
||||||
|
func (n *NodeDestroyResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
||||||
|
// If we have no config we do nothing
|
||||||
|
if n.Config == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
state, lock := ctx.State()
|
||||||
|
lock.RLock()
|
||||||
|
defer lock.RUnlock()
|
||||||
|
|
||||||
|
// Start creating the steps
|
||||||
|
steps := make([]GraphTransformer, 0, 5)
|
||||||
|
|
||||||
|
// We want deposed resources in the state to be destroyed
|
||||||
|
steps = append(steps, &DeposedTransformer{
|
||||||
|
State: state,
|
||||||
|
View: n.Config.Id(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Always end with the root being added
|
||||||
|
steps = append(steps, &RootTransformer{})
|
||||||
|
|
||||||
|
// Build the graph
|
||||||
|
b := &BasicGraphBuilder{Steps: steps}
|
||||||
|
return b.Build(ctx.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable
|
||||||
|
func (n *NodeDestroyResource) EvalTree() EvalNode {
|
||||||
|
// stateId is the ID to put into the state
|
||||||
|
stateId := n.Addr.stateId()
|
||||||
|
if n.Addr.Index > -1 {
|
||||||
|
stateId = fmt.Sprintf("%s.%d", stateId, n.Addr.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the instance info. More of this will be populated during eval
|
||||||
|
info := &InstanceInfo{
|
||||||
|
Id: stateId,
|
||||||
|
Type: n.Addr.Type,
|
||||||
|
uniqueExtra: "destroy",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get our state
|
||||||
|
rs := n.ResourceState
|
||||||
|
if rs == nil {
|
||||||
|
rs = &ResourceState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var diffApply *InstanceDiff
|
||||||
|
var provider ResourceProvider
|
||||||
|
var state *InstanceState
|
||||||
|
var err error
|
||||||
|
return &EvalOpFilter{
|
||||||
|
Ops: []walkOperation{walkApply, walkDestroy},
|
||||||
|
Node: &EvalSequence{
|
||||||
|
Nodes: []EvalNode{
|
||||||
|
// Get the saved diff for apply
|
||||||
|
&EvalReadDiff{
|
||||||
|
Name: stateId,
|
||||||
|
Diff: &diffApply,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Filter the diff so we only get the destroy
|
||||||
|
&EvalFilterDiff{
|
||||||
|
Diff: &diffApply,
|
||||||
|
Output: &diffApply,
|
||||||
|
Destroy: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// If we're not destroying, then compare diffs
|
||||||
|
&EvalIf{
|
||||||
|
If: func(ctx EvalContext) (bool, error) {
|
||||||
|
if diffApply != nil && diffApply.GetDestroy() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, EvalEarlyExitError{}
|
||||||
|
},
|
||||||
|
Then: EvalNoop{},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Load the instance info so we have the module path set
|
||||||
|
&EvalInstanceInfo{Info: info},
|
||||||
|
|
||||||
|
&EvalGetProvider{
|
||||||
|
Name: n.ProvidedBy()[0],
|
||||||
|
Output: &provider,
|
||||||
|
},
|
||||||
|
&EvalReadState{
|
||||||
|
Name: stateId,
|
||||||
|
Output: &state,
|
||||||
|
},
|
||||||
|
&EvalRequireState{
|
||||||
|
State: &state,
|
||||||
|
},
|
||||||
|
// Make sure we handle data sources properly.
|
||||||
|
&EvalIf{
|
||||||
|
If: func(ctx EvalContext) (bool, error) {
|
||||||
|
/* TODO: data source
|
||||||
|
if n.Resource.Mode == config.DataResourceMode {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
},
|
||||||
|
|
||||||
|
Then: &EvalReadDataApply{
|
||||||
|
Info: info,
|
||||||
|
Diff: &diffApply,
|
||||||
|
Provider: &provider,
|
||||||
|
Output: &state,
|
||||||
|
},
|
||||||
|
Else: &EvalApply{
|
||||||
|
Info: info,
|
||||||
|
State: &state,
|
||||||
|
Diff: &diffApply,
|
||||||
|
Provider: &provider,
|
||||||
|
Output: &state,
|
||||||
|
Error: &err,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&EvalWriteState{
|
||||||
|
Name: stateId,
|
||||||
|
ResourceType: n.Addr.Type,
|
||||||
|
Provider: rs.Provider,
|
||||||
|
Dependencies: rs.Dependencies,
|
||||||
|
State: &state,
|
||||||
|
},
|
||||||
|
&EvalApplyPost{
|
||||||
|
Info: info,
|
||||||
|
State: &state,
|
||||||
|
Error: &err,
|
||||||
|
},
|
||||||
|
&EvalUpdateStateHook{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeRootVariable represents a root variable input.
|
||||||
|
type NodeRootVariable struct {
|
||||||
|
Config *config.Variable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeRootVariable) Name() string {
|
||||||
|
result := fmt.Sprintf("var.%s", n.Config.Name)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeReferenceable
|
||||||
|
func (n *NodeRootVariable) ReferenceableName() []string {
|
||||||
|
return []string{n.Name()}
|
||||||
|
}
|
|
@ -72,6 +72,10 @@ type InstanceInfo struct {
|
||||||
|
|
||||||
// HumanId is a unique Id that is human-friendly and useful for UI elements.
|
// HumanId is a unique Id that is human-friendly and useful for UI elements.
|
||||||
func (i *InstanceInfo) HumanId() string {
|
func (i *InstanceInfo) HumanId() string {
|
||||||
|
if i == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
|
||||||
if len(i.ModulePath) <= 1 {
|
if len(i.ModulePath) <= 1 {
|
||||||
return i.Id
|
return i.Id
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,78 @@ func (r *ResourceAddress) String() string {
|
||||||
return strings.Join(result, ".")
|
return strings.Join(result, ".")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stateId returns the ID that this resource should be entered with
|
||||||
|
// in the state. This is also used for diffs. In the future, we'd like to
|
||||||
|
// move away from this string field so I don't export this.
|
||||||
|
func (r *ResourceAddress) stateId() string {
|
||||||
|
result := fmt.Sprintf("%s.%s", r.Type, r.Name)
|
||||||
|
switch r.Mode {
|
||||||
|
case config.ManagedResourceMode:
|
||||||
|
// Done
|
||||||
|
case config.DataResourceMode:
|
||||||
|
result = fmt.Sprintf("data.%s", result)
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unknown resource mode: %s", r.Mode))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseResourceAddressConfig creates a resource address from a config.Resource
|
||||||
|
func parseResourceAddressConfig(r *config.Resource) (*ResourceAddress, error) {
|
||||||
|
return &ResourceAddress{
|
||||||
|
Type: r.Type,
|
||||||
|
Name: r.Name,
|
||||||
|
Index: -1,
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Mode: r.Mode,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseResourceAddressInternal parses the somewhat bespoke resource
|
||||||
|
// identifier used in states and diffs, such as "instance.name.0".
|
||||||
|
func parseResourceAddressInternal(s string) (*ResourceAddress, error) {
|
||||||
|
// Split based on ".". Every resource address should have at least two
|
||||||
|
// elements (type and name).
|
||||||
|
parts := strings.Split(s, ".")
|
||||||
|
if len(parts) < 2 || len(parts) > 4 {
|
||||||
|
return nil, fmt.Errorf("Invalid internal resource address format: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data resource if we have at least 3 parts and the first one is data
|
||||||
|
mode := config.ManagedResourceMode
|
||||||
|
if len(parts) > 2 && parts[0] == "data" {
|
||||||
|
mode = config.DataResourceMode
|
||||||
|
parts = parts[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're not a data resource and we have more than 3, then it is an error
|
||||||
|
if len(parts) > 3 && mode != config.DataResourceMode {
|
||||||
|
return nil, fmt.Errorf("Invalid internal resource address format: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the parts of the resource address that are guaranteed to exist
|
||||||
|
addr := &ResourceAddress{
|
||||||
|
Type: parts[0],
|
||||||
|
Name: parts[1],
|
||||||
|
Index: -1,
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Mode: mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have more parts, then we have an index. Parse that.
|
||||||
|
if len(parts) > 2 {
|
||||||
|
idx, err := strconv.ParseInt(parts[2], 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error parsing resource address %q: %s", s, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addr.Index = int(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ParseResourceAddress(s string) (*ResourceAddress, error) {
|
func ParseResourceAddress(s string) (*ResourceAddress, error) {
|
||||||
matches, err := tokenizeResourceAddress(s)
|
matches, err := tokenizeResourceAddress(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -7,6 +7,99 @@ import (
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestParseResourceAddressInternal(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
Input string
|
||||||
|
Expected *ResourceAddress
|
||||||
|
Output string
|
||||||
|
}{
|
||||||
|
"basic resource": {
|
||||||
|
"aws_instance.foo",
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
"aws_instance.foo",
|
||||||
|
},
|
||||||
|
|
||||||
|
"basic resource with count": {
|
||||||
|
"aws_instance.foo.1",
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Index: 1,
|
||||||
|
},
|
||||||
|
"aws_instance.foo[1]",
|
||||||
|
},
|
||||||
|
|
||||||
|
"data resource": {
|
||||||
|
"data.aws_ami.foo",
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.DataResourceMode,
|
||||||
|
Type: "aws_ami",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
"data.aws_ami.foo",
|
||||||
|
},
|
||||||
|
|
||||||
|
"data resource with count": {
|
||||||
|
"data.aws_ami.foo.1",
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.DataResourceMode,
|
||||||
|
Type: "aws_ami",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Index: 1,
|
||||||
|
},
|
||||||
|
"data.aws_ami.foo[1]",
|
||||||
|
},
|
||||||
|
|
||||||
|
"non-data resource with 4 elements": {
|
||||||
|
"aws_instance.foo.bar.1",
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for tn, tc := range cases {
|
||||||
|
t.Run(tc.Input, func(t *testing.T) {
|
||||||
|
out, err := parseResourceAddressInternal(tc.Input)
|
||||||
|
if (err != nil) != (tc.Expected == nil) {
|
||||||
|
t.Fatalf("%s: unexpected err: %#v", tn, err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(out, tc.Expected) {
|
||||||
|
t.Fatalf("bad: %q\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Expected, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare outputs if those exist
|
||||||
|
expected := tc.Input
|
||||||
|
if tc.Output != "" {
|
||||||
|
expected = tc.Output
|
||||||
|
}
|
||||||
|
if out.String() != expected {
|
||||||
|
t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, expected, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare equality because the internal parse is used
|
||||||
|
// to compare equality to equal inputs.
|
||||||
|
if !out.Equals(tc.Expected) {
|
||||||
|
t.Fatalf("expected equality:\n\n%#v\n\n%#v", out, tc.Expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseResourceAddress(t *testing.T) {
|
func TestParseResourceAddress(t *testing.T) {
|
||||||
cases := map[string]struct {
|
cases := map[string]struct {
|
||||||
Input string
|
Input string
|
||||||
|
@ -461,3 +554,52 @@ func TestResourceAddressEquals(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResourceAddressStateId(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
Input *ResourceAddress
|
||||||
|
Expected string
|
||||||
|
}{
|
||||||
|
"basic resource": {
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
"aws_instance.foo",
|
||||||
|
},
|
||||||
|
|
||||||
|
"basic resource ignores count": {
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Index: 2,
|
||||||
|
},
|
||||||
|
"aws_instance.foo",
|
||||||
|
},
|
||||||
|
|
||||||
|
"data resource": {
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.DataResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
"data.aws_instance.foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for tn, tc := range cases {
|
||||||
|
t.Run(tn, func(t *testing.T) {
|
||||||
|
actual := tc.Input.stateId()
|
||||||
|
if actual != tc.Expected {
|
||||||
|
t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, tc.Expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -208,6 +208,10 @@ func (f *shadowComponentFactoryShared) ResourceProvider(
|
||||||
real, shadow := newShadowResourceProvider(p)
|
real, shadow := newShadowResourceProvider(p)
|
||||||
entry.Real = real
|
entry.Real = real
|
||||||
entry.Shadow = shadow
|
entry.Shadow = shadow
|
||||||
|
|
||||||
|
if f.closed {
|
||||||
|
shadow.CloseShadow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the value
|
// Store the value
|
||||||
|
@ -246,6 +250,10 @@ func (f *shadowComponentFactoryShared) ResourceProvisioner(
|
||||||
real, shadow := newShadowResourceProvisioner(p)
|
real, shadow := newShadowResourceProvisioner(p)
|
||||||
entry.Real = real
|
entry.Real = real
|
||||||
entry.Shadow = shadow
|
entry.Shadow = shadow
|
||||||
|
|
||||||
|
if f.closed {
|
||||||
|
shadow.CloseShadow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the value
|
// Store the value
|
||||||
|
|
|
@ -101,6 +101,10 @@ func newShadowContext(c *Context) (*Context, *Context, Shadow) {
|
||||||
func shadowContextVerify(real, shadow *Context) error {
|
func shadowContextVerify(real, shadow *Context) error {
|
||||||
var result error
|
var result error
|
||||||
|
|
||||||
|
// The states compared must be pruned so they're minimal/clean
|
||||||
|
real.state.prune()
|
||||||
|
shadow.state.prune()
|
||||||
|
|
||||||
// Compare the states
|
// Compare the states
|
||||||
if !real.state.Equal(shadow.state) {
|
if !real.state.Equal(shadow.state) {
|
||||||
result = multierror.Append(result, fmt.Errorf(
|
result = multierror.Append(result, fmt.Errorf(
|
||||||
|
|
|
@ -510,7 +510,7 @@ func (p *shadowResourceProviderShadow) Apply(
|
||||||
p.ErrorLock.Lock()
|
p.ErrorLock.Lock()
|
||||||
defer p.ErrorLock.Unlock()
|
defer p.ErrorLock.Unlock()
|
||||||
p.Error = multierror.Append(p.Error, fmt.Errorf(
|
p.Error = multierror.Append(p.Error, fmt.Errorf(
|
||||||
"Unknown 'apply' shadow value: %#v", raw))
|
"Unknown 'apply' shadow value for %q: %#v", key, raw))
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -518,16 +518,16 @@ func (p *shadowResourceProviderShadow) Apply(
|
||||||
if !state.Equal(result.State) {
|
if !state.Equal(result.State) {
|
||||||
p.ErrorLock.Lock()
|
p.ErrorLock.Lock()
|
||||||
p.Error = multierror.Append(p.Error, fmt.Errorf(
|
p.Error = multierror.Append(p.Error, fmt.Errorf(
|
||||||
"Apply: state had unequal states (real, then shadow):\n\n%#v\n\n%#v",
|
"Apply %q: state had unequal states (real, then shadow):\n\n%#v\n\n%#v",
|
||||||
result.State, state))
|
key, result.State, state))
|
||||||
p.ErrorLock.Unlock()
|
p.ErrorLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !diff.Equal(result.Diff) {
|
if !diff.Equal(result.Diff) {
|
||||||
p.ErrorLock.Lock()
|
p.ErrorLock.Lock()
|
||||||
p.Error = multierror.Append(p.Error, fmt.Errorf(
|
p.Error = multierror.Append(p.Error, fmt.Errorf(
|
||||||
"Apply: unequal diffs (real, then shadow):\n\n%#v\n\n%#v",
|
"Apply %q: unequal diffs (real, then shadow):\n\n%#v\n\n%#v",
|
||||||
result.Diff, diff))
|
key, result.Diff, diff))
|
||||||
p.ErrorLock.Unlock()
|
p.ErrorLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,17 @@ import (
|
||||||
const fixtureDir = "./test-fixtures"
|
const fixtureDir = "./test-fixtures"
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
// Experimental features
|
||||||
|
xNewApply := flag.Bool("Xnew-apply", false, "Experiment: new apply graph")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
// Setup experimental features
|
||||||
|
X_newApply = *xNewApply
|
||||||
|
if X_newApply {
|
||||||
|
println("Xnew-apply enabled")
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
logging.SetOutput()
|
logging.SetOutput()
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
provider "aws" {
|
||||||
|
value = "foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "foo" {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
provider "aws" {
|
||||||
|
foo = "bar"
|
||||||
|
}
|
||||||
|
|
||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
resource "aws_instance" "create" {
|
||||||
|
provisioner "exec" {}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "other" {
|
||||||
|
value = "${aws_instance.create.id}"
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "create" {}
|
||||||
|
|
||||||
|
resource "aws_instance" "other" {
|
||||||
|
foo = "${aws_instance.create.bar}"
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
resource "test" "A" {}
|
||||||
|
resource "test" "B" { value = "${test.A.value}" }
|
|
@ -0,0 +1,3 @@
|
||||||
|
resource "test" "A" {}
|
||||||
|
resource "test" "B" { value = "${test.A.value}" }
|
||||||
|
resource "test" "C" { value = "${test.B.value}" }
|
|
@ -0,0 +1 @@
|
||||||
|
resource "aws_instance" "foo" {}
|
|
@ -0,0 +1 @@
|
||||||
|
resource "aws_instance" "baz" {}
|
|
@ -0,0 +1,6 @@
|
||||||
|
resource "aws_instance" "foo" {}
|
||||||
|
resource "aws_instance" "bar" { value = "${aws_instance.foo.value}" }
|
||||||
|
|
||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
variable "value" {}
|
||||||
|
|
||||||
|
output "result" { value = "${var.value}" }
|
|
@ -0,0 +1,4 @@
|
||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
value = "foo"
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
variable "value" {}
|
||||||
|
|
||||||
|
output "result" { value = "${var.value}" }
|
|
@ -0,0 +1,6 @@
|
||||||
|
variable "value" {}
|
||||||
|
|
||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
value = "${var.value}"
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
value = "foo"
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GraphNodeAttachProvider is an interface that must be implemented by nodes
|
||||||
|
// that want provider configurations attached.
|
||||||
|
type GraphNodeAttachProvider interface {
|
||||||
|
// Must be implemented to determine the path for the configuration
|
||||||
|
GraphNodeSubPath
|
||||||
|
|
||||||
|
// ProviderName with no module prefix. Example: "aws".
|
||||||
|
ProviderName() string
|
||||||
|
|
||||||
|
// Sets the configuration
|
||||||
|
AttachProvider(*config.ProviderConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachProviderConfigTransformer goes through the graph and attaches
|
||||||
|
// provider 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 AttachProviderConfigTransformer struct {
|
||||||
|
Module *module.Tree // Module is the root module for the config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AttachProviderConfigTransformer) Transform(g *Graph) error {
|
||||||
|
if err := t.attachProviders(g); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AttachProviderConfigTransformer) attachProviders(g *Graph) error {
|
||||||
|
// Go through and find GraphNodeAttachProvider
|
||||||
|
for _, v := range g.Vertices() {
|
||||||
|
// Only care about GraphNodeAttachProvider implementations
|
||||||
|
apn, ok := v.(GraphNodeAttachProvider)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: aliases?
|
||||||
|
|
||||||
|
// Determine what we're looking for
|
||||||
|
path := normalizeModulePath(apn.Path())
|
||||||
|
path = path[1:]
|
||||||
|
name := apn.ProviderName()
|
||||||
|
log.Printf("[TRACE] Attach provider request: %#v %s", path, name)
|
||||||
|
|
||||||
|
// Get the configuration.
|
||||||
|
tree := t.Module.Child(path)
|
||||||
|
if tree == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through the provider configs to find the matching config
|
||||||
|
for _, p := range tree.Config().ProviderConfigs {
|
||||||
|
if p.Name == name {
|
||||||
|
log.Printf("[TRACE] Attaching provider config: %#v", p)
|
||||||
|
apn.AttachProvider(p)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
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 {
|
||||||
|
log.Printf("[TRACE] AttachResourceConfigTransformer: Beginning...")
|
||||||
|
|
||||||
|
// 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] AttachResourceConfigTransformer: 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
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GraphNodeAttachResourceState is an interface that can be implemented
|
||||||
|
// to request that a ResourceState is attached to the node.
|
||||||
|
type GraphNodeAttachResourceState interface {
|
||||||
|
// The address to the resource for the state
|
||||||
|
ResourceAddr() *ResourceAddress
|
||||||
|
|
||||||
|
// Sets the state
|
||||||
|
AttachResourceState(*ResourceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachStateTransformer goes through the graph and attaches
|
||||||
|
// state to nodes that implement the interfaces above.
|
||||||
|
type AttachStateTransformer struct {
|
||||||
|
State *State // State is the root state
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AttachStateTransformer) Transform(g *Graph) error {
|
||||||
|
// If no state, then nothing to do
|
||||||
|
if t.State == nil {
|
||||||
|
log.Printf("[DEBUG] Not attaching any state: state is nil")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &StateFilter{State: t.State}
|
||||||
|
for _, v := range g.Vertices() {
|
||||||
|
// Only care about nodes requesting we're adding state
|
||||||
|
an, ok := v.(GraphNodeAttachResourceState)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addr := an.ResourceAddr()
|
||||||
|
|
||||||
|
// Get the module state
|
||||||
|
results, err := filter.Filter(addr.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach the first resource state we get
|
||||||
|
found := false
|
||||||
|
for _, result := range results {
|
||||||
|
if rs, ok := result.Value.(*ResourceState); ok {
|
||||||
|
log.Printf(
|
||||||
|
"[DEBUG] Attaching resource state to %q: %s",
|
||||||
|
dag.VertexName(v), rs)
|
||||||
|
an.AttachResourceState(rs)
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
log.Printf(
|
||||||
|
"[DEBUG] Resource state not foudn for %q: %s",
|
||||||
|
dag.VertexName(v), addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FlatConfigTransformer is a GraphTransformer that adds the configuration
|
||||||
|
// to the graph. The module used to configure this transformer must be
|
||||||
|
// the root module.
|
||||||
|
//
|
||||||
|
// This transform adds the nodes but doesn't connect any of the references.
|
||||||
|
// The ReferenceTransformer should be used for that.
|
||||||
|
//
|
||||||
|
// NOTE: In relation to ConfigTransformer: this is a newer generation config
|
||||||
|
// transformer. It puts the _entire_ config into the graph (there is no
|
||||||
|
// "flattening" step as before).
|
||||||
|
type FlatConfigTransformer struct {
|
||||||
|
Concrete ConcreteResourceNodeFunc // What to turn resources into
|
||||||
|
|
||||||
|
Module *module.Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *FlatConfigTransformer) Transform(g *Graph) error {
|
||||||
|
// If no module, we do nothing
|
||||||
|
if t.Module == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the module is not loaded, that is an error
|
||||||
|
if !t.Module.Loaded() {
|
||||||
|
return errors.New("module must be loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.transform(g, t.Module)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *FlatConfigTransformer) transform(g *Graph, m *module.Tree) error {
|
||||||
|
// If no module, no problem
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform all the children.
|
||||||
|
for _, c := range m.Children() {
|
||||||
|
if err := t.transform(g, c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the configuration for this module
|
||||||
|
config := m.Config()
|
||||||
|
|
||||||
|
// Write all the resources out
|
||||||
|
for _, r := range config.Resources {
|
||||||
|
// Grab the address for this resource
|
||||||
|
addr, err := parseResourceAddressConfig(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
addr.Path = m.Path()
|
||||||
|
|
||||||
|
// Build the abstract resource. We have the config already so
|
||||||
|
// we'll just pre-populate that.
|
||||||
|
abstract := &NodeAbstractResource{
|
||||||
|
Addr: addr,
|
||||||
|
Config: r,
|
||||||
|
}
|
||||||
|
var node dag.Vertex = abstract
|
||||||
|
if f := t.Concrete; f != nil {
|
||||||
|
node = f(abstract)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Add(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFlatConfigTransformer_nilModule(t *testing.T) {
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
tf := &FlatConfigTransformer{}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.Vertices()) > 0 {
|
||||||
|
t.Fatal("graph should be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlatConfigTransformer(t *testing.T) {
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
tf := &FlatConfigTransformer{
|
||||||
|
Module: testModule(t, "transform-flat-config-basic"),
|
||||||
|
}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(testTransformFlatConfigBasicStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testTransformFlatConfigBasicStr = `
|
||||||
|
aws_instance.bar
|
||||||
|
aws_instance.foo
|
||||||
|
module.child.aws_instance.baz
|
||||||
|
`
|
|
@ -0,0 +1,28 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CountBoundaryTransformer adds a node that depends on everything else
|
||||||
|
// so that it runs last in order to clean up the state for nodes that
|
||||||
|
// are on the "count boundary": "foo.0" when only one exists becomes "foo"
|
||||||
|
type CountBoundaryTransformer struct{}
|
||||||
|
|
||||||
|
func (t *CountBoundaryTransformer) Transform(g *Graph) error {
|
||||||
|
node := &NodeCountBoundary{}
|
||||||
|
g.Add(node)
|
||||||
|
|
||||||
|
// Depends on everything
|
||||||
|
for _, v := range g.Vertices() {
|
||||||
|
// Don't connect to ourselves
|
||||||
|
if v == node {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect!
|
||||||
|
g.Connect(dag.BasicEdge(node, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GraphNodeDestroyerCBD must be implemented by nodes that might be
|
||||||
|
// create-before-destroy destroyers.
|
||||||
|
type GraphNodeDestroyerCBD interface {
|
||||||
|
GraphNodeDestroyer
|
||||||
|
|
||||||
|
// CreateBeforeDestroy returns true if this node represents a node
|
||||||
|
// that is doing a CBD.
|
||||||
|
CreateBeforeDestroy() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// CBDEdgeTransformer modifies the edges of CBD nodes that went through
|
||||||
|
// the DestroyEdgeTransformer to have the right dependencies. There are
|
||||||
|
// two real tasks here:
|
||||||
|
//
|
||||||
|
// 1. With CBD, the destroy edge is inverted: the destroy depends on
|
||||||
|
// the creation.
|
||||||
|
//
|
||||||
|
// 2. A_d must depend on resources that depend on A. This is to enable
|
||||||
|
// the destroy to only happen once nodes that depend on A successfully
|
||||||
|
// update to A. Example: adding a web server updates the load balancer
|
||||||
|
// before deleting the old web server.
|
||||||
|
//
|
||||||
|
type CBDEdgeTransformer struct {
|
||||||
|
// Module and State are only needed to look up dependencies in
|
||||||
|
// any way possible. Either can be nil if not availabile.
|
||||||
|
Module *module.Tree
|
||||||
|
State *State
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CBDEdgeTransformer) Transform(g *Graph) error {
|
||||||
|
log.Printf("[TRACE] CBDEdgeTransformer: Beginning CBD transformation...")
|
||||||
|
|
||||||
|
// Go through and reverse any destroy edges
|
||||||
|
destroyMap := make(map[string][]dag.Vertex)
|
||||||
|
for _, v := range g.Vertices() {
|
||||||
|
dn, ok := v.(GraphNodeDestroyerCBD)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dn.CreateBeforeDestroy() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the destroy edge. There should only be one.
|
||||||
|
for _, e := range g.EdgesTo(v) {
|
||||||
|
// Not a destroy edge, ignore it
|
||||||
|
de, ok := e.(*DestroyEdge)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[TRACE] CBDEdgeTransformer: inverting edge: %s => %s",
|
||||||
|
dag.VertexName(de.Source()), dag.VertexName(de.Target()))
|
||||||
|
|
||||||
|
// Found it! Invert.
|
||||||
|
g.RemoveEdge(de)
|
||||||
|
g.Connect(&DestroyEdge{S: de.Target(), T: de.Source()})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this to the list of nodes that we need to fix up
|
||||||
|
// the edges for (step 2 above in the docs).
|
||||||
|
key := dn.DestroyAddr().String()
|
||||||
|
destroyMap[key] = append(destroyMap[key], v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have no CBD nodes, then our work here is done
|
||||||
|
if len(destroyMap) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have CBD nodes. We now have to move on to the much more difficult
|
||||||
|
// task of connecting dependencies of the creation side of the destroy
|
||||||
|
// to the destruction node. The easiest way to explain this is an example:
|
||||||
|
//
|
||||||
|
// Given a pre-destroy dependence of: A => B
|
||||||
|
// And A has CBD set.
|
||||||
|
//
|
||||||
|
// The resulting graph should be: A => B => A_d
|
||||||
|
//
|
||||||
|
// They key here is that B happens before A is destroyed. This is to
|
||||||
|
// facilitate the primary purpose for CBD: making sure that downstreams
|
||||||
|
// are properly updated to avoid downtime before the resource is destroyed.
|
||||||
|
//
|
||||||
|
// We can't trust that the resource being destroyed or anything that
|
||||||
|
// depends on it is actually in our current graph so we make a new
|
||||||
|
// graph in order to determine those dependencies and add them in.
|
||||||
|
log.Printf("[TRACE] CBDEdgeTransformer: building graph to find dependencies...")
|
||||||
|
depMap, err := t.depMap(destroyMap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now have the mapping of resource addresses to the destroy
|
||||||
|
// nodes they need to depend on. We now go through our own vertices to
|
||||||
|
// find any matching these addresses and make the connection.
|
||||||
|
for _, v := range g.Vertices() {
|
||||||
|
// We're looking for creators
|
||||||
|
rn, ok := v.(GraphNodeCreator)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the address
|
||||||
|
addr := rn.CreateAddr()
|
||||||
|
key := addr.String()
|
||||||
|
|
||||||
|
// If there is nothing this resource should depend on, ignore it
|
||||||
|
dns, ok := depMap[key]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have nodes! Make the connection
|
||||||
|
for _, dn := range dns {
|
||||||
|
log.Printf("[TRACE] CBDEdgeTransformer: destroy depends on dependence: %s => %s",
|
||||||
|
dag.VertexName(dn), dag.VertexName(v))
|
||||||
|
g.Connect(dag.BasicEdge(dn, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CBDEdgeTransformer) depMap(
|
||||||
|
destroyMap map[string][]dag.Vertex) (map[string][]dag.Vertex, error) {
|
||||||
|
// Build the graph of our config, this ensures that all resources
|
||||||
|
// are present in the graph.
|
||||||
|
g, err := (&BasicGraphBuilder{
|
||||||
|
Steps: []GraphTransformer{
|
||||||
|
&FlatConfigTransformer{Module: t.Module},
|
||||||
|
&AttachResourceConfigTransformer{Module: t.Module},
|
||||||
|
&AttachStateTransformer{State: t.State},
|
||||||
|
&ReferenceTransformer{},
|
||||||
|
},
|
||||||
|
}).Build(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using this graph, build the list of destroy nodes that each resource
|
||||||
|
// address should depend on. For example, when we find B, we map the
|
||||||
|
// address of B to A_d in the "depMap" variable below.
|
||||||
|
depMap := make(map[string][]dag.Vertex)
|
||||||
|
for _, v := range g.Vertices() {
|
||||||
|
// We're looking for resources.
|
||||||
|
rn, ok := v.(GraphNodeResource)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the address
|
||||||
|
addr := rn.ResourceAddr()
|
||||||
|
key := addr.String()
|
||||||
|
|
||||||
|
// Get the destroy nodes that are destroying this resource.
|
||||||
|
// If there aren't any, then we don't need to worry about
|
||||||
|
// any connections.
|
||||||
|
dns, ok := destroyMap[key]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the nodes that depend on this on. In the example above:
|
||||||
|
// finding B in A => B.
|
||||||
|
for _, v := range g.UpEdges(v).List() {
|
||||||
|
// We're looking for resources.
|
||||||
|
rn, ok := v.(GraphNodeResource)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep track of the destroy nodes that this address
|
||||||
|
// needs to depend on.
|
||||||
|
key := rn.ResourceAddr().String()
|
||||||
|
depMap[key] = append(depMap[key], dns...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return depMap, nil
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCBDEdgeTransformer(t *testing.T) {
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
g.Add(&graphNodeCreatorTest{AddrString: "test.A"})
|
||||||
|
g.Add(&graphNodeCreatorTest{AddrString: "test.B"})
|
||||||
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.A", CBD: true})
|
||||||
|
|
||||||
|
module := testModule(t, "transform-destroy-edge-basic")
|
||||||
|
|
||||||
|
{
|
||||||
|
tf := &DestroyEdgeTransformer{
|
||||||
|
Module: module,
|
||||||
|
}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
tf := &CBDEdgeTransformer{Module: module}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(testTransformCBDEdgeBasicStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testTransformCBDEdgeBasicStr = `
|
||||||
|
test.A
|
||||||
|
test.A (destroy)
|
||||||
|
test.A
|
||||||
|
test.B
|
||||||
|
test.B
|
||||||
|
`
|
|
@ -0,0 +1,191 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GraphNodeDestroyer must be implemented by nodes that destroy resources.
|
||||||
|
type GraphNodeDestroyer interface {
|
||||||
|
dag.Vertex
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeCreator must be implemented by nodes that create OR update resources.
|
||||||
|
type GraphNodeCreator interface {
|
||||||
|
// ResourceAddr is the address of the resource being created or updated
|
||||||
|
CreateAddr() *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 {
|
||||||
|
// Module and State are only needed to look up dependencies in
|
||||||
|
// any way possible. Either can be nil if not availabile.
|
||||||
|
Module *module.Tree
|
||||||
|
State *State
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
|
||||||
|
log.Printf("[TRACE] DestroyEdgeTransformer: Beginning destroy edge transformation...")
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
log.Printf(
|
||||||
|
"[TRACE] DestroyEdgeTransformer: %s destroying %q",
|
||||||
|
dag.VertexName(dn), key)
|
||||||
|
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 and connect creators to destroyers. Going along with
|
||||||
|
// our example, this makes: A_d => A
|
||||||
|
for _, v := range g.Vertices() {
|
||||||
|
cn, ok := v.(GraphNodeCreator)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := cn.CreateAddr()
|
||||||
|
if addr == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := addr.String()
|
||||||
|
ds := destroyers[key]
|
||||||
|
if len(ds) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range ds {
|
||||||
|
// For illustrating our example
|
||||||
|
a_d := d.(dag.Vertex)
|
||||||
|
a := v
|
||||||
|
|
||||||
|
log.Printf(
|
||||||
|
"[TRACE] DestroyEdgeTransformer: connecting creator/destroyer: %s, %s",
|
||||||
|
dag.VertexName(a), dag.VertexName(a_d))
|
||||||
|
|
||||||
|
g.Connect(&DestroyEdge{S: a, T: a_d})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is strange but is the easiest way to get the dependencies
|
||||||
|
// of a node that is being destroyed. We use another graph to make sure
|
||||||
|
// the resource is in the graph and ask for references. We have to do this
|
||||||
|
// because the node that is being destroyed may NOT be in the graph.
|
||||||
|
//
|
||||||
|
// Example: resource A is force new, then destroy A AND create A are
|
||||||
|
// in the graph. BUT if resource A is just pure destroy, then only
|
||||||
|
// destroy A is in the graph, and create A is not.
|
||||||
|
steps := []GraphTransformer{
|
||||||
|
&AttachResourceConfigTransformer{Module: t.Module},
|
||||||
|
&AttachStateTransformer{State: t.State},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 d, dns := range destroyers {
|
||||||
|
// d is what is being destroyed. We parse the resource address
|
||||||
|
// which it came from it is a panic if this fails.
|
||||||
|
addr, err := ParseResourceAddress(d)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This part is a little bit weird but is the best way to
|
||||||
|
// find the dependencies we need to: build a graph and use the
|
||||||
|
// attach config and state transformers then ask for references.
|
||||||
|
node := &NodeAbstractResource{Addr: addr}
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
// then there are no edges to make here.
|
||||||
|
prefix := modulePrefixStr(normalizeModulePath(addr.Path))
|
||||||
|
deps := modulePrefixList(node.References(), prefix)
|
||||||
|
log.Printf(
|
||||||
|
"[TRACE] DestroyEdgeTransformer: creation of %q depends on %#v",
|
||||||
|
d, deps)
|
||||||
|
if len(deps) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have dependencies, check if any are being destroyed
|
||||||
|
// to build the list of things that we must depend on!
|
||||||
|
//
|
||||||
|
// In the example of the struct, if we have:
|
||||||
|
//
|
||||||
|
// B_d => A_d => A => B
|
||||||
|
//
|
||||||
|
// Then at this point in the algorithm we started with A_d,
|
||||||
|
// we built A (to get dependencies), and we found B. We're now looking
|
||||||
|
// to see if B_d exists.
|
||||||
|
var depDestroyers []dag.Vertex
|
||||||
|
for _, d := range deps {
|
||||||
|
if ds, ok := destroyers[d]; ok {
|
||||||
|
for _, d := range ds {
|
||||||
|
depDestroyers = append(depDestroyers, d.(dag.Vertex))
|
||||||
|
log.Printf(
|
||||||
|
"[TRACE] DestroyEdgeTransformer: destruction of %q depends on %s",
|
||||||
|
addr.String(), dag.VertexName(d))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through and make the connections. Use the variable
|
||||||
|
// names "a_d" and "b_d" to reference our example.
|
||||||
|
for _, a_d := range dns {
|
||||||
|
for _, b_d := range depDestroyers {
|
||||||
|
g.Connect(dag.BasicEdge(b_d, a_d))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDestroyEdgeTransformer(t *testing.T) {
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
|
||||||
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.B"})
|
||||||
|
tf := &DestroyEdgeTransformer{
|
||||||
|
Module: testModule(t, "transform-destroy-edge-basic"),
|
||||||
|
}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(testTransformDestroyEdgeBasicStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDestroyEdgeTransformer_create(t *testing.T) {
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
|
||||||
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.B"})
|
||||||
|
g.Add(&graphNodeCreatorTest{AddrString: "test.A"})
|
||||||
|
tf := &DestroyEdgeTransformer{
|
||||||
|
Module: testModule(t, "transform-destroy-edge-basic"),
|
||||||
|
}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(testTransformDestroyEdgeCreatorStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDestroyEdgeTransformer_multi(t *testing.T) {
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
|
||||||
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.B"})
|
||||||
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.C"})
|
||||||
|
tf := &DestroyEdgeTransformer{
|
||||||
|
Module: testModule(t, "transform-destroy-edge-multi"),
|
||||||
|
}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(testTransformDestroyEdgeMultiStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type graphNodeCreatorTest struct {
|
||||||
|
AddrString string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeCreatorTest) Name() string { return n.CreateAddr().String() }
|
||||||
|
func (n *graphNodeCreatorTest) CreateAddr() *ResourceAddress {
|
||||||
|
addr, err := ParseResourceAddress(n.AddrString)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
type graphNodeDestroyerTest struct {
|
||||||
|
AddrString string
|
||||||
|
CBD bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeDestroyerTest) Name() string { return n.DestroyAddr().String() + " (destroy)" }
|
||||||
|
func (n *graphNodeDestroyerTest) CreateBeforeDestroy() bool { return n.CBD }
|
||||||
|
func (n *graphNodeDestroyerTest) DestroyAddr() *ResourceAddress {
|
||||||
|
addr, err := ParseResourceAddress(n.AddrString)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
const testTransformDestroyEdgeBasicStr = `
|
||||||
|
test.A (destroy)
|
||||||
|
test.B (destroy)
|
||||||
|
test.B (destroy)
|
||||||
|
`
|
||||||
|
|
||||||
|
const testTransformDestroyEdgeCreatorStr = `
|
||||||
|
test.A
|
||||||
|
test.A (destroy)
|
||||||
|
test.A (destroy)
|
||||||
|
test.B (destroy)
|
||||||
|
test.B (destroy)
|
||||||
|
`
|
||||||
|
|
||||||
|
const testTransformDestroyEdgeMultiStr = `
|
||||||
|
test.A (destroy)
|
||||||
|
test.B (destroy)
|
||||||
|
test.B (destroy)
|
||||||
|
test.C (destroy)
|
||||||
|
test.C (destroy)
|
||||||
|
`
|
|
@ -0,0 +1,86 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DiffTransformer is a GraphTransformer that adds the elements of
|
||||||
|
// the diff to the graph.
|
||||||
|
//
|
||||||
|
// This transform is used for example by the ApplyGraphBuilder to ensure
|
||||||
|
// that only resources that are being modified are represented in the graph.
|
||||||
|
//
|
||||||
|
// Module and State is still required for the DiffTransformer for annotations
|
||||||
|
// since the Diff doesn't contain all the information required to build the
|
||||||
|
// complete graph (such as create-before-destroy information). The graph
|
||||||
|
// is built based on the diff first, though, ensuring that only resources
|
||||||
|
// that are being modified are present in the graph.
|
||||||
|
type DiffTransformer struct {
|
||||||
|
Concrete ConcreteResourceNodeFunc
|
||||||
|
|
||||||
|
Diff *Diff
|
||||||
|
Module *module.Tree
|
||||||
|
State *State
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DiffTransformer) Transform(g *Graph) error {
|
||||||
|
// If the diff is nil or empty (nil is empty) then do nothing
|
||||||
|
if t.Diff.Empty() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through all the modules in the diff.
|
||||||
|
log.Printf("[TRACE] DiffTransformer: starting")
|
||||||
|
var nodes []dag.Vertex
|
||||||
|
for _, m := range t.Diff.Modules {
|
||||||
|
log.Printf("[TRACE] DiffTransformer: Module: %s", m)
|
||||||
|
// TODO: If this is a destroy diff then add a module destroy node
|
||||||
|
|
||||||
|
// Go through all the resources in this module.
|
||||||
|
for name, inst := range m.Resources {
|
||||||
|
log.Printf("[TRACE] DiffTransformer: Resource %q: %#v", name, inst)
|
||||||
|
|
||||||
|
// We have changes! This is a create or update operation.
|
||||||
|
// First grab the address so we have a unique way to
|
||||||
|
// reference this resource.
|
||||||
|
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 = m.Path[1:]
|
||||||
|
|
||||||
|
// If we're destroying, add the destroy node
|
||||||
|
if inst.Destroy {
|
||||||
|
abstract := NodeAbstractResource{Addr: addr}
|
||||||
|
g.Add(&NodeDestroyResource{NodeAbstractResource: abstract})
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have changes, then add the applyable version
|
||||||
|
if len(inst.Attributes) > 0 {
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDiffTransformer_nilDiff(t *testing.T) {
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
tf := &DiffTransformer{}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.Vertices()) > 0 {
|
||||||
|
t.Fatal("graph should be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiffTransformer(t *testing.T) {
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
tf := &DiffTransformer{
|
||||||
|
Module: testModule(t, "transform-diff-basic"),
|
||||||
|
Diff: &Diff{
|
||||||
|
Modules: []*ModuleDiff{
|
||||||
|
&ModuleDiff{
|
||||||
|
Path: []string{"root"},
|
||||||
|
Resources: map[string]*InstanceDiff{
|
||||||
|
"aws_instance.foo": &InstanceDiff{
|
||||||
|
Attributes: map[string]*ResourceAttrDiff{
|
||||||
|
"name": &ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(testTransformDiffBasicStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testTransformDiffBasicStr = `
|
||||||
|
aws_instance.foo
|
||||||
|
`
|
|
@ -1 +0,0 @@
|
||||||
package terraform
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ModuleVariableTransformer is a GraphTransformer that adds all the variables
|
||||||
|
// in the configuration to the graph.
|
||||||
|
//
|
||||||
|
// This only adds variables that either have no dependencies (and therefore
|
||||||
|
// always succeed) or has dependencies that are 100% represented in the
|
||||||
|
// graph.
|
||||||
|
type ModuleVariableTransformer struct {
|
||||||
|
Module *module.Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ModuleVariableTransformer) Transform(g *Graph) error {
|
||||||
|
return t.transform(g, nil, t.Module)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ModuleVariableTransformer) transform(g *Graph, parent, m *module.Tree) error {
|
||||||
|
// If no config, no variables
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a parent, we can determine if a module variable is being
|
||||||
|
// used, so we transform this.
|
||||||
|
if parent != nil {
|
||||||
|
if err := t.transformSingle(g, parent, m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform all the children. This must be done AFTER the transform
|
||||||
|
// above since child module variables can reference parent module variables.
|
||||||
|
for _, c := range m.Children() {
|
||||||
|
if err := t.transform(g, m, c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ModuleVariableTransformer) transformSingle(g *Graph, parent, m *module.Tree) error {
|
||||||
|
// If we have no vars, we're done!
|
||||||
|
vars := m.Config().Variables
|
||||||
|
if len(vars) == 0 {
|
||||||
|
log.Printf("[TRACE] Module %#v has no variables, skipping.", m.Path())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for usage of this module
|
||||||
|
var mod *config.Module
|
||||||
|
for _, modUse := range parent.Config().Modules {
|
||||||
|
if modUse.Name == m.Name() {
|
||||||
|
mod = modUse
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mod == nil {
|
||||||
|
log.Printf("[INFO] Module %#v not used, not adding variables", m.Path())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the reference map so we can determine if we're referencing things.
|
||||||
|
refMap := NewReferenceMap(g.Vertices())
|
||||||
|
|
||||||
|
// Add all variables here
|
||||||
|
for _, v := range vars {
|
||||||
|
// Determine the value of the variable. If it isn't in the
|
||||||
|
// configuration then it was never set and that's not a problem.
|
||||||
|
var value *config.RawConfig
|
||||||
|
if raw, ok := mod.RawConfig.Raw[v.Name]; ok {
|
||||||
|
var err error
|
||||||
|
value, err = config.NewRawConfig(map[string]interface{}{
|
||||||
|
v.Name: raw,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// This shouldn't happen because it is already in
|
||||||
|
// a RawConfig above meaning it worked once before.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the node.
|
||||||
|
//
|
||||||
|
// NOTE: For now this is just an "applyable" variable. As we build
|
||||||
|
// new graph builders for the other operations I suspect we'll
|
||||||
|
// find a way to parameterize this, require new transforms, etc.
|
||||||
|
node := &NodeApplyableModuleVariable{
|
||||||
|
PathValue: normalizeModulePath(m.Path()),
|
||||||
|
Config: v,
|
||||||
|
Value: value,
|
||||||
|
Module: t.Module,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the node references something, then we check to make sure
|
||||||
|
// that the thing it references is in the graph. If it isn't, then
|
||||||
|
// we don't add it because we may not be able to compute the output.
|
||||||
|
//
|
||||||
|
// If the node references nothing, we always include it since there
|
||||||
|
// is no other clear time to compute it.
|
||||||
|
matches, missing := refMap.References(node)
|
||||||
|
if len(missing) > 0 {
|
||||||
|
log.Printf(
|
||||||
|
"[INFO] Not including %q in graph, matches: %v, missing: %s",
|
||||||
|
dag.VertexName(node), matches, missing)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add it!
|
||||||
|
g.Add(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestModuleVariableTransformer(t *testing.T) {
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
module := testModule(t, "transform-module-var-basic")
|
||||||
|
|
||||||
|
{
|
||||||
|
tf := &RootVariableTransformer{Module: module}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
tf := &ModuleVariableTransformer{Module: module}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(testTransformModuleVarBasicStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModuleVariableTransformer_nested(t *testing.T) {
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
module := testModule(t, "transform-module-var-nested")
|
||||||
|
|
||||||
|
{
|
||||||
|
tf := &RootVariableTransformer{Module: module}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
tf := &ModuleVariableTransformer{Module: module}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(testTransformModuleVarNestedStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testTransformModuleVarBasicStr = `
|
||||||
|
module.child.var.value
|
||||||
|
`
|
||||||
|
|
||||||
|
const testTransformModuleVarNestedStr = `
|
||||||
|
module.child.module.child.var.value
|
||||||
|
module.child.var.value
|
||||||
|
`
|
|
@ -209,6 +209,8 @@ func (n *graphNodeOrphanResource) EvalTree() EvalNode {
|
||||||
|
|
||||||
// Build instance info
|
// Build instance info
|
||||||
info := &InstanceInfo{Id: n.ResourceKey.String(), Type: n.ResourceKey.Type}
|
info := &InstanceInfo{Id: n.ResourceKey.String(), Type: n.ResourceKey.Type}
|
||||||
|
info.uniqueExtra = "destroy"
|
||||||
|
|
||||||
seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info})
|
seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info})
|
||||||
|
|
||||||
// Each resource mode has its own lifecycle
|
// Each resource mode has its own lifecycle
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OrphanOutputTransformer finds the outputs that aren't present
|
||||||
|
// in the given config that are in the state and adds them to the graph
|
||||||
|
// for deletion.
|
||||||
|
type OrphanOutputTransformer struct {
|
||||||
|
Module *module.Tree // Root module
|
||||||
|
State *State // State is the root state
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *OrphanOutputTransformer) Transform(g *Graph) error {
|
||||||
|
if t.State == nil {
|
||||||
|
log.Printf("[DEBUG] No state, no orphan outputs")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.transform(g, t.Module)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *OrphanOutputTransformer) transform(g *Graph, m *module.Tree) error {
|
||||||
|
// Get our configuration, and recurse into children
|
||||||
|
var c *config.Config
|
||||||
|
if m != nil {
|
||||||
|
c = m.Config()
|
||||||
|
for _, child := range m.Children() {
|
||||||
|
if err := t.transform(g, child); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the state. If there is no state, then we have no orphans!
|
||||||
|
path := normalizeModulePath(m.Path())
|
||||||
|
state := t.State.ModuleByPath(path)
|
||||||
|
if state == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a map of the valid outputs
|
||||||
|
valid := make(map[string]struct{})
|
||||||
|
for _, o := range c.Outputs {
|
||||||
|
valid[o.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through the outputs and find the ones that aren't in our config.
|
||||||
|
for n, _ := range state.Outputs {
|
||||||
|
// If it is in the valid map, then ignore
|
||||||
|
if _, ok := valid[n]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Orphan!
|
||||||
|
g.Add(&NodeOutputOrphan{OutputName: n, PathValue: path})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,98 +1,59 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/dag"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GraphNodeOutput is an interface that nodes that are outputs must
|
// OutputTransformer is a GraphTransformer that adds all the outputs
|
||||||
// implement. The OutputName returned is the name of the output key
|
// in the configuration to the graph.
|
||||||
// that they manage.
|
//
|
||||||
type GraphNodeOutput interface {
|
// This is done for the apply graph builder even if dependent nodes
|
||||||
OutputName() string
|
// aren't changing since there is no downside: the state will be available
|
||||||
|
// even if the dependent items aren't changing.
|
||||||
|
type OutputTransformer struct {
|
||||||
|
Module *module.Tree
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddOutputOrphanTransformer is a transformer that adds output orphans
|
func (t *OutputTransformer) Transform(g *Graph) error {
|
||||||
// to the graph. Output orphans are outputs that are no longer in the
|
return t.transform(g, t.Module)
|
||||||
// configuration and therefore need to be removed from the state.
|
|
||||||
type AddOutputOrphanTransformer struct {
|
|
||||||
State *State
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *AddOutputOrphanTransformer) Transform(g *Graph) error {
|
func (t *OutputTransformer) transform(g *Graph, m *module.Tree) error {
|
||||||
// Get the state for this module. If we have no state, we have no orphans
|
// If no config, no outputs
|
||||||
state := t.State.ModuleByPath(g.Path)
|
if m == nil {
|
||||||
if state == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the set of outputs we do have in the graph
|
// Transform all the children. We must do this first because
|
||||||
found := make(map[string]struct{})
|
// we can reference module outputs and they must show up in the
|
||||||
for _, v := range g.Vertices() {
|
// reference map.
|
||||||
on, ok := v.(GraphNodeOutput)
|
for _, c := range m.Children() {
|
||||||
if !ok {
|
if err := t.transform(g, c); err != nil {
|
||||||
continue
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
found[on.OutputName()] = struct{}{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go over all the outputs. If we don't have a graph node for it,
|
// If we have no outputs, we're done!
|
||||||
// create it. It doesn't need to depend on anything, since its just
|
os := m.Config().Outputs
|
||||||
// setting it empty.
|
if len(os) == 0 {
|
||||||
for k, _ := range state.Outputs {
|
return nil
|
||||||
if _, ok := found[k]; ok {
|
}
|
||||||
continue
|
|
||||||
|
// Add all outputs here
|
||||||
|
for _, o := range os {
|
||||||
|
// Build the node.
|
||||||
|
//
|
||||||
|
// NOTE: For now this is just an "applyable" output. As we build
|
||||||
|
// new graph builders for the other operations I suspect we'll
|
||||||
|
// find a way to parameterize this, require new transforms, etc.
|
||||||
|
node := &NodeApplyableOutput{
|
||||||
|
PathValue: normalizeModulePath(m.Path()),
|
||||||
|
Config: o,
|
||||||
}
|
}
|
||||||
|
|
||||||
g.Add(&graphNodeOrphanOutput{OutputName: k})
|
// Add it!
|
||||||
|
g.Add(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type graphNodeOrphanOutput struct {
|
|
||||||
OutputName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeOrphanOutput) Name() string {
|
|
||||||
return fmt.Sprintf("output.%s (orphan)", n.OutputName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeOrphanOutput) EvalTree() EvalNode {
|
|
||||||
return &EvalOpFilter{
|
|
||||||
Ops: []walkOperation{walkApply, walkDestroy, walkRefresh},
|
|
||||||
Node: &EvalDeleteOutput{
|
|
||||||
Name: n.OutputName,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeFlattenable impl.
|
|
||||||
func (n *graphNodeOrphanOutput) Flatten(p []string) (dag.Vertex, error) {
|
|
||||||
return &graphNodeOrphanOutputFlat{
|
|
||||||
graphNodeOrphanOutput: n,
|
|
||||||
PathValue: p,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type graphNodeOrphanOutputFlat struct {
|
|
||||||
*graphNodeOrphanOutput
|
|
||||||
|
|
||||||
PathValue []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeOrphanOutputFlat) Name() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeOrphanOutput.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeOrphanOutputFlat) EvalTree() EvalNode {
|
|
||||||
return &EvalOpFilter{
|
|
||||||
Ops: []walkOperation{walkApply, walkDestroy, walkRefresh},
|
|
||||||
Node: &EvalDeleteOutput{
|
|
||||||
Name: n.OutputName,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GraphNodeOutput is an interface that nodes that are outputs must
|
||||||
|
// implement. The OutputName returned is the name of the output key
|
||||||
|
// that they manage.
|
||||||
|
type GraphNodeOutput interface {
|
||||||
|
OutputName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddOutputOrphanTransformer is a transformer that adds output orphans
|
||||||
|
// to the graph. Output orphans are outputs that are no longer in the
|
||||||
|
// configuration and therefore need to be removed from the state.
|
||||||
|
//
|
||||||
|
// NOTE: This is the _old_ way to add output orphans that is used with
|
||||||
|
// legacy graph builders. The new way is OrphanOutputTransformer.
|
||||||
|
type AddOutputOrphanTransformer struct {
|
||||||
|
State *State
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AddOutputOrphanTransformer) Transform(g *Graph) error {
|
||||||
|
// Get the state for this module. If we have no state, we have no orphans
|
||||||
|
state := t.State.ModuleByPath(g.Path)
|
||||||
|
if state == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the set of outputs we do have in the graph
|
||||||
|
found := make(map[string]struct{})
|
||||||
|
for _, v := range g.Vertices() {
|
||||||
|
on, ok := v.(GraphNodeOutput)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
found[on.OutputName()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go over all the outputs. If we don't have a graph node for it,
|
||||||
|
// create it. It doesn't need to depend on anything, since its just
|
||||||
|
// setting it empty.
|
||||||
|
for k, _ := range state.Outputs {
|
||||||
|
if _, ok := found[k]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Add(&graphNodeOrphanOutput{OutputName: k})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type graphNodeOrphanOutput struct {
|
||||||
|
OutputName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeOrphanOutput) Name() string {
|
||||||
|
return fmt.Sprintf("output.%s (orphan)", n.OutputName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeOrphanOutput) EvalTree() EvalNode {
|
||||||
|
return &EvalOpFilter{
|
||||||
|
Ops: []walkOperation{walkApply, walkDestroy, walkRefresh},
|
||||||
|
Node: &EvalDeleteOutput{
|
||||||
|
Name: n.OutputName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeFlattenable impl.
|
||||||
|
func (n *graphNodeOrphanOutput) Flatten(p []string) (dag.Vertex, error) {
|
||||||
|
return &graphNodeOrphanOutputFlat{
|
||||||
|
graphNodeOrphanOutput: n,
|
||||||
|
PathValue: p,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type graphNodeOrphanOutputFlat struct {
|
||||||
|
*graphNodeOrphanOutput
|
||||||
|
|
||||||
|
PathValue []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeOrphanOutputFlat) Name() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeOrphanOutput.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeOrphanOutputFlat) EvalTree() EvalNode {
|
||||||
|
return &EvalOpFilter{
|
||||||
|
Ops: []walkOperation{walkApply, walkDestroy, walkRefresh},
|
||||||
|
Node: &EvalDeleteOutput{
|
||||||
|
Name: n.OutputName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,55 +33,6 @@ type GraphNodeProviderConsumer interface {
|
||||||
ProvidedBy() []string
|
ProvidedBy() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableProviderTransformer "disables" any providers that are only
|
|
||||||
// depended on by modules.
|
|
||||||
type DisableProviderTransformer struct{}
|
|
||||||
|
|
||||||
func (t *DisableProviderTransformer) Transform(g *Graph) error {
|
|
||||||
// Since we're comparing against edges, we need to make sure we connect
|
|
||||||
g.ConnectDependents()
|
|
||||||
|
|
||||||
for _, v := range g.Vertices() {
|
|
||||||
// We only care about providers
|
|
||||||
pn, ok := v.(GraphNodeProvider)
|
|
||||||
if !ok || pn.ProviderName() == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go through all the up-edges (things that depend on this
|
|
||||||
// provider) and if any is not a module, then ignore this node.
|
|
||||||
nonModule := false
|
|
||||||
for _, sourceRaw := range g.UpEdges(v).List() {
|
|
||||||
source := sourceRaw.(dag.Vertex)
|
|
||||||
cn, ok := source.(graphNodeConfig)
|
|
||||||
if !ok {
|
|
||||||
nonModule = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if cn.ConfigType() != GraphNodeConfigTypeModule {
|
|
||||||
nonModule = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if nonModule {
|
|
||||||
// We found something that depends on this provider that
|
|
||||||
// isn't a module, so skip it.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable the provider by replacing it with a "disabled" provider
|
|
||||||
disabled := &graphNodeDisabledProvider{GraphNodeProvider: pn}
|
|
||||||
if !g.Replace(v, disabled) {
|
|
||||||
panic(fmt.Sprintf(
|
|
||||||
"vertex disappeared from under us: %s",
|
|
||||||
dag.VertexName(v)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProviderTransformer is a GraphTransformer that maps resources to
|
// ProviderTransformer is a GraphTransformer that maps resources to
|
||||||
// providers within the graph. This will error if there are any resources
|
// providers within the graph. This will error if there are any resources
|
||||||
// that don't map to proper resources.
|
// that don't map to proper resources.
|
||||||
|
@ -163,9 +114,19 @@ func (t *CloseProviderTransformer) Transform(g *Graph) error {
|
||||||
type MissingProviderTransformer struct {
|
type MissingProviderTransformer struct {
|
||||||
// Providers is the list of providers we support.
|
// Providers is the list of providers we support.
|
||||||
Providers []string
|
Providers []string
|
||||||
|
|
||||||
|
// Factory, if set, overrides how the providers are made.
|
||||||
|
Factory func(name string, path []string) GraphNodeProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MissingProviderTransformer) Transform(g *Graph) error {
|
func (t *MissingProviderTransformer) Transform(g *Graph) error {
|
||||||
|
// Initialize factory
|
||||||
|
if t.Factory == nil {
|
||||||
|
t.Factory = func(name string, path []string) GraphNodeProvider {
|
||||||
|
return &graphNodeProvider{ProviderNameValue: name}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create a set of our supported providers
|
// Create a set of our supported providers
|
||||||
supported := make(map[string]struct{}, len(t.Providers))
|
supported := make(map[string]struct{}, len(t.Providers))
|
||||||
for _, v := range t.Providers {
|
for _, v := range t.Providers {
|
||||||
|
@ -217,13 +178,14 @@ func (t *MissingProviderTransformer) Transform(g *Graph) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the missing provider node to the graph
|
// Add the missing provider node to the graph
|
||||||
raw := &graphNodeProvider{ProviderNameValue: p}
|
v := t.Factory(p, path).(dag.Vertex)
|
||||||
var v dag.Vertex = raw
|
|
||||||
if len(path) > 0 {
|
if len(path) > 0 {
|
||||||
var err error
|
if fn, ok := v.(GraphNodeFlattenable); ok {
|
||||||
v, err = raw.Flatten(path)
|
var err error
|
||||||
if err != nil {
|
v, err = fn.Flatten(path)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll need the parent provider as well, so let's
|
// We'll need the parent provider as well, so let's
|
||||||
|
@ -242,6 +204,66 @@ func (t *MissingProviderTransformer) Transform(g *Graph) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParentProviderTransformer connects provider nodes to their parents.
|
||||||
|
//
|
||||||
|
// This works by finding nodes that are both GraphNodeProviders and
|
||||||
|
// GraphNodeSubPath. It then connects the providers to their parent
|
||||||
|
// path.
|
||||||
|
type ParentProviderTransformer struct{}
|
||||||
|
|
||||||
|
func (t *ParentProviderTransformer) Transform(g *Graph) error {
|
||||||
|
// Make a mapping of path to dag.Vertex, where path is: "path.name"
|
||||||
|
m := make(map[string]dag.Vertex)
|
||||||
|
|
||||||
|
// Also create a map that maps a provider to its parent
|
||||||
|
parentMap := make(map[dag.Vertex]string)
|
||||||
|
for _, raw := range g.Vertices() {
|
||||||
|
// If it is the flat version, then make it the non-flat version.
|
||||||
|
// We eventually want to get rid of the flat version entirely so
|
||||||
|
// this is a stop-gap while it still exists.
|
||||||
|
var v dag.Vertex = raw
|
||||||
|
if f, ok := v.(*graphNodeProviderFlat); ok {
|
||||||
|
v = f.graphNodeProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only care about providers
|
||||||
|
pn, ok := v.(GraphNodeProvider)
|
||||||
|
if !ok || pn.ProviderName() == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also require a subpath, if there is no subpath then we
|
||||||
|
// just totally ignore it. The expectation of this transform is
|
||||||
|
// that it is used with a graph builder that is already flattened.
|
||||||
|
var path []string
|
||||||
|
if pn, ok := raw.(GraphNodeSubPath); ok {
|
||||||
|
path = pn.Path()
|
||||||
|
}
|
||||||
|
path = normalizeModulePath(path)
|
||||||
|
|
||||||
|
// Build the key with path.name i.e. "child.subchild.aws"
|
||||||
|
key := fmt.Sprintf("%s.%s", strings.Join(path, "."), pn.ProviderName())
|
||||||
|
m[key] = raw
|
||||||
|
|
||||||
|
// Determine the parent if we're non-root. This is length 1 since
|
||||||
|
// the 0 index should be "root" since we normalize above.
|
||||||
|
if len(path) > 1 {
|
||||||
|
path = path[:len(path)-1]
|
||||||
|
key := fmt.Sprintf("%s.%s", strings.Join(path, "."), pn.ProviderName())
|
||||||
|
parentMap[raw] = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect!
|
||||||
|
for v, key := range parentMap {
|
||||||
|
if parent, ok := m[key]; ok {
|
||||||
|
g.Connect(dag.BasicEdge(v, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// PruneProviderTransformer is a GraphTransformer that prunes all the
|
// PruneProviderTransformer is a GraphTransformer that prunes all the
|
||||||
// providers that aren't needed from the graph. A provider is unneeded if
|
// providers that aren't needed from the graph. A provider is unneeded if
|
||||||
// no resource or module is using that provider.
|
// no resource or module is using that provider.
|
||||||
|
@ -283,7 +305,16 @@ func providerVertexMap(g *Graph) map[string]dag.Vertex {
|
||||||
m := make(map[string]dag.Vertex)
|
m := make(map[string]dag.Vertex)
|
||||||
for _, v := range g.Vertices() {
|
for _, v := range g.Vertices() {
|
||||||
if pv, ok := v.(GraphNodeProvider); ok {
|
if pv, ok := v.(GraphNodeProvider); ok {
|
||||||
m[pv.ProviderName()] = v
|
key := pv.ProviderName()
|
||||||
|
|
||||||
|
// This special case is because the new world view of providers
|
||||||
|
// is that they should return only their pure name (not the full
|
||||||
|
// module path with ProviderName). Working towards this future.
|
||||||
|
if _, ok := v.(*NodeApplyableProvider); ok {
|
||||||
|
key = providerMapKey(pv.ProviderName(), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
m[key] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,118 +332,6 @@ func closeProviderVertexMap(g *Graph) map[string]dag.Vertex {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
type graphNodeDisabledProvider struct {
|
|
||||||
GraphNodeProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeEvalable impl.
|
|
||||||
func (n *graphNodeDisabledProvider) EvalTree() EvalNode {
|
|
||||||
var resourceConfig *ResourceConfig
|
|
||||||
|
|
||||||
return &EvalOpFilter{
|
|
||||||
Ops: []walkOperation{walkInput, walkValidate, walkRefresh, walkPlan, walkApply, walkDestroy},
|
|
||||||
Node: &EvalSequence{
|
|
||||||
Nodes: []EvalNode{
|
|
||||||
&EvalInterpolate{
|
|
||||||
Config: n.ProviderConfig(),
|
|
||||||
Output: &resourceConfig,
|
|
||||||
},
|
|
||||||
&EvalBuildProviderConfig{
|
|
||||||
Provider: n.ProviderName(),
|
|
||||||
Config: &resourceConfig,
|
|
||||||
Output: &resourceConfig,
|
|
||||||
},
|
|
||||||
&EvalSetProviderConfig{
|
|
||||||
Provider: n.ProviderName(),
|
|
||||||
Config: &resourceConfig,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeFlattenable impl.
|
|
||||||
func (n *graphNodeDisabledProvider) Flatten(p []string) (dag.Vertex, error) {
|
|
||||||
return &graphNodeDisabledProviderFlat{
|
|
||||||
graphNodeDisabledProvider: n,
|
|
||||||
PathValue: p,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeDisabledProvider) Name() string {
|
|
||||||
return fmt.Sprintf("%s (disabled)", dag.VertexName(n.GraphNodeProvider))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeDotter impl.
|
|
||||||
func (n *graphNodeDisabledProvider) DotNode(name string, opts *GraphDotOpts) *dot.Node {
|
|
||||||
return dot.NewNode(name, map[string]string{
|
|
||||||
"label": n.Name(),
|
|
||||||
"shape": "diamond",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeDotterOrigin impl.
|
|
||||||
func (n *graphNodeDisabledProvider) DotOrigin() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeDependable impl.
|
|
||||||
func (n *graphNodeDisabledProvider) DependableName() []string {
|
|
||||||
return []string{"provider." + n.ProviderName()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeProvider impl.
|
|
||||||
func (n *graphNodeDisabledProvider) ProviderName() string {
|
|
||||||
return n.GraphNodeProvider.ProviderName()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeProvider impl.
|
|
||||||
func (n *graphNodeDisabledProvider) ProviderConfig() *config.RawConfig {
|
|
||||||
return n.GraphNodeProvider.ProviderConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same as graphNodeDisabledProvider, but for flattening
|
|
||||||
type graphNodeDisabledProviderFlat struct {
|
|
||||||
*graphNodeDisabledProvider
|
|
||||||
|
|
||||||
PathValue []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeDisabledProviderFlat) Name() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeDisabledProvider.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeDisabledProviderFlat) Path() []string {
|
|
||||||
return n.PathValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeDisabledProviderFlat) ProviderName() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"%s.%s", modulePrefixStr(n.PathValue),
|
|
||||||
n.graphNodeDisabledProvider.ProviderName())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeDependable impl.
|
|
||||||
func (n *graphNodeDisabledProviderFlat) DependableName() []string {
|
|
||||||
return modulePrefixList(
|
|
||||||
n.graphNodeDisabledProvider.DependableName(),
|
|
||||||
modulePrefixStr(n.PathValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeDisabledProviderFlat) DependentOn() []string {
|
|
||||||
var result []string
|
|
||||||
|
|
||||||
// If we're in a module, then depend on our parent's provider
|
|
||||||
if len(n.PathValue) > 1 {
|
|
||||||
prefix := modulePrefixStr(n.PathValue[:len(n.PathValue)-1])
|
|
||||||
result = modulePrefixList(
|
|
||||||
n.graphNodeDisabledProvider.DependableName(), prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
type graphNodeCloseProvider struct {
|
type graphNodeCloseProvider struct {
|
||||||
ProviderNameValue string
|
ProviderNameValue string
|
||||||
}
|
}
|
||||||
|
@ -464,6 +383,7 @@ func (n *graphNodeProvider) DependableName() []string {
|
||||||
return []string{n.Name()}
|
return []string{n.Name()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GraphNodeProvider
|
||||||
func (n *graphNodeProvider) ProviderName() string {
|
func (n *graphNodeProvider) ProviderName() string {
|
||||||
return n.ProviderNameValue
|
return n.ProviderNameValue
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DisableProviderTransformer "disables" any providers that are not actually
|
||||||
|
// used by anything. This avoids the provider being initialized and configured.
|
||||||
|
// This both saves resources but also avoids errors since configuration
|
||||||
|
// may imply initialization which may require auth.
|
||||||
|
type DisableProviderTransformer struct{}
|
||||||
|
|
||||||
|
func (t *DisableProviderTransformer) Transform(g *Graph) error {
|
||||||
|
for _, v := range g.Vertices() {
|
||||||
|
// We only care about providers
|
||||||
|
pn, ok := v.(GraphNodeProvider)
|
||||||
|
if !ok || pn.ProviderName() == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have dependencies, then don't disable
|
||||||
|
if g.UpEdges(v).Len() > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the path
|
||||||
|
var path []string
|
||||||
|
if pn, ok := v.(GraphNodeSubPath); ok {
|
||||||
|
path = pn.Path()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable the provider by replacing it with a "disabled" provider
|
||||||
|
disabled := &NodeDisabledProvider{
|
||||||
|
NodeAbstractProvider: &NodeAbstractProvider{
|
||||||
|
NameValue: pn.ProviderName(),
|
||||||
|
PathValue: path,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !g.Replace(v, disabled) {
|
||||||
|
panic(fmt.Sprintf(
|
||||||
|
"vertex disappeared from under us: %s",
|
||||||
|
dag.VertexName(v)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
"github.com/hashicorp/terraform/dot"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DisableProviderTransformer "disables" any providers that are only
|
||||||
|
// depended on by modules.
|
||||||
|
//
|
||||||
|
// NOTE: "old" = used by old graph builders, will be removed one day
|
||||||
|
type DisableProviderTransformerOld struct{}
|
||||||
|
|
||||||
|
func (t *DisableProviderTransformerOld) Transform(g *Graph) error {
|
||||||
|
// Since we're comparing against edges, we need to make sure we connect
|
||||||
|
g.ConnectDependents()
|
||||||
|
|
||||||
|
for _, v := range g.Vertices() {
|
||||||
|
// We only care about providers
|
||||||
|
pn, ok := v.(GraphNodeProvider)
|
||||||
|
if !ok || pn.ProviderName() == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through all the up-edges (things that depend on this
|
||||||
|
// provider) and if any is not a module, then ignore this node.
|
||||||
|
nonModule := false
|
||||||
|
for _, sourceRaw := range g.UpEdges(v).List() {
|
||||||
|
source := sourceRaw.(dag.Vertex)
|
||||||
|
cn, ok := source.(graphNodeConfig)
|
||||||
|
if !ok {
|
||||||
|
nonModule = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if cn.ConfigType() != GraphNodeConfigTypeModule {
|
||||||
|
nonModule = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nonModule {
|
||||||
|
// We found something that depends on this provider that
|
||||||
|
// isn't a module, so skip it.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable the provider by replacing it with a "disabled" provider
|
||||||
|
disabled := &graphNodeDisabledProvider{GraphNodeProvider: pn}
|
||||||
|
if !g.Replace(v, disabled) {
|
||||||
|
panic(fmt.Sprintf(
|
||||||
|
"vertex disappeared from under us: %s",
|
||||||
|
dag.VertexName(v)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type graphNodeDisabledProvider struct {
|
||||||
|
GraphNodeProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable impl.
|
||||||
|
func (n *graphNodeDisabledProvider) EvalTree() EvalNode {
|
||||||
|
var resourceConfig *ResourceConfig
|
||||||
|
|
||||||
|
return &EvalOpFilter{
|
||||||
|
Ops: []walkOperation{walkInput, walkValidate, walkRefresh, walkPlan, walkApply, walkDestroy},
|
||||||
|
Node: &EvalSequence{
|
||||||
|
Nodes: []EvalNode{
|
||||||
|
&EvalInterpolate{
|
||||||
|
Config: n.ProviderConfig(),
|
||||||
|
Output: &resourceConfig,
|
||||||
|
},
|
||||||
|
&EvalBuildProviderConfig{
|
||||||
|
Provider: n.ProviderName(),
|
||||||
|
Config: &resourceConfig,
|
||||||
|
Output: &resourceConfig,
|
||||||
|
},
|
||||||
|
&EvalSetProviderConfig{
|
||||||
|
Provider: n.ProviderName(),
|
||||||
|
Config: &resourceConfig,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeFlattenable impl.
|
||||||
|
func (n *graphNodeDisabledProvider) Flatten(p []string) (dag.Vertex, error) {
|
||||||
|
return &graphNodeDisabledProviderFlat{
|
||||||
|
graphNodeDisabledProvider: n,
|
||||||
|
PathValue: p,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeDisabledProvider) Name() string {
|
||||||
|
return fmt.Sprintf("%s (disabled)", dag.VertexName(n.GraphNodeProvider))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeDotter impl.
|
||||||
|
func (n *graphNodeDisabledProvider) DotNode(name string, opts *GraphDotOpts) *dot.Node {
|
||||||
|
return dot.NewNode(name, map[string]string{
|
||||||
|
"label": n.Name(),
|
||||||
|
"shape": "diamond",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeDotterOrigin impl.
|
||||||
|
func (n *graphNodeDisabledProvider) DotOrigin() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeDependable impl.
|
||||||
|
func (n *graphNodeDisabledProvider) DependableName() []string {
|
||||||
|
return []string{"provider." + n.ProviderName()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeProvider impl.
|
||||||
|
func (n *graphNodeDisabledProvider) ProviderName() string {
|
||||||
|
return n.GraphNodeProvider.ProviderName()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeProvider impl.
|
||||||
|
func (n *graphNodeDisabledProvider) ProviderConfig() *config.RawConfig {
|
||||||
|
return n.GraphNodeProvider.ProviderConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as graphNodeDisabledProvider, but for flattening
|
||||||
|
type graphNodeDisabledProviderFlat struct {
|
||||||
|
*graphNodeDisabledProvider
|
||||||
|
|
||||||
|
PathValue []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeDisabledProviderFlat) Name() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeDisabledProvider.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeDisabledProviderFlat) Path() []string {
|
||||||
|
return n.PathValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeDisabledProviderFlat) ProviderName() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s.%s", modulePrefixStr(n.PathValue),
|
||||||
|
n.graphNodeDisabledProvider.ProviderName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeDependable impl.
|
||||||
|
func (n *graphNodeDisabledProviderFlat) DependableName() []string {
|
||||||
|
return modulePrefixList(
|
||||||
|
n.graphNodeDisabledProvider.DependableName(),
|
||||||
|
modulePrefixStr(n.PathValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeDisabledProviderFlat) DependentOn() []string {
|
||||||
|
var result []string
|
||||||
|
|
||||||
|
// If we're in a module, then depend on our parent's provider
|
||||||
|
if len(n.PathValue) > 1 {
|
||||||
|
prefix := modulePrefixStr(n.PathValue[:len(n.PathValue)-1])
|
||||||
|
result = modulePrefixList(
|
||||||
|
n.graphNodeDisabledProvider.DependableName(), prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
|
@ -230,6 +230,89 @@ func TestMissingProviderTransformer_moduleGrandchild(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParentProviderTransformer(t *testing.T) {
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
|
||||||
|
// Introduce a cihld module
|
||||||
|
{
|
||||||
|
tf := &ImportStateTransformer{
|
||||||
|
Targets: []*ImportTarget{
|
||||||
|
&ImportTarget{
|
||||||
|
Addr: "module.moo.foo_instance.qux",
|
||||||
|
ID: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the missing modules
|
||||||
|
{
|
||||||
|
tf := &MissingProviderTransformer{Providers: []string{"foo", "bar"}}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect parents
|
||||||
|
{
|
||||||
|
tf := &ParentProviderTransformer{}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(testTransformParentProviderStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParentProviderTransformer_moduleGrandchild(t *testing.T) {
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
|
||||||
|
// We use the import state transformer since at the time of writing
|
||||||
|
// this test it is the first and only transformer that will introduce
|
||||||
|
// multiple module-path nodes at a single go.
|
||||||
|
{
|
||||||
|
tf := &ImportStateTransformer{
|
||||||
|
Targets: []*ImportTarget{
|
||||||
|
&ImportTarget{
|
||||||
|
Addr: "module.a.module.b.foo_instance.qux",
|
||||||
|
ID: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
tf := &MissingProviderTransformer{Providers: []string{"foo", "bar"}}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect parents
|
||||||
|
{
|
||||||
|
tf := &ParentProviderTransformer{}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(testTransformParentProviderModuleGrandchildStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPruneProviderTransformer(t *testing.T) {
|
func TestPruneProviderTransformer(t *testing.T) {
|
||||||
mod := testModule(t, "transform-provider-prune")
|
mod := testModule(t, "transform-provider-prune")
|
||||||
|
|
||||||
|
@ -284,7 +367,7 @@ func TestDisableProviderTransformer(t *testing.T) {
|
||||||
&ConfigTransformer{Module: mod},
|
&ConfigTransformer{Module: mod},
|
||||||
&MissingProviderTransformer{Providers: []string{"aws"}},
|
&MissingProviderTransformer{Providers: []string{"aws"}},
|
||||||
&ProviderTransformer{},
|
&ProviderTransformer{},
|
||||||
&DisableProviderTransformer{},
|
&DisableProviderTransformerOld{},
|
||||||
&CloseProviderTransformer{},
|
&CloseProviderTransformer{},
|
||||||
&PruneProviderTransformer{},
|
&PruneProviderTransformer{},
|
||||||
}
|
}
|
||||||
|
@ -310,7 +393,7 @@ func TestDisableProviderTransformer_keep(t *testing.T) {
|
||||||
&ConfigTransformer{Module: mod},
|
&ConfigTransformer{Module: mod},
|
||||||
&MissingProviderTransformer{Providers: []string{"aws"}},
|
&MissingProviderTransformer{Providers: []string{"aws"}},
|
||||||
&ProviderTransformer{},
|
&ProviderTransformer{},
|
||||||
&DisableProviderTransformer{},
|
&DisableProviderTransformerOld{},
|
||||||
&CloseProviderTransformer{},
|
&CloseProviderTransformer{},
|
||||||
&PruneProviderTransformer{},
|
&PruneProviderTransformer{},
|
||||||
}
|
}
|
||||||
|
@ -382,6 +465,22 @@ module.a.provider.foo
|
||||||
provider.foo
|
provider.foo
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const testTransformParentProviderStr = `
|
||||||
|
module.moo.foo_instance.qux (import id: bar)
|
||||||
|
module.moo.provider.foo
|
||||||
|
provider.foo
|
||||||
|
provider.foo
|
||||||
|
`
|
||||||
|
|
||||||
|
const testTransformParentProviderModuleGrandchildStr = `
|
||||||
|
module.a.module.b.foo_instance.qux (import id: bar)
|
||||||
|
module.a.module.b.provider.foo
|
||||||
|
module.a.provider.foo
|
||||||
|
module.a.provider.foo
|
||||||
|
provider.foo
|
||||||
|
provider.foo
|
||||||
|
`
|
||||||
|
|
||||||
const testTransformProviderModuleChildStr = `
|
const testTransformProviderModuleChildStr = `
|
||||||
module.moo.foo_instance.qux (import id: bar)
|
module.moo.foo_instance.qux (import id: bar)
|
||||||
module.moo.provider.foo
|
module.moo.provider.foo
|
||||||
|
|
|
@ -0,0 +1,180 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GraphNodeReferenceable must be implemented by any node that represents
|
||||||
|
// a Terraform thing that can be referenced (resource, module, etc.).
|
||||||
|
type GraphNodeReferenceable interface {
|
||||||
|
// ReferenceableName is the name by which this can be referenced.
|
||||||
|
// This can be either just the type, or include the field. Example:
|
||||||
|
// "aws_instance.bar" or "aws_instance.bar.id".
|
||||||
|
ReferenceableName() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeReferencer must be implemented by nodes that reference other
|
||||||
|
// Terraform items and therefore depend on them.
|
||||||
|
type GraphNodeReferencer interface {
|
||||||
|
// References are the list of things that this node references. This
|
||||||
|
// can include fields or just the type, just like GraphNodeReferenceable
|
||||||
|
// above.
|
||||||
|
References() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeReferenceGlobal is an interface that can optionally be
|
||||||
|
// implemented. If ReferenceGlobal returns true, then the References()
|
||||||
|
// and ReferenceableName() must be _fully qualified_ with "module.foo.bar"
|
||||||
|
// etc.
|
||||||
|
//
|
||||||
|
// This allows a node to reference and be referenced by a specific name
|
||||||
|
// that may cross module boundaries. This can be very dangerous so use
|
||||||
|
// this wisely.
|
||||||
|
//
|
||||||
|
// The primary use case for this is module boundaries (variables coming in).
|
||||||
|
type GraphNodeReferenceGlobal interface {
|
||||||
|
// Set to true to signal that references and name are fully
|
||||||
|
// qualified. See the above docs for more information.
|
||||||
|
ReferenceGlobal() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReferenceTransformer is a GraphTransformer that connects all the
|
||||||
|
// nodes that reference each other in order to form the proper ordering.
|
||||||
|
type ReferenceTransformer struct{}
|
||||||
|
|
||||||
|
func (t *ReferenceTransformer) Transform(g *Graph) error {
|
||||||
|
// Build a reference map so we can efficiently look up the references
|
||||||
|
vs := g.Vertices()
|
||||||
|
m := NewReferenceMap(vs)
|
||||||
|
|
||||||
|
// Find the things that reference things and connect them
|
||||||
|
for _, v := range vs {
|
||||||
|
parents, _ := m.References(v)
|
||||||
|
for _, parent := range parents {
|
||||||
|
g.Connect(dag.BasicEdge(v, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReferenceMap is a structure that can be used to efficiently check
|
||||||
|
// for references on a graph.
|
||||||
|
type ReferenceMap struct {
|
||||||
|
// m is the mapping of referenceable name to list of verticies that
|
||||||
|
// implement that name. This is built on initialization.
|
||||||
|
m map[string][]dag.Vertex
|
||||||
|
}
|
||||||
|
|
||||||
|
// References returns the list of vertices that this vertex
|
||||||
|
// references along with any missing references.
|
||||||
|
func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []string) {
|
||||||
|
rn, ok := v.(GraphNodeReferencer)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var matches []dag.Vertex
|
||||||
|
var missing []string
|
||||||
|
prefix := m.prefix(v)
|
||||||
|
for _, n := range rn.References() {
|
||||||
|
n = prefix + n
|
||||||
|
parents, ok := m.m[n]
|
||||||
|
if !ok {
|
||||||
|
missing = append(missing, n)
|
||||||
|
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 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
matches = append(matches, parents...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches, missing
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ReferenceMap) prefix(v dag.Vertex) string {
|
||||||
|
// If the node is stating it is already fully qualified then
|
||||||
|
// we don't have to create the prefix!
|
||||||
|
if gn, ok := v.(GraphNodeReferenceGlobal); ok && gn.ReferenceGlobal() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the prefix based on the path
|
||||||
|
var prefix string
|
||||||
|
if pn, ok := v.(GraphNodeSubPath); ok {
|
||||||
|
if path := normalizeModulePath(pn.Path()); len(path) > 1 {
|
||||||
|
prefix = modulePrefixStr(path) + "."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReferenceMap is used to create a new reference map for the
|
||||||
|
// given set of vertices.
|
||||||
|
func NewReferenceMap(vs []dag.Vertex) *ReferenceMap {
|
||||||
|
var m ReferenceMap
|
||||||
|
|
||||||
|
// Build the lookup table
|
||||||
|
refMap := make(map[string][]dag.Vertex)
|
||||||
|
for _, v := range vs {
|
||||||
|
// We're only looking for referenceable nodes
|
||||||
|
rn, ok := v.(GraphNodeReferenceable)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through and cache them
|
||||||
|
prefix := m.prefix(v)
|
||||||
|
for _, n := range rn.ReferenceableName() {
|
||||||
|
n = prefix + n
|
||||||
|
refMap[n] = append(refMap[n], v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.m = refMap
|
||||||
|
return &m
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReferencesFromConfig returns the references that a configuration has
|
||||||
|
// based on the interpolated variables in a configuration.
|
||||||
|
func ReferencesFromConfig(c *config.RawConfig) []string {
|
||||||
|
var result []string
|
||||||
|
for _, v := range c.Variables {
|
||||||
|
if r := ReferenceFromInterpolatedVar(v); r != "" {
|
||||||
|
result = append(result, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReferenceFromInterpolatedVar returns the reference from this variable,
|
||||||
|
// or an empty string if there is no reference.
|
||||||
|
func ReferenceFromInterpolatedVar(v config.InterpolatedVariable) string {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case *config.ModuleVariable:
|
||||||
|
return fmt.Sprintf("module.%s.output.%s", v.Name, v.Field)
|
||||||
|
case *config.ResourceVariable:
|
||||||
|
return v.ResourceId()
|
||||||
|
case *config.UserVariable:
|
||||||
|
return fmt.Sprintf("var.%s", v.Name)
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReferenceTransformer_simple(t *testing.T) {
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
g.Add(&graphNodeRefParentTest{
|
||||||
|
NameValue: "A",
|
||||||
|
Names: []string{"A"},
|
||||||
|
})
|
||||||
|
g.Add(&graphNodeRefChildTest{
|
||||||
|
NameValue: "B",
|
||||||
|
Refs: []string{"A"},
|
||||||
|
})
|
||||||
|
|
||||||
|
tf := &ReferenceTransformer{}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(testTransformRefBasicStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReferenceTransformer_self(t *testing.T) {
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
g.Add(&graphNodeRefParentTest{
|
||||||
|
NameValue: "A",
|
||||||
|
Names: []string{"A"},
|
||||||
|
})
|
||||||
|
g.Add(&graphNodeRefChildTest{
|
||||||
|
NameValue: "B",
|
||||||
|
Refs: []string{"A", "B"},
|
||||||
|
})
|
||||||
|
|
||||||
|
tf := &ReferenceTransformer{}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(testTransformRefBasicStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReferenceTransformer_path(t *testing.T) {
|
||||||
|
g := Graph{Path: RootModulePath}
|
||||||
|
g.Add(&graphNodeRefParentTest{
|
||||||
|
NameValue: "A",
|
||||||
|
Names: []string{"A"},
|
||||||
|
})
|
||||||
|
g.Add(&graphNodeRefChildTest{
|
||||||
|
NameValue: "B",
|
||||||
|
Refs: []string{"A"},
|
||||||
|
})
|
||||||
|
g.Add(&graphNodeRefParentTest{
|
||||||
|
NameValue: "child.A",
|
||||||
|
PathValue: []string{"root", "child"},
|
||||||
|
Names: []string{"A"},
|
||||||
|
})
|
||||||
|
g.Add(&graphNodeRefChildTest{
|
||||||
|
NameValue: "child.B",
|
||||||
|
PathValue: []string{"root", "child"},
|
||||||
|
Refs: []string{"A"},
|
||||||
|
})
|
||||||
|
|
||||||
|
tf := &ReferenceTransformer{}
|
||||||
|
if err := tf.Transform(&g); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(testTransformRefPathStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type graphNodeRefParentTest struct {
|
||||||
|
NameValue string
|
||||||
|
PathValue []string
|
||||||
|
Names []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeRefParentTest) Name() string { return n.NameValue }
|
||||||
|
func (n *graphNodeRefParentTest) ReferenceableName() []string { return n.Names }
|
||||||
|
func (n *graphNodeRefParentTest) Path() []string { return n.PathValue }
|
||||||
|
|
||||||
|
type graphNodeRefChildTest struct {
|
||||||
|
NameValue string
|
||||||
|
PathValue []string
|
||||||
|
Refs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeRefChildTest) Name() string { return n.NameValue }
|
||||||
|
func (n *graphNodeRefChildTest) References() []string { return n.Refs }
|
||||||
|
func (n *graphNodeRefChildTest) Path() []string { return n.PathValue }
|
||||||
|
|
||||||
|
const testTransformRefBasicStr = `
|
||||||
|
A
|
||||||
|
B
|
||||||
|
A
|
||||||
|
`
|
||||||
|
|
||||||
|
const testTransformRefPathStr = `
|
||||||
|
A
|
||||||
|
B
|
||||||
|
A
|
||||||
|
child.A
|
||||||
|
child.B
|
||||||
|
child.A
|
||||||
|
`
|
|
@ -0,0 +1,40 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RootVariableTransformer is a GraphTransformer that adds all the root
|
||||||
|
// variables to the graph.
|
||||||
|
//
|
||||||
|
// Root variables are currently no-ops but they must be added to the
|
||||||
|
// graph since downstream things that depend on them must be able to
|
||||||
|
// reach them.
|
||||||
|
type RootVariableTransformer struct {
|
||||||
|
Module *module.Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *RootVariableTransformer) Transform(g *Graph) error {
|
||||||
|
// If no config, no variables
|
||||||
|
if t.Module == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have no vars, we're done!
|
||||||
|
vars := t.Module.Config().Variables
|
||||||
|
if len(vars) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all variables here
|
||||||
|
for _, v := range vars {
|
||||||
|
node := &NodeRootVariable{
|
||||||
|
Config: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add it!
|
||||||
|
g.Add(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue