Merge pull request #11329 from hashicorp/f-destroy-prov
Destroy Provisioners
This commit is contained in:
commit
b35b263015
|
@ -136,6 +136,9 @@ type Provisioner struct {
|
|||
Type string
|
||||
RawConfig *RawConfig
|
||||
ConnInfo *RawConfig
|
||||
|
||||
When ProvisionerWhen
|
||||
OnFailure ProvisionerOnFailure
|
||||
}
|
||||
|
||||
// Copy returns a copy of this Provisioner
|
||||
|
@ -144,6 +147,8 @@ func (p *Provisioner) Copy() *Provisioner {
|
|||
Type: p.Type,
|
||||
RawConfig: p.RawConfig.Copy(),
|
||||
ConnInfo: p.ConnInfo.Copy(),
|
||||
When: p.When,
|
||||
OnFailure: p.OnFailure,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -553,7 +558,7 @@ func (c *Config) Validate() error {
|
|||
// Validate DependsOn
|
||||
errs = append(errs, c.validateDependsOn(n, r.DependsOn, resources, modules)...)
|
||||
|
||||
// Verify provisioners don't contain any splats
|
||||
// Verify provisioners
|
||||
for _, p := range r.Provisioners {
|
||||
// This validation checks that there are now splat variables
|
||||
// referencing ourself. This currently is not allowed.
|
||||
|
@ -585,6 +590,17 @@ func (c *Config) Validate() error {
|
|||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Check for invalid when/onFailure values, though this should be
|
||||
// picked up by the loader we check here just in case.
|
||||
if p.When == ProvisionerWhenInvalid {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"%s: provisioner 'when' value is invalid", n))
|
||||
}
|
||||
if p.OnFailure == ProvisionerOnFailureInvalid {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"%s: provisioner 'on_failure' value is invalid", n))
|
||||
}
|
||||
}
|
||||
|
||||
// Verify ignore_changes contains valid entries
|
||||
|
|
|
@ -214,7 +214,16 @@ func resourcesStr(rs []*Resource) string {
|
|||
if len(r.Provisioners) > 0 {
|
||||
result += fmt.Sprintf(" provisioners\n")
|
||||
for _, p := range r.Provisioners {
|
||||
result += fmt.Sprintf(" %s\n", p.Type)
|
||||
when := ""
|
||||
if p.When != ProvisionerWhenCreate {
|
||||
when = fmt.Sprintf(" (%s)", p.When.String())
|
||||
}
|
||||
|
||||
result += fmt.Sprintf(" %s%s\n", p.Type, when)
|
||||
|
||||
if p.OnFailure != ProvisionerOnFailureFail {
|
||||
result += fmt.Sprintf(" on_failure = %s\n", p.OnFailure.String())
|
||||
}
|
||||
|
||||
ks := make([]string, 0, len(p.RawConfig.Raw))
|
||||
for k, _ := range p.RawConfig.Raw {
|
||||
|
|
|
@ -168,6 +168,13 @@ func TestConfigValidate_table(t *testing.T) {
|
|||
true,
|
||||
"data sources cannot have",
|
||||
},
|
||||
|
||||
{
|
||||
"basic provisioners",
|
||||
"validate-basic-provisioners",
|
||||
false,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
|
|
|
@ -849,8 +849,40 @@ func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{})
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Delete the "connection" section, handle separately
|
||||
// Parse the "when" value
|
||||
when := ProvisionerWhenCreate
|
||||
if v, ok := config["when"]; ok {
|
||||
switch v {
|
||||
case "create":
|
||||
when = ProvisionerWhenCreate
|
||||
case "destroy":
|
||||
when = ProvisionerWhenDestroy
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"position %s: 'provisioner' when must be 'create' or 'destroy'",
|
||||
item.Pos())
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the "on_failure" value
|
||||
onFailure := ProvisionerOnFailureFail
|
||||
if v, ok := config["on_failure"]; ok {
|
||||
switch v {
|
||||
case "continue":
|
||||
onFailure = ProvisionerOnFailureContinue
|
||||
case "fail":
|
||||
onFailure = ProvisionerOnFailureFail
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"position %s: 'provisioner' on_failure must be 'continue' or 'fail'",
|
||||
item.Pos())
|
||||
}
|
||||
}
|
||||
|
||||
// Delete fields we special case
|
||||
delete(config, "connection")
|
||||
delete(config, "when")
|
||||
delete(config, "on_failure")
|
||||
|
||||
rawConfig, err := NewRawConfig(config)
|
||||
if err != nil {
|
||||
|
@ -889,6 +921,8 @@ func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{})
|
|||
Type: n,
|
||||
RawConfig: rawConfig,
|
||||
ConnInfo: connRaw,
|
||||
When: when,
|
||||
OnFailure: onFailure,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -629,6 +629,22 @@ func TestLoadFile_provisioners(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoadFile_provisionersDestroy(t *testing.T) {
|
||||
c, err := LoadFile(filepath.Join(fixtureDir, "provisioners-destroy.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(provisionerDestroyResourcesStr) {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFile_unnamedOutput(t *testing.T) {
|
||||
_, err := LoadFile(filepath.Join(fixtureDir, "output-unnamed.tf"))
|
||||
if err == nil {
|
||||
|
@ -1126,6 +1142,17 @@ aws_instance.web (x1)
|
|||
user: var.foo
|
||||
`
|
||||
|
||||
const provisionerDestroyResourcesStr = `
|
||||
aws_instance.web (x1)
|
||||
provisioners
|
||||
shell
|
||||
shell (destroy)
|
||||
path
|
||||
shell (destroy)
|
||||
on_failure = continue
|
||||
path
|
||||
`
|
||||
|
||||
const connectionResourcesStr = `
|
||||
aws_instance.web (x1)
|
||||
ami
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package config
|
||||
|
||||
// ProvisionerWhen is an enum for valid values for when to run provisioners.
|
||||
type ProvisionerWhen int
|
||||
|
||||
const (
|
||||
ProvisionerWhenInvalid ProvisionerWhen = iota
|
||||
ProvisionerWhenCreate
|
||||
ProvisionerWhenDestroy
|
||||
)
|
||||
|
||||
var provisionerWhenStrs = map[ProvisionerWhen]string{
|
||||
ProvisionerWhenInvalid: "invalid",
|
||||
ProvisionerWhenCreate: "create",
|
||||
ProvisionerWhenDestroy: "destroy",
|
||||
}
|
||||
|
||||
func (v ProvisionerWhen) String() string {
|
||||
return provisionerWhenStrs[v]
|
||||
}
|
||||
|
||||
// ProvisionerOnFailure is an enum for valid values for on_failure options
|
||||
// for provisioners.
|
||||
type ProvisionerOnFailure int
|
||||
|
||||
const (
|
||||
ProvisionerOnFailureInvalid ProvisionerOnFailure = iota
|
||||
ProvisionerOnFailureContinue
|
||||
ProvisionerOnFailureFail
|
||||
)
|
||||
|
||||
var provisionerOnFailureStrs = map[ProvisionerOnFailure]string{
|
||||
ProvisionerOnFailureInvalid: "invalid",
|
||||
ProvisionerOnFailureContinue: "continue",
|
||||
ProvisionerOnFailureFail: "fail",
|
||||
}
|
||||
|
||||
func (v ProvisionerOnFailure) String() string {
|
||||
return provisionerOnFailureStrs[v]
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
resource "aws_instance" "web" {
|
||||
provisioner "shell" {}
|
||||
|
||||
provisioner "shell" {
|
||||
path = "foo"
|
||||
when = "destroy"
|
||||
}
|
||||
|
||||
provisioner "shell" {
|
||||
path = "foo"
|
||||
when = "destroy"
|
||||
on_failure = "continue"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
resource "aws_instance" "web" {
|
||||
provisioner "shell" {}
|
||||
|
||||
provisioner "shell" {
|
||||
path = "foo"
|
||||
when = "destroy"
|
||||
}
|
||||
|
||||
provisioner "shell" {
|
||||
path = "foo"
|
||||
when = "destroy"
|
||||
on_failure = "continue"
|
||||
}
|
||||
}
|
|
@ -3998,6 +3998,432 @@ aws_instance.web:
|
|||
`)
|
||||
}
|
||||
|
||||
// Verify that a normal provisioner with on_failure "continue" set won't
|
||||
// taint the resource and continues executing.
|
||||
func TestContext2Apply_provisionerFailContinue(t *testing.T) {
|
||||
m := testModule(t, "apply-provisioner-fail-continue")
|
||||
p := testProvider("aws")
|
||||
pr := testProvisioner()
|
||||
p.ApplyFn = testApplyFn
|
||||
p.DiffFn = testDiffFn
|
||||
|
||||
pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error {
|
||||
return fmt.Errorf("provisioner error")
|
||||
}
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Module: m,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ResourceProvisionerFactory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
||||
if _, err := ctx.Plan(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state, err := ctx.Apply()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
checkStateString(t, state, `
|
||||
aws_instance.foo:
|
||||
ID = foo
|
||||
foo = bar
|
||||
type = aws_instance
|
||||
`)
|
||||
|
||||
// Verify apply was invoked
|
||||
if !pr.ApplyCalled {
|
||||
t.Fatalf("provisioner not invoked")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that a normal provisioner with on_failure "continue" records
|
||||
// the error with the hook.
|
||||
func TestContext2Apply_provisionerFailContinueHook(t *testing.T) {
|
||||
h := new(MockHook)
|
||||
m := testModule(t, "apply-provisioner-fail-continue")
|
||||
p := testProvider("aws")
|
||||
pr := testProvisioner()
|
||||
p.ApplyFn = testApplyFn
|
||||
p.DiffFn = testDiffFn
|
||||
pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error {
|
||||
return fmt.Errorf("provisioner error")
|
||||
}
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Module: m,
|
||||
Hooks: []Hook{h},
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ResourceProvisionerFactory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
||||
if _, err := ctx.Plan(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if _, err := ctx.Apply(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !h.PostProvisionCalled {
|
||||
t.Fatal("PostProvision not called")
|
||||
}
|
||||
if h.PostProvisionErrorArg == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Apply_provisionerDestroy(t *testing.T) {
|
||||
m := testModule(t, "apply-provisioner-destroy")
|
||||
p := testProvider("aws")
|
||||
pr := testProvisioner()
|
||||
p.ApplyFn = testApplyFn
|
||||
p.DiffFn = testDiffFn
|
||||
pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error {
|
||||
val, ok := c.Config["foo"]
|
||||
if !ok || val != "destroy" {
|
||||
t.Fatalf("bad value for foo: %v %#v", val, c)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Module: m,
|
||||
State: state,
|
||||
Destroy: true,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ResourceProvisionerFactory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
||||
if _, err := ctx.Plan(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state, err := ctx.Apply()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
checkStateString(t, state, `<no state>`)
|
||||
|
||||
// Verify apply was invoked
|
||||
if !pr.ApplyCalled {
|
||||
t.Fatalf("provisioner not invoked")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that on destroy provisioner failure, nothing happens to the instance
|
||||
func TestContext2Apply_provisionerDestroyFail(t *testing.T) {
|
||||
m := testModule(t, "apply-provisioner-destroy")
|
||||
p := testProvider("aws")
|
||||
pr := testProvisioner()
|
||||
p.ApplyFn = testApplyFn
|
||||
p.DiffFn = testDiffFn
|
||||
pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error {
|
||||
return fmt.Errorf("provisioner error")
|
||||
}
|
||||
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Module: m,
|
||||
State: state,
|
||||
Destroy: true,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ResourceProvisionerFactory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
||||
if _, err := ctx.Plan(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state, err := ctx.Apply()
|
||||
if err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
|
||||
checkStateString(t, state, `
|
||||
aws_instance.foo:
|
||||
ID = bar
|
||||
`)
|
||||
|
||||
// Verify apply was invoked
|
||||
if !pr.ApplyCalled {
|
||||
t.Fatalf("provisioner not invoked")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that on destroy provisioner failure with "continue" that
|
||||
// we continue to the next provisioner.
|
||||
func TestContext2Apply_provisionerDestroyFailContinue(t *testing.T) {
|
||||
m := testModule(t, "apply-provisioner-destroy-continue")
|
||||
p := testProvider("aws")
|
||||
pr := testProvisioner()
|
||||
p.ApplyFn = testApplyFn
|
||||
p.DiffFn = testDiffFn
|
||||
|
||||
var calls []string
|
||||
pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error {
|
||||
val, ok := c.Config["foo"]
|
||||
if !ok {
|
||||
t.Fatalf("bad value for foo: %v %#v", val, c)
|
||||
}
|
||||
|
||||
calls = append(calls, val.(string))
|
||||
return fmt.Errorf("provisioner error")
|
||||
}
|
||||
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Module: m,
|
||||
State: state,
|
||||
Destroy: true,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ResourceProvisionerFactory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
||||
if _, err := ctx.Plan(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state, err := ctx.Apply()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
checkStateString(t, state, `<no state>`)
|
||||
|
||||
// Verify apply was invoked
|
||||
if !pr.ApplyCalled {
|
||||
t.Fatalf("provisioner not invoked")
|
||||
}
|
||||
|
||||
expected := []string{"one", "two"}
|
||||
if !reflect.DeepEqual(calls, expected) {
|
||||
t.Fatalf("bad: %#v", calls)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that on destroy provisioner failure with "continue" that
|
||||
// we continue to the next provisioner. But if the next provisioner defines
|
||||
// to fail, then we fail after running it.
|
||||
func TestContext2Apply_provisionerDestroyFailContinueFail(t *testing.T) {
|
||||
m := testModule(t, "apply-provisioner-destroy-fail")
|
||||
p := testProvider("aws")
|
||||
pr := testProvisioner()
|
||||
p.ApplyFn = testApplyFn
|
||||
p.DiffFn = testDiffFn
|
||||
|
||||
var calls []string
|
||||
pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error {
|
||||
val, ok := c.Config["foo"]
|
||||
if !ok {
|
||||
t.Fatalf("bad value for foo: %v %#v", val, c)
|
||||
}
|
||||
|
||||
calls = append(calls, val.(string))
|
||||
return fmt.Errorf("provisioner error")
|
||||
}
|
||||
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Module: m,
|
||||
State: state,
|
||||
Destroy: true,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ResourceProvisionerFactory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
||||
if _, err := ctx.Plan(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state, err := ctx.Apply()
|
||||
if err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
|
||||
checkStateString(t, state, `
|
||||
aws_instance.foo:
|
||||
ID = bar
|
||||
`)
|
||||
|
||||
// Verify apply was invoked
|
||||
if !pr.ApplyCalled {
|
||||
t.Fatalf("provisioner not invoked")
|
||||
}
|
||||
|
||||
expected := []string{"one", "two"}
|
||||
if !reflect.DeepEqual(calls, expected) {
|
||||
t.Fatalf("bad: %#v", calls)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify destroy provisioners are not run for tainted instances.
|
||||
func TestContext2Apply_provisionerDestroyTainted(t *testing.T) {
|
||||
m := testModule(t, "apply-provisioner-destroy")
|
||||
p := testProvider("aws")
|
||||
pr := testProvisioner()
|
||||
p.ApplyFn = testApplyFn
|
||||
p.DiffFn = testDiffFn
|
||||
|
||||
destroyCalled := false
|
||||
pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error {
|
||||
expected := "create"
|
||||
if rs.ID == "bar" {
|
||||
destroyCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
val, ok := c.Config["foo"]
|
||||
if !ok || val != expected {
|
||||
t.Fatalf("bad value for foo: %v %#v", val, c)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "bar",
|
||||
Tainted: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Module: m,
|
||||
State: state,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ResourceProvisionerFactory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
||||
if _, err := ctx.Plan(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state, err := ctx.Apply()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
checkStateString(t, state, `
|
||||
aws_instance.foo:
|
||||
ID = foo
|
||||
foo = bar
|
||||
type = aws_instance
|
||||
`)
|
||||
|
||||
// Verify apply was invoked
|
||||
if !pr.ApplyCalled {
|
||||
t.Fatalf("provisioner not invoked")
|
||||
}
|
||||
|
||||
if destroyCalled {
|
||||
t.Fatal("destroy should not be called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Apply_provisionerResourceRef(t *testing.T) {
|
||||
m := testModule(t, "apply-provisioner-resource-ref")
|
||||
p := testProvider("aws")
|
||||
|
|
|
@ -413,7 +413,7 @@ func (*DebugHook) PreProvision(ii *InstanceInfo, s string) (HookAction, error) {
|
|||
return HookActionContinue, nil
|
||||
}
|
||||
|
||||
func (*DebugHook) PostProvision(ii *InstanceInfo, s string) (HookAction, error) {
|
||||
func (*DebugHook) PostProvision(ii *InstanceInfo, s string, err error) (HookAction, error) {
|
||||
if dbug == nil {
|
||||
return HookActionContinue, nil
|
||||
}
|
||||
|
|
|
@ -156,7 +156,7 @@ func TestDebugHook_nilArgs(t *testing.T) {
|
|||
h.PostApply(nil, nil, nil)
|
||||
h.PostDiff(nil, nil)
|
||||
h.PostImportState(nil, nil)
|
||||
h.PostProvision(nil, "")
|
||||
h.PostProvision(nil, "", nil)
|
||||
h.PostProvisionResource(nil, nil)
|
||||
h.PostRefresh(nil, nil)
|
||||
h.PostStateUpdate(nil)
|
||||
|
|
|
@ -52,16 +52,6 @@ func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
|
|||
*n.CreateNew = state.ID == "" && !diff.GetDestroy() || diff.RequiresNew()
|
||||
}
|
||||
|
||||
{
|
||||
// Call pre-apply hook
|
||||
err := ctx.Hook(func(h Hook) (HookAction, error) {
|
||||
return h.PreApply(n.Info, state, diff)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// With the completed diff, apply!
|
||||
log.Printf("[DEBUG] apply: %s: executing Apply", n.Info.Id)
|
||||
state, err := provider.Apply(n.Info, state, diff)
|
||||
|
@ -104,6 +94,37 @@ func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
// EvalApplyPre is an EvalNode implementation that does the pre-Apply work
|
||||
type EvalApplyPre struct {
|
||||
Info *InstanceInfo
|
||||
State **InstanceState
|
||||
Diff **InstanceDiff
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
func (n *EvalApplyPre) Eval(ctx EvalContext) (interface{}, error) {
|
||||
state := *n.State
|
||||
diff := *n.Diff
|
||||
|
||||
// If the state is nil, make it non-nil
|
||||
if state == nil {
|
||||
state = new(InstanceState)
|
||||
}
|
||||
state.init()
|
||||
|
||||
{
|
||||
// Call post-apply hook
|
||||
err := ctx.Hook(func(h Hook) (HookAction, error) {
|
||||
return h.PreApply(n.Info, state, diff)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// EvalApplyPost is an EvalNode implementation that does the post-Apply work
|
||||
type EvalApplyPost struct {
|
||||
Info *InstanceInfo
|
||||
|
@ -140,25 +161,33 @@ type EvalApplyProvisioners struct {
|
|||
InterpResource *Resource
|
||||
CreateNew *bool
|
||||
Error *error
|
||||
|
||||
// When is the type of provisioner to run at this point
|
||||
When config.ProvisionerWhen
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
|
||||
state := *n.State
|
||||
|
||||
if !*n.CreateNew {
|
||||
if n.CreateNew != nil && !*n.CreateNew {
|
||||
// If we're not creating a new resource, then don't run provisioners
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(n.Resource.Provisioners) == 0 {
|
||||
provs := n.filterProvisioners()
|
||||
if len(provs) == 0 {
|
||||
// We have no provisioners, so don't do anything
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// taint tells us whether to enable tainting.
|
||||
taint := n.When == config.ProvisionerWhenCreate
|
||||
|
||||
if n.Error != nil && *n.Error != nil {
|
||||
// We're already errored creating, so mark as tainted and continue
|
||||
if taint {
|
||||
state.Tainted = true
|
||||
}
|
||||
|
||||
// We're already tainted, so just return out
|
||||
return nil, nil
|
||||
|
@ -176,10 +205,11 @@ func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
|
|||
|
||||
// If there are no errors, then we append it to our output error
|
||||
// if we have one, otherwise we just output it.
|
||||
err := n.apply(ctx)
|
||||
err := n.apply(ctx, provs)
|
||||
if err != nil {
|
||||
// Provisioning failed, so mark the resource as tainted
|
||||
if taint {
|
||||
state.Tainted = true
|
||||
}
|
||||
|
||||
if n.Error != nil {
|
||||
*n.Error = multierror.Append(*n.Error, err)
|
||||
|
@ -201,7 +231,29 @@ func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (n *EvalApplyProvisioners) apply(ctx EvalContext) error {
|
||||
// filterProvisioners filters the provisioners on the resource to only
|
||||
// the provisioners specified by the "when" option.
|
||||
func (n *EvalApplyProvisioners) filterProvisioners() []*config.Provisioner {
|
||||
// Fast path the zero case
|
||||
if n.Resource == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(n.Resource.Provisioners) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make([]*config.Provisioner, 0, len(n.Resource.Provisioners))
|
||||
for _, p := range n.Resource.Provisioners {
|
||||
if p.When == n.When {
|
||||
result = append(result, p)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *EvalApplyProvisioners) apply(ctx EvalContext, provs []*config.Provisioner) error {
|
||||
state := *n.State
|
||||
|
||||
// Store the original connection info, restore later
|
||||
|
@ -210,7 +262,7 @@ func (n *EvalApplyProvisioners) apply(ctx EvalContext) error {
|
|||
state.Ephemeral.ConnInfo = origConnInfo
|
||||
}()
|
||||
|
||||
for _, prov := range n.Resource.Provisioners {
|
||||
for _, prov := range provs {
|
||||
// Get the provisioner
|
||||
provisioner := ctx.Provisioner(prov.Type)
|
||||
|
||||
|
@ -275,18 +327,30 @@ func (n *EvalApplyProvisioners) apply(ctx EvalContext) error {
|
|||
|
||||
// Invoke the Provisioner
|
||||
output := CallbackUIOutput{OutputFn: outputFn}
|
||||
if err := provisioner.Apply(&output, state, provConfig); err != nil {
|
||||
return err
|
||||
applyErr := provisioner.Apply(&output, state, provConfig)
|
||||
|
||||
// Call post hook
|
||||
hookErr := ctx.Hook(func(h Hook) (HookAction, error) {
|
||||
return h.PostProvision(n.Info, prov.Type, applyErr)
|
||||
})
|
||||
|
||||
// Handle the error before we deal with the hook
|
||||
if applyErr != nil {
|
||||
// Determine failure behavior
|
||||
switch prov.OnFailure {
|
||||
case config.ProvisionerOnFailureContinue:
|
||||
log.Printf(
|
||||
"[INFO] apply: %s [%s]: error during provision, continue requested",
|
||||
n.Info.Id, prov.Type)
|
||||
|
||||
case config.ProvisionerOnFailureFail:
|
||||
return applyErr
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Call post hook
|
||||
err := ctx.Hook(func(h Hook) (HookAction, error) {
|
||||
return h.PostProvision(n.Info, prov.Type)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Deal with the hook
|
||||
if hookErr != nil {
|
||||
return hookErr
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -96,13 +96,8 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
|
|||
),
|
||||
|
||||
// Provisioner-related transformations
|
||||
GraphTransformIf(
|
||||
func() bool { return !b.Destroy },
|
||||
GraphTransformMulti(
|
||||
&MissingProvisionerTransformer{Provisioners: b.Provisioners},
|
||||
&ProvisionerTransformer{},
|
||||
),
|
||||
),
|
||||
|
||||
// Add root variables
|
||||
&RootVariableTransformer{Module: b.Module},
|
||||
|
|
|
@ -232,6 +232,78 @@ func TestApplyGraphBuilder_moduleDestroy(t *testing.T) {
|
|||
"module.A.null_resource.foo (destroy)")
|
||||
}
|
||||
|
||||
func TestApplyGraphBuilder_provisioner(t *testing.T) {
|
||||
diff := &Diff{
|
||||
Modules: []*ModuleDiff{
|
||||
&ModuleDiff{
|
||||
Path: []string{"root"},
|
||||
Resources: map[string]*InstanceDiff{
|
||||
"null_resource.foo": &InstanceDiff{
|
||||
Attributes: map[string]*ResourceAttrDiff{
|
||||
"name": &ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b := &ApplyGraphBuilder{
|
||||
Module: testModule(t, "graph-builder-apply-provisioner"),
|
||||
Diff: diff,
|
||||
Providers: []string{"null"},
|
||||
Provisioners: []string{"local"},
|
||||
}
|
||||
|
||||
g, err := b.Build(RootModulePath)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
testGraphContains(t, g, "provisioner.local")
|
||||
testGraphHappensBefore(
|
||||
t, g,
|
||||
"provisioner.local",
|
||||
"null_resource.foo")
|
||||
}
|
||||
|
||||
func TestApplyGraphBuilder_provisionerDestroy(t *testing.T) {
|
||||
diff := &Diff{
|
||||
Modules: []*ModuleDiff{
|
||||
&ModuleDiff{
|
||||
Path: []string{"root"},
|
||||
Resources: map[string]*InstanceDiff{
|
||||
"null_resource.foo": &InstanceDiff{
|
||||
Destroy: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b := &ApplyGraphBuilder{
|
||||
Destroy: true,
|
||||
Module: testModule(t, "graph-builder-apply-provisioner"),
|
||||
Diff: diff,
|
||||
Providers: []string{"null"},
|
||||
Provisioners: []string{"local"},
|
||||
}
|
||||
|
||||
g, err := b.Build(RootModulePath)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
testGraphContains(t, g, "provisioner.local")
|
||||
testGraphHappensBefore(
|
||||
t, g,
|
||||
"provisioner.local",
|
||||
"null_resource.foo (destroy)")
|
||||
}
|
||||
|
||||
const testApplyGraphBuilderStr = `
|
||||
aws_instance.create
|
||||
provider.aws
|
||||
|
|
|
@ -88,6 +88,32 @@ func TestGraphWalk_panicWrap(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// testGraphContains is an assertion helper that tests that a node is
|
||||
// contained in the graph.
|
||||
func testGraphContains(t *testing.T, g *Graph, name string) {
|
||||
for _, v := range g.Vertices() {
|
||||
if dag.VertexName(v) == name {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatalf(
|
||||
"Expected %q in:\n\n%s",
|
||||
name, g.String())
|
||||
}
|
||||
|
||||
// testGraphnotContains is an assertion helper that tests that a node is
|
||||
// NOT contained in the graph.
|
||||
func testGraphNotContains(t *testing.T, g *Graph, name string) {
|
||||
for _, v := range g.Vertices() {
|
||||
if dag.VertexName(v) == name {
|
||||
t.Fatalf(
|
||||
"Expected %q to NOT be in:\n\n%s",
|
||||
name, g.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testGraphHappensBefore is an assertion helper that tests that node
|
||||
// A (dag.VertexName value) happens before node B.
|
||||
func testGraphHappensBefore(t *testing.T, g *Graph, A, B string) {
|
||||
|
|
|
@ -42,7 +42,7 @@ type Hook interface {
|
|||
PreProvisionResource(*InstanceInfo, *InstanceState) (HookAction, error)
|
||||
PostProvisionResource(*InstanceInfo, *InstanceState) (HookAction, error)
|
||||
PreProvision(*InstanceInfo, string) (HookAction, error)
|
||||
PostProvision(*InstanceInfo, string) (HookAction, error)
|
||||
PostProvision(*InstanceInfo, string, error) (HookAction, error)
|
||||
ProvisionOutput(*InstanceInfo, string, string)
|
||||
|
||||
// PreRefresh and PostRefresh are called before and after a single
|
||||
|
@ -92,7 +92,7 @@ func (*NilHook) PreProvision(*InstanceInfo, string) (HookAction, error) {
|
|||
return HookActionContinue, nil
|
||||
}
|
||||
|
||||
func (*NilHook) PostProvision(*InstanceInfo, string) (HookAction, error) {
|
||||
func (*NilHook) PostProvision(*InstanceInfo, string, error) (HookAction, error) {
|
||||
return HookActionContinue, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ type MockHook struct {
|
|||
PostProvisionCalled bool
|
||||
PostProvisionInfo *InstanceInfo
|
||||
PostProvisionProvisionerId string
|
||||
PostProvisionErrorArg error
|
||||
PostProvisionReturn HookAction
|
||||
PostProvisionError error
|
||||
|
||||
|
@ -170,13 +171,14 @@ func (h *MockHook) PreProvision(n *InstanceInfo, provId string) (HookAction, err
|
|||
return h.PreProvisionReturn, h.PreProvisionError
|
||||
}
|
||||
|
||||
func (h *MockHook) PostProvision(n *InstanceInfo, provId string) (HookAction, error) {
|
||||
func (h *MockHook) PostProvision(n *InstanceInfo, provId string, err error) (HookAction, error) {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
h.PostProvisionCalled = true
|
||||
h.PostProvisionInfo = n
|
||||
h.PostProvisionProvisionerId = provId
|
||||
h.PostProvisionErrorArg = err
|
||||
return h.PostProvisionReturn, h.PostProvisionError
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ func (h *stopHook) PreProvision(*InstanceInfo, string) (HookAction, error) {
|
|||
return h.hook()
|
||||
}
|
||||
|
||||
func (h *stopHook) PostProvision(*InstanceInfo, string) (HookAction, error) {
|
||||
func (h *stopHook) PostProvision(*InstanceInfo, string, error) (HookAction, error) {
|
||||
return h.hook()
|
||||
}
|
||||
|
||||
|
|
|
@ -298,6 +298,12 @@ func (n *NodeApplyableResource) evalTreeManagedResource(
|
|||
Name: stateId,
|
||||
Output: &state,
|
||||
},
|
||||
// Call pre-apply hook
|
||||
&EvalApplyPre{
|
||||
Info: info,
|
||||
State: &state,
|
||||
Diff: &diffApply,
|
||||
},
|
||||
&EvalApply{
|
||||
Info: info,
|
||||
State: &state,
|
||||
|
@ -321,6 +327,7 @@ func (n *NodeApplyableResource) evalTreeManagedResource(
|
|||
InterpResource: resource,
|
||||
CreateNew: &createNew,
|
||||
Error: &err,
|
||||
When: config.ProvisionerWhenCreate,
|
||||
},
|
||||
&EvalIf{
|
||||
If: func(ctx EvalContext) (bool, error) {
|
||||
|
|
|
@ -107,6 +107,17 @@ func (n *NodeDestroyResource) EvalTree() EvalNode {
|
|||
uniqueExtra: "destroy",
|
||||
}
|
||||
|
||||
// Build the resource for eval
|
||||
addr := n.Addr
|
||||
resource := &Resource{
|
||||
Name: addr.Name,
|
||||
Type: addr.Type,
|
||||
CountIndex: addr.Index,
|
||||
}
|
||||
if resource.CountIndex < 0 {
|
||||
resource.CountIndex = 0
|
||||
}
|
||||
|
||||
// Get our state
|
||||
rs := n.ResourceState
|
||||
if rs == nil {
|
||||
|
@ -160,6 +171,48 @@ func (n *NodeDestroyResource) EvalTree() EvalNode {
|
|||
&EvalRequireState{
|
||||
State: &state,
|
||||
},
|
||||
|
||||
// Call pre-apply hook
|
||||
&EvalApplyPre{
|
||||
Info: info,
|
||||
State: &state,
|
||||
Diff: &diffApply,
|
||||
},
|
||||
|
||||
// Run destroy provisioners if not tainted
|
||||
&EvalIf{
|
||||
If: func(ctx EvalContext) (bool, error) {
|
||||
if state != nil && state.Tainted {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
},
|
||||
|
||||
Then: &EvalApplyProvisioners{
|
||||
Info: info,
|
||||
State: &state,
|
||||
Resource: n.Config,
|
||||
InterpResource: resource,
|
||||
Error: &err,
|
||||
When: config.ProvisionerWhenDestroy,
|
||||
},
|
||||
},
|
||||
|
||||
// If we have a provisioning error, then we just call
|
||||
// the post-apply hook now.
|
||||
&EvalIf{
|
||||
If: func(ctx EvalContext) (bool, error) {
|
||||
return err != nil, nil
|
||||
},
|
||||
|
||||
Then: &EvalApplyPost{
|
||||
Info: info,
|
||||
State: &state,
|
||||
Error: &err,
|
||||
},
|
||||
},
|
||||
|
||||
// Make sure we handle data sources properly.
|
||||
&EvalIf{
|
||||
If: func(ctx EvalContext) (bool, error) {
|
||||
|
|
|
@ -179,6 +179,10 @@ func (h *HookRecordApplyOrder) PreApply(
|
|||
info *InstanceInfo,
|
||||
s *InstanceState,
|
||||
d *InstanceDiff) (HookAction, error) {
|
||||
if d.Empty() {
|
||||
return HookActionContinue, nil
|
||||
}
|
||||
|
||||
if h.Active {
|
||||
h.l.Lock()
|
||||
defer h.l.Unlock()
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
resource "aws_instance" "foo" {
|
||||
foo = "bar"
|
||||
|
||||
provisioner "shell" {
|
||||
foo = "one"
|
||||
when = "destroy"
|
||||
on_failure = "continue"
|
||||
}
|
||||
|
||||
provisioner "shell" {
|
||||
foo = "two"
|
||||
when = "destroy"
|
||||
on_failure = "continue"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
resource "aws_instance" "foo" {
|
||||
foo = "bar"
|
||||
|
||||
provisioner "shell" {
|
||||
foo = "one"
|
||||
when = "destroy"
|
||||
on_failure = "continue"
|
||||
}
|
||||
|
||||
provisioner "shell" {
|
||||
foo = "two"
|
||||
when = "destroy"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
resource "aws_instance" "foo" {
|
||||
foo = "bar"
|
||||
|
||||
provisioner "shell" {
|
||||
foo = "create"
|
||||
}
|
||||
|
||||
provisioner "shell" {
|
||||
foo = "destroy"
|
||||
when = "destroy"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
resource "aws_instance" "foo" {
|
||||
foo = "bar"
|
||||
|
||||
provisioner "shell" {
|
||||
on_failure = "continue"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
resource "null_resource" "foo" {
|
||||
provisioner "local" {}
|
||||
}
|
|
@ -127,6 +127,12 @@ func (n *graphNodeDeposedResource) EvalTree() EvalNode {
|
|||
State: &state,
|
||||
Output: &diff,
|
||||
},
|
||||
// Call pre-apply hook
|
||||
&EvalApplyPre{
|
||||
Info: info,
|
||||
State: &state,
|
||||
Diff: &diff,
|
||||
},
|
||||
&EvalApply{
|
||||
Info: info,
|
||||
State: &state,
|
||||
|
|
|
@ -321,6 +321,11 @@ func (n *graphNodeOrphanResource) managedResourceEvalNodes(info *InstanceInfo) [
|
|||
Name: n.ResourceKey.String(),
|
||||
Output: &state,
|
||||
},
|
||||
&EvalApplyPre{
|
||||
Info: info,
|
||||
State: &state,
|
||||
Diff: &diff,
|
||||
},
|
||||
&EvalApply{
|
||||
Info: info,
|
||||
State: &state,
|
||||
|
|
|
@ -500,6 +500,11 @@ func (n *graphNodeExpandedResource) managedResourceEvalNodes(resource *Resource,
|
|||
Name: n.stateId(),
|
||||
Output: &state,
|
||||
},
|
||||
&EvalApplyPre{
|
||||
Info: info,
|
||||
State: &state,
|
||||
Diff: &diffApply,
|
||||
},
|
||||
&EvalApply{
|
||||
Info: info,
|
||||
State: &state,
|
||||
|
|
|
@ -295,6 +295,9 @@ where `PROVISIONER` is:
|
|||
provisioner NAME {
|
||||
CONFIG ...
|
||||
|
||||
[when = "create"|"destroy"]
|
||||
[on_failure = "continue"|"fail"]
|
||||
|
||||
[CONNECTION]
|
||||
}
|
||||
```
|
||||
|
|
|
@ -3,15 +3,104 @@ layout: "docs"
|
|||
page_title: "Provisioners"
|
||||
sidebar_current: "docs-provisioners"
|
||||
description: |-
|
||||
When a resource is initially created, provisioners can be executed to initialize that resource. This can be used to add resources to an inventory management system, run a configuration management tool, bootstrap the resource into a cluster, etc.
|
||||
Provisioners are used to execute scripts on a local or remote machine as part of resource creation or destruction.
|
||||
---
|
||||
|
||||
# Provisioners
|
||||
|
||||
When a resource is initially created, provisioners can be executed to
|
||||
initialize that resource. This can be used to add resources to an inventory
|
||||
management system, run a configuration management tool, bootstrap the
|
||||
resource into a cluster, etc.
|
||||
Provisioners are used to execute scripts on a local or remote machine
|
||||
as part of resource creation or destruction. Provisioners can be used to
|
||||
bootstrap a resource, cleanup before destroy, run configuration management, etc.
|
||||
|
||||
Use the navigation to the left to read about the available provisioners.
|
||||
Provisioners are added directly to any resource:
|
||||
|
||||
```
|
||||
resource "aws_instance" "web" {
|
||||
# ...
|
||||
|
||||
provisioner "local-exec" {
|
||||
command = "echo ${self.private_ip_address} > file.txt"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For provisioners other than local execution, you must specify
|
||||
[connection settings](/docs/provisioners/connection.html) so Terraform knows
|
||||
how to communicate with the resource.
|
||||
|
||||
## Creation-Time Provisioners
|
||||
|
||||
Provisioners by default run when the resource they are defined within is
|
||||
created. Creation-time provisioners are only run during _creation_, not
|
||||
during updating or any other lifecycle. They are meant as a means to perform
|
||||
bootstrapping of a system.
|
||||
|
||||
If a creation-time provisioner fails, the resource is marked as **tainted**.
|
||||
A tainted resource will be planned for destruction and recreation upon the
|
||||
next `terraform apply`. Terraform does this because a failed provisioner
|
||||
can leave a resource in a semi-configured state. Because Terraform cannot
|
||||
reason about what the provisioner does, the only way to ensure proper creation
|
||||
of a resource is to recreate it. This is tainting.
|
||||
|
||||
You can change this behavior by setting the `on_failure` attribute,
|
||||
which is covered in detail below.
|
||||
|
||||
## Destroy-Time Provisioners
|
||||
|
||||
If `when = "destroy"` is specified, the provisioner will run when the
|
||||
resource it is defined within is _destroyed_.
|
||||
|
||||
Destroy provisioners are run before the resource is destroyed. If they
|
||||
fail, Terraform will error and rerun the provisioners again on the next
|
||||
`terraform apply`. Due to this behavior, care should be taken for destroy
|
||||
provisioners to be safe to run multiple times.
|
||||
|
||||
## Multiple Provisioners
|
||||
|
||||
Multiple provisioners can be specified within a resource. Multiple provisioners
|
||||
are executed in the order they're defined in the configuration file.
|
||||
|
||||
You may also mix and match creation and destruction provisioners. Only
|
||||
the provisioners that are valid for a given operation will be run. Those
|
||||
valid provisioners will be run in the order they're defined in the configuration
|
||||
file.
|
||||
|
||||
Example of multiple provisioners:
|
||||
|
||||
```
|
||||
resource "aws_instance" "web" {
|
||||
# ...
|
||||
|
||||
provisioner "local-exec" {
|
||||
command = "echo first"
|
||||
}
|
||||
|
||||
provisioner "local-exec" {
|
||||
command = "echo second"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Failure Behavior
|
||||
|
||||
By default, provisioners that fail will also cause the Terraform apply
|
||||
itself to error. The `on_failure` setting can be used to change this. The
|
||||
allowed values are:
|
||||
|
||||
* `"continue"` - Ignore the error and continue with creation or destruction.
|
||||
|
||||
* `"fail"` - Error (the default behavior). If this is a creation provisioner,
|
||||
taint the resource.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
resource "aws_instance" "web" {
|
||||
# ...
|
||||
|
||||
provisioner "local-exec" {
|
||||
command = "echo ${self.private_ip_address} > file.txt"
|
||||
on_failure = "continue"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -100,6 +100,20 @@ If you create an execution plan with a tainted resource, however, the
|
|||
plan will clearly state that the resource will be destroyed because
|
||||
it is tainted.
|
||||
|
||||
## Destroy Provisioners
|
||||
|
||||
Provisioners can also be defined that run only during a destroy
|
||||
operation. These are useful for performing system cleanup, extracting
|
||||
data, etc.
|
||||
|
||||
For many resources, using built-in cleanup mechanisms is recommended
|
||||
if possible (such as init scripts), but provisioners can be used if
|
||||
necessary.
|
||||
|
||||
The getting started guide won't show any destroy provisioner examples.
|
||||
If you need to use destroy provisioners, please
|
||||
[see the provisioner documentation](/docs/provisioners).
|
||||
|
||||
## Next
|
||||
|
||||
Provisioning is important for being able to bootstrap instances.
|
||||
|
|
Loading…
Reference in New Issue