Skip to content

Commit

Permalink
Adding ignore_changes lifecycle meta property
Browse files Browse the repository at this point in the history
  • Loading branch information
robzienert committed Aug 9, 2015
1 parent d4f67bf commit cba29f2
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 2 deletions.
5 changes: 3 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ type Resource struct {
// ResourceLifecycle is used to store the lifecycle tuning parameters
// to allow customized behavior
type ResourceLifecycle struct {
CreateBeforeDestroy bool `mapstructure:"create_before_destroy"`
PreventDestroy bool `mapstructure:"prevent_destroy"`
CreateBeforeDestroy bool `mapstructure:"create_before_destroy"`
PreventDestroy bool `mapstructure:"prevent_destroy"`
IgnoreChanges []string `mapstructure:"ignore_changes"`
}

// Provisioner is a configured provisioner step on a resource.
Expand Down
57 changes: 57 additions & 0 deletions config/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,54 @@ func TestLoadFile_createBeforeDestroy(t *testing.T) {
}
}

func TestLoadFile_ignoreChanges(t *testing.T) {
c, err := LoadFile(filepath.Join(fixtureDir, "ignore-changes.tf"))
if err != nil {
t.Fatalf("err: %s", err)
}

if c == nil {
t.Fatal("config should not be nil")
}

actual := resourcesStr(c.Resources)
print(actual)
if actual != strings.TrimSpace(ignoreChangesResourcesStr) {
t.Fatalf("bad:\n%s", actual)
}

// Check for the flag value
r := c.Resources[0]
if r.Name != "web" && r.Type != "aws_instance" {
t.Fatalf("Bad: %#v", r)
}

// Should populate ignore changes
if len(r.Lifecycle.IgnoreChanges) == 0 {
t.Fatalf("Bad: %#v", r)
}

r = c.Resources[1]
if r.Name != "bar" && r.Type != "aws_instance" {
t.Fatalf("Bad: %#v", r)
}

// Should not populate ignore changes
if len(r.Lifecycle.IgnoreChanges) > 0 {
t.Fatalf("Bad: %#v", r)
}

r = c.Resources[2]
if r.Name != "baz" && r.Type != "aws_instance" {
t.Fatalf("Bad: %#v", r)
}

// Should not populate ignore changes
if len(r.Lifecycle.IgnoreChanges) > 0 {
t.Fatalf("Bad: %#v", r)
}
}

func TestLoad_preventDestroyString(t *testing.T) {
c, err := LoadFile(filepath.Join(fixtureDir, "prevent-destroy-string.tf"))
if err != nil {
Expand Down Expand Up @@ -676,3 +724,12 @@ aws_instance[bar] (x1)
aws_instance[web] (x1)
ami
`

const ignoreChangesResourcesStr = `
aws_instance[bar] (x1)
ami
aws_instance[baz] (x1)
ami
aws_instance[web] (x1)
ami
`
17 changes: 17 additions & 0 deletions config/test-fixtures/ignore-changes.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
resource "aws_instance" "web" {
ami = "foo"
lifecycle {
ignore_changes = ["ami"]
}
}

resource "aws_instance" "bar" {
ami = "foo"
lifecycle {
ignore_changes = []
}
}

resource "aws_instance" "baz" {
ami = "foo"
}
33 changes: 33 additions & 0 deletions terraform/eval_ignore_changes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package terraform
import (
"github.com/hashicorp/terraform/config"
"strings"
)

// EvalIgnoreChanges is an EvalNode implementation that removes diff
// attributes if their name matches names provided by the resource's
// IgnoreChanges lifecycle.
type EvalIgnoreChanges struct {
Resource *config.Resource
Diff **InstanceDiff
}

func (n *EvalIgnoreChanges) Eval(ctx EvalContext) (interface{}, error) {
if n.Diff == nil || *n.Diff == nil || n.Resource == nil || n.Resource.Id() == "" {
return nil, nil
}

diff := *n.Diff
ignoreChanges := n.Resource.Lifecycle.IgnoreChanges

for _, ignoredName := range ignoreChanges {
for name := range diff.Attributes {
if strings.HasPrefix(name, ignoredName) {
delete(diff.Attributes, name)
diff.Attributes[name].GoString()
}
}
}

return nil, nil
}
4 changes: 4 additions & 0 deletions terraform/transform_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,10 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
Resource: n.Resource,
Diff: &diff,
},
&EvalIgnoreChanges{
Resource: n.Resource,
Diff: &diff,
},
&EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Expand Down
11 changes: 11 additions & 0 deletions website/source/docs/configuration/resources.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,20 @@ The `lifecycle` block allows the following keys to be set:
destruction of a given resource. When this is set to `true`, any plan
that includes a destroy of this resource will return an error message.

* `ignore_changes` (list of strings) - Customizes how diffs are evaluated for
resources, allowing individual attributes to be ignored through changes.
As an example, this can be used to ignore dynamic changes to the
resource from external resources. Other meta-parameters cannot be ignored.

~> **NOTE on create\_before\_destroy and dependencies:** Resources that utilize
the `create_before_destroy` key can only depend on other resources that also
include `create_before_destroy`. Referencing a resource that does not include
`create_before_destroy` will result in a dependency graph cycle.

~> **NOTE on ignore\_changes:** Ignored attribute names can be matched by their
name, not state ID. For example, if an `aws_route_table` has two routes defined
and the `ignore_changes` list contains "route", both routes will be ignored.

-------------

Within a resource, you can optionally have a **connection block**.
Expand Down Expand Up @@ -191,6 +200,8 @@ where `LIFECYCLE` is:
```
lifecycle {
[create_before_destroy = true|false]
[prevent_destroy = true|false]
[ignore_changes = [ATTRIBUTE NAME, ...]]
}
```

Expand Down

0 comments on commit cba29f2

Please sign in to comment.