Merge pull request #2525 from robzienert/ignore-updates-lifecycle

Adding ignore_changes lifecycle flag
This commit is contained in:
Paul Hinze 2015-10-14 18:22:24 -05:00
commit b430b984e5
10 changed files with 193 additions and 2 deletions

1
.gitignore vendored
View File

@ -18,3 +18,4 @@ website/node_modules
*.bak
*~
.*.swp
.idea

View File

@ -86,6 +86,7 @@ type Resource struct {
type ResourceLifecycle struct {
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.

View File

@ -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 {
@ -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
`

View File

@ -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"
}

View File

@ -1672,3 +1672,49 @@ func TestContext2Plan_varListErr(t *testing.T) {
t.Fatal("should error")
}
}
func TestContext2Plan_ignoreChanges(t *testing.T) {
m := testModule(t, "plan-ignore-changes")
p := testProvider("aws")
p.DiffFn = testDiffFn
s := &State{
Modules: []*ModuleState{
&ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
"aws_instance.foo": &ResourceState{
Primary: &InstanceState{
ID: "bar",
Attributes: map[string]string{"ami": "ami-abcd1234"},
},
},
},
},
},
}
ctx := testContext2(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
Variables: map[string]string{
"foo": "ami-1234abcd",
},
State: s,
})
plan, err := ctx.Plan()
if err != nil {
t.Fatalf("err: %s", err)
}
if len(plan.Diff.RootModule().Resources) < 1 {
t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources)
}
actual := strings.TrimSpace(plan.String())
expected := strings.TrimSpace(testTerraformPlanIgnoreChangesStr)
if actual != expected {
t.Fatalf("bad:\n%s\n\nexpected\n\n%s", actual, expected)
}
}

View File

@ -0,0 +1,32 @@
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)
}
}
}
return nil, nil
}

View File

@ -1286,3 +1286,16 @@ STATE:
<no state>
`
const testTerraformPlanIgnoreChangesStr = `
DIFF:
UPDATE: aws_instance.foo
type: "" => "aws_instance"
STATE:
aws_instance.foo:
ID = bar
ami = ami-abcd1234
`

View File

@ -0,0 +1,9 @@
variable "foo" {}
resource "aws_instance" "foo" {
ami = "${var.foo}"
lifecycle {
ignore_changes = ["ami"]
}
}

View File

@ -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,

View File

@ -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**.
@ -191,6 +200,8 @@ where `LIFECYCLE` is:
```
lifecycle {
[create_before_destroy = true|false]
[prevent_destroy = true|false]
[ignore_changes = [ATTRIBUTE NAME, ...]]
}
```