terraform: graph tainted resources into the graph
This commit is contained in:
parent
5e0765c24a
commit
f89c2c5ff0
|
@ -106,15 +106,19 @@ func Graph(opts *GraphOpts) (*depgraph.Graph, error) {
|
|||
g := new(depgraph.Graph)
|
||||
|
||||
// First, build the initial resource graph. This only has the resources
|
||||
// and no dependencies.
|
||||
// and no dependencies. This only adds resources that are in the config
|
||||
// and not "orphans" (that are in the state, but not in the config).
|
||||
graphAddConfigResources(g, opts.Config, opts.State)
|
||||
|
||||
// Add explicit dependsOn dependencies to the graph
|
||||
graphAddExplicitDeps(g)
|
||||
|
||||
// Next, add the state orphans if we have any
|
||||
if opts.State != nil {
|
||||
// Next, add the state orphans if we have any
|
||||
graphAddOrphans(g, opts.Config, opts.State)
|
||||
|
||||
// Add tainted resources if we have any.
|
||||
graphAddTainted(g, opts.State)
|
||||
}
|
||||
|
||||
// Map the provider configurations to all of the resources
|
||||
|
@ -750,14 +754,11 @@ func graphAddVariableDeps(g *depgraph.Graph) {
|
|||
var vars map[string]config.InterpolatedVariable
|
||||
switch m := n.Meta.(type) {
|
||||
case *GraphNodeResource:
|
||||
// Ignore orphan nodes
|
||||
if m.Orphan {
|
||||
continue
|
||||
}
|
||||
|
||||
if m.Config != nil {
|
||||
// Handle the resource variables
|
||||
vars = m.Config.RawConfig.Variables
|
||||
nounAddVariableDeps(g, n, vars, false)
|
||||
}
|
||||
|
||||
// Handle the variables of the resource provisioners
|
||||
for _, p := range m.Resource.Provisioners {
|
||||
|
@ -778,6 +779,73 @@ func graphAddVariableDeps(g *depgraph.Graph) {
|
|||
}
|
||||
}
|
||||
|
||||
// graphAddTainted adds the tainted instances to the graph.
|
||||
func graphAddTainted(g *depgraph.Graph, s *State) {
|
||||
// TODO: Handle other modules
|
||||
mod := s.ModuleByPath(rootModulePath)
|
||||
if mod == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var nlist []*depgraph.Noun
|
||||
for k, rs := range mod.Resources {
|
||||
// If we have no tainted resources, continue on
|
||||
if len(rs.Tainted) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the untainted resource of this in the noun list
|
||||
var untainted *depgraph.Noun
|
||||
for _, n := range g.Nouns {
|
||||
if n.Name == k {
|
||||
untainted = n
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for i, _ := range rs.Tainted {
|
||||
name := fmt.Sprintf("%s (tainted #%d)", k, i+1)
|
||||
|
||||
// Add each of the tainted resources to the graph, and encode
|
||||
// a dependency from the non-tainted resource to this so that
|
||||
// tainted resources are always destroyed first.
|
||||
noun := &depgraph.Noun{
|
||||
Name: name,
|
||||
Meta: &GraphNodeResource{
|
||||
Index: -1,
|
||||
Type: rs.Type,
|
||||
Resource: &Resource{
|
||||
Id: k,
|
||||
State: rs,
|
||||
Config: NewResourceConfig(nil),
|
||||
Diff: &InstanceDiff{Destroy: true},
|
||||
Tainted: true,
|
||||
TaintedIndex: i,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Append it to the list so we handle it later
|
||||
nlist = append(nlist, noun)
|
||||
|
||||
// If we have an untainted version, then make sure to add
|
||||
// the dependency.
|
||||
if untainted != nil {
|
||||
dep := &depgraph.Dependency{
|
||||
Name: name,
|
||||
Source: untainted,
|
||||
Target: noun,
|
||||
}
|
||||
|
||||
untainted.Deps = append(untainted.Deps, dep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the nouns to the graph
|
||||
g.Nouns = append(g.Nouns, nlist...)
|
||||
}
|
||||
|
||||
// nounAddVariableDeps updates the dependencies of a noun given
|
||||
// a set of associated variable values
|
||||
func nounAddVariableDeps(
|
||||
|
|
|
@ -112,6 +112,81 @@ func TestGraph_state(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGraph_tainted(t *testing.T) {
|
||||
config := testConfig(t, "graph-tainted")
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
Tainted: []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g, err := Graph(&GraphOpts{Config: config, State: state})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTerraformGraphTaintedStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGraph_taintedMulti(t *testing.T) {
|
||||
config := testConfig(t, "graph-tainted")
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
Tainted: []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "bar",
|
||||
},
|
||||
&InstanceState{
|
||||
ID: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g, err := Graph(&GraphOpts{Config: config, State: state})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTerraformGraphTaintedMultiStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGraphFull(t *testing.T) {
|
||||
rpAws := new(MockResourceProvider)
|
||||
rpOS := new(MockResourceProvider)
|
||||
|
@ -715,6 +790,44 @@ root
|
|||
root -> openstack_floating_ip.random
|
||||
`
|
||||
|
||||
const testTerraformGraphTaintedStr = `
|
||||
root: root
|
||||
aws_instance.web
|
||||
aws_instance.web -> aws_instance.web (tainted #1)
|
||||
aws_instance.web -> aws_security_group.firewall
|
||||
aws_instance.web -> provider.aws
|
||||
aws_instance.web (tainted #1)
|
||||
aws_instance.web (tainted #1) -> provider.aws
|
||||
aws_security_group.firewall
|
||||
aws_security_group.firewall -> provider.aws
|
||||
provider.aws
|
||||
root
|
||||
root -> aws_instance.web
|
||||
root -> aws_instance.web (tainted #1)
|
||||
root -> aws_security_group.firewall
|
||||
`
|
||||
|
||||
const testTerraformGraphTaintedMultiStr = `
|
||||
root: root
|
||||
aws_instance.web
|
||||
aws_instance.web -> aws_instance.web (tainted #1)
|
||||
aws_instance.web -> aws_instance.web (tainted #2)
|
||||
aws_instance.web -> aws_security_group.firewall
|
||||
aws_instance.web -> provider.aws
|
||||
aws_instance.web (tainted #1)
|
||||
aws_instance.web (tainted #1) -> provider.aws
|
||||
aws_instance.web (tainted #2)
|
||||
aws_instance.web (tainted #2) -> provider.aws
|
||||
aws_security_group.firewall
|
||||
aws_security_group.firewall -> provider.aws
|
||||
provider.aws
|
||||
root
|
||||
root -> aws_instance.web
|
||||
root -> aws_instance.web (tainted #1)
|
||||
root -> aws_instance.web (tainted #2)
|
||||
root -> aws_security_group.firewall
|
||||
`
|
||||
|
||||
const testTerraformGraphCountOrphanStr = `
|
||||
root: root
|
||||
aws_instance.web
|
||||
|
|
|
@ -33,6 +33,7 @@ type Resource struct {
|
|||
State *ResourceState
|
||||
Provisioners []*ResourceProvisionerConfig
|
||||
Tainted bool
|
||||
TaintedIndex int
|
||||
}
|
||||
|
||||
// Vars returns the mapping of variables that should be replaced in
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
variable "foo" {
|
||||
default = "bar"
|
||||
description = "bar"
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
foo = "${openstack_floating_ip.random.value}"
|
||||
}
|
||||
|
||||
resource "aws_security_group" "firewall" {}
|
||||
|
||||
resource "aws_instance" "web" {
|
||||
ami = "${var.foo}"
|
||||
security_groups = [
|
||||
"foo",
|
||||
"${aws_security_group.firewall.foo}"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue