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)
|
g := new(depgraph.Graph)
|
||||||
|
|
||||||
// First, build the initial resource graph. This only has the resources
|
// 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)
|
graphAddConfigResources(g, opts.Config, opts.State)
|
||||||
|
|
||||||
// Add explicit dependsOn dependencies to the graph
|
// Add explicit dependsOn dependencies to the graph
|
||||||
graphAddExplicitDeps(g)
|
graphAddExplicitDeps(g)
|
||||||
|
|
||||||
// Next, add the state orphans if we have any
|
|
||||||
if opts.State != nil {
|
if opts.State != nil {
|
||||||
|
// Next, add the state orphans if we have any
|
||||||
graphAddOrphans(g, opts.Config, opts.State)
|
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
|
// Map the provider configurations to all of the resources
|
||||||
|
@ -750,14 +754,11 @@ func graphAddVariableDeps(g *depgraph.Graph) {
|
||||||
var vars map[string]config.InterpolatedVariable
|
var vars map[string]config.InterpolatedVariable
|
||||||
switch m := n.Meta.(type) {
|
switch m := n.Meta.(type) {
|
||||||
case *GraphNodeResource:
|
case *GraphNodeResource:
|
||||||
// Ignore orphan nodes
|
if m.Config != nil {
|
||||||
if m.Orphan {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the resource variables
|
// Handle the resource variables
|
||||||
vars = m.Config.RawConfig.Variables
|
vars = m.Config.RawConfig.Variables
|
||||||
nounAddVariableDeps(g, n, vars, false)
|
nounAddVariableDeps(g, n, vars, false)
|
||||||
|
}
|
||||||
|
|
||||||
// Handle the variables of the resource provisioners
|
// Handle the variables of the resource provisioners
|
||||||
for _, p := range m.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
|
// nounAddVariableDeps updates the dependencies of a noun given
|
||||||
// a set of associated variable values
|
// a set of associated variable values
|
||||||
func nounAddVariableDeps(
|
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) {
|
func TestGraphFull(t *testing.T) {
|
||||||
rpAws := new(MockResourceProvider)
|
rpAws := new(MockResourceProvider)
|
||||||
rpOS := new(MockResourceProvider)
|
rpOS := new(MockResourceProvider)
|
||||||
|
@ -715,6 +790,44 @@ root
|
||||||
root -> openstack_floating_ip.random
|
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 = `
|
const testTerraformGraphCountOrphanStr = `
|
||||||
root: root
|
root: root
|
||||||
aws_instance.web
|
aws_instance.web
|
||||||
|
|
|
@ -33,6 +33,7 @@ type Resource struct {
|
||||||
State *ResourceState
|
State *ResourceState
|
||||||
Provisioners []*ResourceProvisionerConfig
|
Provisioners []*ResourceProvisionerConfig
|
||||||
Tainted bool
|
Tainted bool
|
||||||
|
TaintedIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vars returns the mapping of variables that should be replaced in
|
// 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