Skip to content

Commit

Permalink
Closes hashicorp#1768 - Implements taint with wildcard
Browse files Browse the repository at this point in the history
This implementation of wildcard functionality enables the following:

Given resources:
- test_instance.foo.a
- test_instance.foo.b
- test_instance.bar.a

Matches:

`test_instance.*`
- test_instance.foo.a
- test_instance.foo.b
- test_instance.bar.a

`test_instance.*.a`
- test_instance.foo.a
- test_instance.bar.a

`test_instance.foo.*`
- test_instance.foo.a
- test_instance.foo.b

`test_instance.foo*`
- Doesn't match anything
  • Loading branch information
Arthur Burkart committed Nov 20, 2016
1 parent 3920460 commit 8d85a09
Show file tree
Hide file tree
Showing 3 changed files with 291 additions and 1 deletion.
62 changes: 62 additions & 0 deletions command/taint.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,67 @@ func (c *TaintCommand) Run(args []string) int {
return 1
}

// Taint resources using wildcard
if strings.Contains(name, ".*") {
nameParts := strings.Split(name, ".")
tainted := []string{}

for k, _ := range mod.Resources {
resourceParts := strings.Split(k, ".")
// Verify it's possible for name to match resource
if len(resourceParts) < len(nameParts) {
continue
}
if len(resourceParts) > len(nameParts) && !strings.HasSuffix(name, ".*") {
continue
}

// Verify whether resource paths match
isMatch := true
for i, p := range nameParts {
if p != resourceParts[i] && p != "*" {
isMatch = false
break
}
}

// Mark the resource for tainting if it matches
if isMatch {
rs, _ := mod.Resources[k]
rs.Taint()
tainted = append(tainted, k)
}
}

// Check whether it's okay if no resources were tainted
if len(tainted) == 0 {
if allowMissing {
return c.allowMissingExit(name, module)
}

c.Ui.Error(fmt.Sprintf(
"No resources matching pattern %s could be found in the module %s.",
name,
module))
return 1
}

// Attempt to write new state with tainted resources
log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
if err := c.Meta.PersistState(s); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
return 1
}

// Give notice that resources were successfully tainted
for _, k := range tainted {
c.Ui.Output(fmt.Sprintf(
"The resource %s in the module %s has been marked as tainted!",
k, module))
}
return 0
}

// Get the resource we're looking for
rs, ok := mod.Resources[name]
if !ok {
Expand All @@ -121,6 +182,7 @@ func (c *TaintCommand) Run(args []string) int {
// Taint the resource
rs.Taint()

// Attempt to write new state with tainted resource
log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
if err := c.Meta.PersistState(s); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
Expand Down
228 changes: 228 additions & 0 deletions command/taint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,198 @@ func TestTaint(t *testing.T) {
testStateOutput(t, statePath, testTaintStr)
}

func TestTaint_badwildcard(t *testing.T) {
state := &terraform.State{
Modules: []*terraform.ModuleState{
&terraform.ModuleState{
Path: []string{"root"},
Resources: map[string]*terraform.ResourceState{
"test_instance.foo.1": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo1",
},
},
"test_instance.foo.2": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo2",
},
},
"test_instance.bar": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "bar",
},
},
},
},
},
}
statePath := testStateFile(t, state)

ui := new(cli.MockUi)
c := &TaintCommand{
Meta: Meta{
Ui: ui,
},
}

args := []string{
"-state", statePath,
"test_instance*",
}
if code := c.Run(args); code == 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}

testStateOutput(t, statePath, testTaintStrBadWildcard)
}

func TestTaint_wildcard1(t *testing.T) {
state := &terraform.State{
Modules: []*terraform.ModuleState{
&terraform.ModuleState{
Path: []string{"root"},
Resources: map[string]*terraform.ResourceState{
"test_instance.foo.1": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo1",
},
},
"test_instance.foo.2": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo2",
},
},
"test_instance.bar": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "bar",
},
},
},
},
},
}
statePath := testStateFile(t, state)

ui := new(cli.MockUi)
c := &TaintCommand{
Meta: Meta{
Ui: ui,
},
}

args := []string{
"-state", statePath,
"test_instance.*",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}

testStateOutput(t, statePath, testTaintStrWildcard1)
}

func TestTaint_wildcard2(t *testing.T) {
state := &terraform.State{
Modules: []*terraform.ModuleState{
&terraform.ModuleState{
Path: []string{"root"},
Resources: map[string]*terraform.ResourceState{
"test_instance.foo.1": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo1",
},
},
"test_instance.foo.2": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo2",
},
},
"test_instance.bar": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "bar",
},
},
},
},
},
}
statePath := testStateFile(t, state)

ui := new(cli.MockUi)
c := &TaintCommand{
Meta: Meta{
Ui: ui,
},
}

args := []string{
"-state", statePath,
"test_instance.foo.*",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}

testStateOutput(t, statePath, testTaintStrWildcard2)
}

func TestTaint_wildcard3(t *testing.T) {
state := &terraform.State{
Modules: []*terraform.ModuleState{
&terraform.ModuleState{
Path: []string{"root"},
Resources: map[string]*terraform.ResourceState{
"test_instance.foo.1": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo1",
},
},
"test_instance.foo.2": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo2",
},
},
"test_instance.bar.1": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "bar1",
},
},
},
},
},
}
statePath := testStateFile(t, state)

ui := new(cli.MockUi)
c := &TaintCommand{
Meta: Meta{
Ui: ui,
},
}

args := []string{
"-state", statePath,
"test_instance.*.1",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}

testStateOutput(t, statePath, testTaintStrWildcard3)
}

func TestTaint_backup(t *testing.T) {
// Get a temp cwd
tmp, cwd := testCwd(t)
Expand Down Expand Up @@ -351,6 +543,42 @@ test_instance.foo: (tainted)
ID = bar
`

const testTaintStrBadWildcard = `
test_instance.bar:
ID = bar
test_instance.foo.1:
ID = foo1
test_instance.foo.2:
ID = foo2
`

const testTaintStrWildcard1 = `
test_instance.bar: (tainted)
ID = bar
test_instance.foo.1: (tainted)
ID = foo1
test_instance.foo.2: (tainted)
ID = foo2
`

const testTaintStrWildcard2 = `
test_instance.bar:
ID = bar
test_instance.foo.1: (tainted)
ID = foo1
test_instance.foo.2: (tainted)
ID = foo2
`

const testTaintStrWildcard3 = `
test_instance.bar.1: (tainted)
ID = bar1
test_instance.foo.1: (tainted)
ID = foo1
test_instance.foo.2:
ID = foo2
`

const testTaintDefaultStr = `
test_instance.foo:
ID = bar
Expand Down
2 changes: 1 addition & 1 deletion terraform/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -1279,7 +1279,7 @@ func ParseResourceStateKey(k string) (*ResourceStateKey, error) {
Name: parts[1],
Index: -1,
}
if len(parts) == 3 {
if len(parts) == 3 && parts[2] != "*" {
index, err := strconv.Atoi(parts[2])
if err != nil {
return nil, fmt.Errorf("Malformed resource state key index: %s", k)
Expand Down

0 comments on commit 8d85a09

Please sign in to comment.