diff --git a/config/config.go b/config/config.go index 811b77ec7ea9..18c2e37e3fea 100644 --- a/config/config.go +++ b/config/config.go @@ -86,6 +86,7 @@ type Resource struct { type ResourceLifecycle struct { CreateBeforeDestroy bool `mapstructure:"create_before_destroy"` PreventDestroy bool `mapstructure:"prevent_destroy"` + IgnoreUpdates bool `mapstructure:"ignore_updates"` } // Provisioner is a configured provisioner step on a resource. diff --git a/config/loader_test.go b/config/loader_test.go index d239bd0b9ad3..65ba410b4147 100644 --- a/config/loader_test.go +++ b/config/loader_test.go @@ -440,6 +440,43 @@ func TestLoadFile_createBeforeDestroy(t *testing.T) { } } +func TestLoadFile_ignoreUpdates(t *testing.T) { + c, err := LoadFile(filepath.Join(fixtureDir, "ignore-updates.tf")) + if err != nil { + t.Fatalf("err: %s", err) + } + + if c == nil { + t.Fatal("config should not be nil") + } + + actual := resourcesStr(c.Resources) + if actual != strings.TrimSpace(ignoreUpdatesResourcesStr) { + 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 enable ignore updates + if !r.Lifecycle.IgnoreUpdates { + t.Fatalf("Bad: %#v", r) + } + + r = c.Resources[1] + if r.Name != "bar" && r.Type != "aws_instance" { + t.Fatalf("Bad: %#v", r) + } + + // Should not enable ignore updates + if r.Lifecycle.IgnoreUpdates { + 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 +713,10 @@ aws_instance[bar] (x1) aws_instance[web] (x1) ami ` + +const ignoreUpdatesResourcesStr = ` +aws_instance[bar] (x1) + ami +aws_instance[web] (x1) + ami +` diff --git a/config/test-fixtures/ignore-updates.tf b/config/test-fixtures/ignore-updates.tf new file mode 100644 index 000000000000..52ad76fd8211 --- /dev/null +++ b/config/test-fixtures/ignore-updates.tf @@ -0,0 +1,13 @@ +resource "aws_instance" "web" { + ami = "foo" + lifecycle { + ignore_updates = true + } +} + +resource "aws_instance" "bar" { + ami = "foo" + lifecycle { + ignore_updates = false + } +} diff --git a/terraform/transform_resource.go b/terraform/transform_resource.go index 0b56721b07a7..87c4b7e1ee55 100644 --- a/terraform/transform_resource.go +++ b/terraform/transform_resource.go @@ -395,6 +395,24 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode { Then: EvalNoop{}, }, + // We don't want to apply if the resource has the IgnoreUpdates lifecycle + &EvalIf{ + If: func(ctx EvalContext) (bool, error) { + update := false + if diffApply != nil { + update = !diffApply.Empty() + } + + ignoreUpdatesEnabled := n.Resource.Lifecycle.IgnoreUpdates && update + + return ignoreUpdatesEnabled, nil + }, + Then: &EvalWriteDiff{ + Name: n.stateId(), + Diff: nil, + }, + }, + &EvalIf{ If: func(ctx EvalContext) (bool, error) { destroy := false diff --git a/website/source/docs/configuration/resources.html.md b/website/source/docs/configuration/resources.html.md index 1ed50df98710..04f373ca40f3 100644 --- a/website/source/docs/configuration/resources.html.md +++ b/website/source/docs/configuration/resources.html.md @@ -68,6 +68,10 @@ 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_updates` (bool) - This flag is used to allow ignoring + external changes to a resource. As an example, this can be used + to ignore dynamic network interface assignments to a resource. + ------------- Within a resource, you can optionally have a **connection block**. @@ -186,6 +190,8 @@ where `LIFECYCLE` is: ``` lifecycle { [create_before_destroy = true|false] + [prevent_destroy = true|false] + [ignore_updates = true|false] } ```