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
|
Type string
|
||||||
RawConfig *RawConfig
|
RawConfig *RawConfig
|
||||||
ConnInfo *RawConfig
|
ConnInfo *RawConfig
|
||||||
|
|
||||||
|
When ProvisionerWhen
|
||||||
|
OnFailure ProvisionerOnFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy returns a copy of this Provisioner
|
// Copy returns a copy of this Provisioner
|
||||||
|
@ -144,6 +147,8 @@ func (p *Provisioner) Copy() *Provisioner {
|
||||||
Type: p.Type,
|
Type: p.Type,
|
||||||
RawConfig: p.RawConfig.Copy(),
|
RawConfig: p.RawConfig.Copy(),
|
||||||
ConnInfo: p.ConnInfo.Copy(),
|
ConnInfo: p.ConnInfo.Copy(),
|
||||||
|
When: p.When,
|
||||||
|
OnFailure: p.OnFailure,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -553,7 +558,7 @@ func (c *Config) Validate() error {
|
||||||
// Validate DependsOn
|
// Validate DependsOn
|
||||||
errs = append(errs, c.validateDependsOn(n, r.DependsOn, resources, modules)...)
|
errs = append(errs, c.validateDependsOn(n, r.DependsOn, resources, modules)...)
|
||||||
|
|
||||||
// Verify provisioners don't contain any splats
|
// Verify provisioners
|
||||||
for _, p := range r.Provisioners {
|
for _, p := range r.Provisioners {
|
||||||
// This validation checks that there are now splat variables
|
// This validation checks that there are now splat variables
|
||||||
// referencing ourself. This currently is not allowed.
|
// referencing ourself. This currently is not allowed.
|
||||||
|
@ -585,6 +590,17 @@ func (c *Config) Validate() error {
|
||||||
break
|
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
|
// Verify ignore_changes contains valid entries
|
||||||
|
|
|
@ -214,7 +214,16 @@ func resourcesStr(rs []*Resource) string {
|
||||||
if len(r.Provisioners) > 0 {
|
if len(r.Provisioners) > 0 {
|
||||||
result += fmt.Sprintf(" provisioners\n")
|
result += fmt.Sprintf(" provisioners\n")
|
||||||
for _, p := range r.Provisioners {
|
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))
|
ks := make([]string, 0, len(p.RawConfig.Raw))
|
||||||
for k, _ := range p.RawConfig.Raw {
|
for k, _ := range p.RawConfig.Raw {
|
||||||
|
|
|
@ -168,6 +168,13 @@ func TestConfigValidate_table(t *testing.T) {
|
||||||
true,
|
true,
|
||||||
"data sources cannot have",
|
"data sources cannot have",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"basic provisioners",
|
||||||
|
"validate-basic-provisioners",
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range cases {
|
for i, tc := range cases {
|
||||||
|
|
|
@ -849,8 +849,40 @@ func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{})
|
||||||
return nil, err
|
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, "connection")
|
||||||
|
delete(config, "when")
|
||||||
|
delete(config, "on_failure")
|
||||||
|
|
||||||
rawConfig, err := NewRawConfig(config)
|
rawConfig, err := NewRawConfig(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -889,6 +921,8 @@ func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{})
|
||||||
Type: n,
|
Type: n,
|
||||||
RawConfig: rawConfig,
|
RawConfig: rawConfig,
|
||||||
ConnInfo: connRaw,
|
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) {
|
func TestLoadFile_unnamedOutput(t *testing.T) {
|
||||||
_, err := LoadFile(filepath.Join(fixtureDir, "output-unnamed.tf"))
|
_, err := LoadFile(filepath.Join(fixtureDir, "output-unnamed.tf"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -1126,6 +1142,17 @@ aws_instance.web (x1)
|
||||||
user: var.foo
|
user: var.foo
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const provisionerDestroyResourcesStr = `
|
||||||
|
aws_instance.web (x1)
|
||||||
|
provisioners
|
||||||
|
shell
|
||||||
|
shell (destroy)
|
||||||
|
path
|
||||||
|
shell (destroy)
|
||||||
|
on_failure = continue
|
||||||
|
path
|
||||||
|
`
|
||||||
|
|
||||||
const connectionResourcesStr = `
|
const connectionResourcesStr = `
|
||||||
aws_instance.web (x1)
|
aws_instance.web (x1)
|
||||||
ami
|
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) {
|
func TestContext2Apply_provisionerResourceRef(t *testing.T) {
|
||||||
m := testModule(t, "apply-provisioner-resource-ref")
|
m := testModule(t, "apply-provisioner-resource-ref")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
|
|
@ -413,7 +413,7 @@ func (*DebugHook) PreProvision(ii *InstanceInfo, s string) (HookAction, error) {
|
||||||
return HookActionContinue, nil
|
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 {
|
if dbug == nil {
|
||||||
return HookActionContinue, nil
|
return HookActionContinue, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,7 +156,7 @@ func TestDebugHook_nilArgs(t *testing.T) {
|
||||||
h.PostApply(nil, nil, nil)
|
h.PostApply(nil, nil, nil)
|
||||||
h.PostDiff(nil, nil)
|
h.PostDiff(nil, nil)
|
||||||
h.PostImportState(nil, nil)
|
h.PostImportState(nil, nil)
|
||||||
h.PostProvision(nil, "")
|
h.PostProvision(nil, "", nil)
|
||||||
h.PostProvisionResource(nil, nil)
|
h.PostProvisionResource(nil, nil)
|
||||||
h.PostRefresh(nil, nil)
|
h.PostRefresh(nil, nil)
|
||||||
h.PostStateUpdate(nil)
|
h.PostStateUpdate(nil)
|
||||||
|
|
|
@ -52,16 +52,6 @@ func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
*n.CreateNew = state.ID == "" && !diff.GetDestroy() || diff.RequiresNew()
|
*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!
|
// With the completed diff, apply!
|
||||||
log.Printf("[DEBUG] apply: %s: executing Apply", n.Info.Id)
|
log.Printf("[DEBUG] apply: %s: executing Apply", n.Info.Id)
|
||||||
state, err := provider.Apply(n.Info, state, diff)
|
state, err := provider.Apply(n.Info, state, diff)
|
||||||
|
@ -104,6 +94,37 @@ func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
return nil, nil
|
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
|
// EvalApplyPost is an EvalNode implementation that does the post-Apply work
|
||||||
type EvalApplyPost struct {
|
type EvalApplyPost struct {
|
||||||
Info *InstanceInfo
|
Info *InstanceInfo
|
||||||
|
@ -140,25 +161,33 @@ type EvalApplyProvisioners struct {
|
||||||
InterpResource *Resource
|
InterpResource *Resource
|
||||||
CreateNew *bool
|
CreateNew *bool
|
||||||
Error *error
|
Error *error
|
||||||
|
|
||||||
|
// When is the type of provisioner to run at this point
|
||||||
|
When config.ProvisionerWhen
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
// TODO: test
|
||||||
func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
|
func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
state := *n.State
|
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
|
// If we're not creating a new resource, then don't run provisioners
|
||||||
return nil, nil
|
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
|
// We have no provisioners, so don't do anything
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// taint tells us whether to enable tainting.
|
||||||
|
taint := n.When == config.ProvisionerWhenCreate
|
||||||
|
|
||||||
if n.Error != nil && *n.Error != nil {
|
if n.Error != nil && *n.Error != nil {
|
||||||
// We're already errored creating, so mark as tainted and continue
|
if taint {
|
||||||
state.Tainted = true
|
state.Tainted = true
|
||||||
|
}
|
||||||
|
|
||||||
// We're already tainted, so just return out
|
// We're already tainted, so just return out
|
||||||
return nil, nil
|
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 there are no errors, then we append it to our output error
|
||||||
// if we have one, otherwise we just output it.
|
// if we have one, otherwise we just output it.
|
||||||
err := n.apply(ctx)
|
err := n.apply(ctx, provs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Provisioning failed, so mark the resource as tainted
|
if taint {
|
||||||
state.Tainted = true
|
state.Tainted = true
|
||||||
|
}
|
||||||
|
|
||||||
if n.Error != nil {
|
if n.Error != nil {
|
||||||
*n.Error = multierror.Append(*n.Error, err)
|
*n.Error = multierror.Append(*n.Error, err)
|
||||||
|
@ -201,7 +231,29 @@ func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
return nil, nil
|
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
|
state := *n.State
|
||||||
|
|
||||||
// Store the original connection info, restore later
|
// Store the original connection info, restore later
|
||||||
|
@ -210,7 +262,7 @@ func (n *EvalApplyProvisioners) apply(ctx EvalContext) error {
|
||||||
state.Ephemeral.ConnInfo = origConnInfo
|
state.Ephemeral.ConnInfo = origConnInfo
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for _, prov := range n.Resource.Provisioners {
|
for _, prov := range provs {
|
||||||
// Get the provisioner
|
// Get the provisioner
|
||||||
provisioner := ctx.Provisioner(prov.Type)
|
provisioner := ctx.Provisioner(prov.Type)
|
||||||
|
|
||||||
|
@ -275,18 +327,30 @@ func (n *EvalApplyProvisioners) apply(ctx EvalContext) error {
|
||||||
|
|
||||||
// Invoke the Provisioner
|
// Invoke the Provisioner
|
||||||
output := CallbackUIOutput{OutputFn: outputFn}
|
output := CallbackUIOutput{OutputFn: outputFn}
|
||||||
if err := provisioner.Apply(&output, state, provConfig); err != nil {
|
applyErr := provisioner.Apply(&output, state, provConfig)
|
||||||
return err
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
// Deal with the hook
|
||||||
// Call post hook
|
if hookErr != nil {
|
||||||
err := ctx.Hook(func(h Hook) (HookAction, error) {
|
return hookErr
|
||||||
return h.PostProvision(n.Info, prov.Type)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,13 +96,8 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
|
||||||
),
|
),
|
||||||
|
|
||||||
// Provisioner-related transformations
|
// Provisioner-related transformations
|
||||||
GraphTransformIf(
|
&MissingProvisionerTransformer{Provisioners: b.Provisioners},
|
||||||
func() bool { return !b.Destroy },
|
&ProvisionerTransformer{},
|
||||||
GraphTransformMulti(
|
|
||||||
&MissingProvisionerTransformer{Provisioners: b.Provisioners},
|
|
||||||
&ProvisionerTransformer{},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Add root variables
|
// Add root variables
|
||||||
&RootVariableTransformer{Module: b.Module},
|
&RootVariableTransformer{Module: b.Module},
|
||||||
|
|
|
@ -232,6 +232,78 @@ func TestApplyGraphBuilder_moduleDestroy(t *testing.T) {
|
||||||
"module.A.null_resource.foo (destroy)")
|
"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 = `
|
const testApplyGraphBuilderStr = `
|
||||||
aws_instance.create
|
aws_instance.create
|
||||||
provider.aws
|
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
|
// testGraphHappensBefore is an assertion helper that tests that node
|
||||||
// A (dag.VertexName value) happens before node B.
|
// A (dag.VertexName value) happens before node B.
|
||||||
func testGraphHappensBefore(t *testing.T, g *Graph, A, B string) {
|
func testGraphHappensBefore(t *testing.T, g *Graph, A, B string) {
|
||||||
|
|
|
@ -42,7 +42,7 @@ type Hook interface {
|
||||||
PreProvisionResource(*InstanceInfo, *InstanceState) (HookAction, error)
|
PreProvisionResource(*InstanceInfo, *InstanceState) (HookAction, error)
|
||||||
PostProvisionResource(*InstanceInfo, *InstanceState) (HookAction, error)
|
PostProvisionResource(*InstanceInfo, *InstanceState) (HookAction, error)
|
||||||
PreProvision(*InstanceInfo, string) (HookAction, error)
|
PreProvision(*InstanceInfo, string) (HookAction, error)
|
||||||
PostProvision(*InstanceInfo, string) (HookAction, error)
|
PostProvision(*InstanceInfo, string, error) (HookAction, error)
|
||||||
ProvisionOutput(*InstanceInfo, string, string)
|
ProvisionOutput(*InstanceInfo, string, string)
|
||||||
|
|
||||||
// PreRefresh and PostRefresh are called before and after a single
|
// PreRefresh and PostRefresh are called before and after a single
|
||||||
|
@ -92,7 +92,7 @@ func (*NilHook) PreProvision(*InstanceInfo, string) (HookAction, error) {
|
||||||
return HookActionContinue, nil
|
return HookActionContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*NilHook) PostProvision(*InstanceInfo, string) (HookAction, error) {
|
func (*NilHook) PostProvision(*InstanceInfo, string, error) (HookAction, error) {
|
||||||
return HookActionContinue, nil
|
return HookActionContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,7 @@ type MockHook struct {
|
||||||
PostProvisionCalled bool
|
PostProvisionCalled bool
|
||||||
PostProvisionInfo *InstanceInfo
|
PostProvisionInfo *InstanceInfo
|
||||||
PostProvisionProvisionerId string
|
PostProvisionProvisionerId string
|
||||||
|
PostProvisionErrorArg error
|
||||||
PostProvisionReturn HookAction
|
PostProvisionReturn HookAction
|
||||||
PostProvisionError error
|
PostProvisionError error
|
||||||
|
|
||||||
|
@ -170,13 +171,14 @@ func (h *MockHook) PreProvision(n *InstanceInfo, provId string) (HookAction, err
|
||||||
return h.PreProvisionReturn, h.PreProvisionError
|
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()
|
h.Lock()
|
||||||
defer h.Unlock()
|
defer h.Unlock()
|
||||||
|
|
||||||
h.PostProvisionCalled = true
|
h.PostProvisionCalled = true
|
||||||
h.PostProvisionInfo = n
|
h.PostProvisionInfo = n
|
||||||
h.PostProvisionProvisionerId = provId
|
h.PostProvisionProvisionerId = provId
|
||||||
|
h.PostProvisionErrorArg = err
|
||||||
return h.PostProvisionReturn, h.PostProvisionError
|
return h.PostProvisionReturn, h.PostProvisionError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ func (h *stopHook) PreProvision(*InstanceInfo, string) (HookAction, error) {
|
||||||
return h.hook()
|
return h.hook()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *stopHook) PostProvision(*InstanceInfo, string) (HookAction, error) {
|
func (h *stopHook) PostProvision(*InstanceInfo, string, error) (HookAction, error) {
|
||||||
return h.hook()
|
return h.hook()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -298,6 +298,12 @@ func (n *NodeApplyableResource) evalTreeManagedResource(
|
||||||
Name: stateId,
|
Name: stateId,
|
||||||
Output: &state,
|
Output: &state,
|
||||||
},
|
},
|
||||||
|
// Call pre-apply hook
|
||||||
|
&EvalApplyPre{
|
||||||
|
Info: info,
|
||||||
|
State: &state,
|
||||||
|
Diff: &diffApply,
|
||||||
|
},
|
||||||
&EvalApply{
|
&EvalApply{
|
||||||
Info: info,
|
Info: info,
|
||||||
State: &state,
|
State: &state,
|
||||||
|
@ -321,6 +327,7 @@ func (n *NodeApplyableResource) evalTreeManagedResource(
|
||||||
InterpResource: resource,
|
InterpResource: resource,
|
||||||
CreateNew: &createNew,
|
CreateNew: &createNew,
|
||||||
Error: &err,
|
Error: &err,
|
||||||
|
When: config.ProvisionerWhenCreate,
|
||||||
},
|
},
|
||||||
&EvalIf{
|
&EvalIf{
|
||||||
If: func(ctx EvalContext) (bool, error) {
|
If: func(ctx EvalContext) (bool, error) {
|
||||||
|
|
|
@ -107,6 +107,17 @@ func (n *NodeDestroyResource) EvalTree() EvalNode {
|
||||||
uniqueExtra: "destroy",
|
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
|
// Get our state
|
||||||
rs := n.ResourceState
|
rs := n.ResourceState
|
||||||
if rs == nil {
|
if rs == nil {
|
||||||
|
@ -160,6 +171,48 @@ func (n *NodeDestroyResource) EvalTree() EvalNode {
|
||||||
&EvalRequireState{
|
&EvalRequireState{
|
||||||
State: &state,
|
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.
|
// Make sure we handle data sources properly.
|
||||||
&EvalIf{
|
&EvalIf{
|
||||||
If: func(ctx EvalContext) (bool, error) {
|
If: func(ctx EvalContext) (bool, error) {
|
||||||
|
|
|
@ -179,6 +179,10 @@ func (h *HookRecordApplyOrder) PreApply(
|
||||||
info *InstanceInfo,
|
info *InstanceInfo,
|
||||||
s *InstanceState,
|
s *InstanceState,
|
||||||
d *InstanceDiff) (HookAction, error) {
|
d *InstanceDiff) (HookAction, error) {
|
||||||
|
if d.Empty() {
|
||||||
|
return HookActionContinue, nil
|
||||||
|
}
|
||||||
|
|
||||||
if h.Active {
|
if h.Active {
|
||||||
h.l.Lock()
|
h.l.Lock()
|
||||||
defer h.l.Unlock()
|
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,
|
State: &state,
|
||||||
Output: &diff,
|
Output: &diff,
|
||||||
},
|
},
|
||||||
|
// Call pre-apply hook
|
||||||
|
&EvalApplyPre{
|
||||||
|
Info: info,
|
||||||
|
State: &state,
|
||||||
|
Diff: &diff,
|
||||||
|
},
|
||||||
&EvalApply{
|
&EvalApply{
|
||||||
Info: info,
|
Info: info,
|
||||||
State: &state,
|
State: &state,
|
||||||
|
|
|
@ -321,6 +321,11 @@ func (n *graphNodeOrphanResource) managedResourceEvalNodes(info *InstanceInfo) [
|
||||||
Name: n.ResourceKey.String(),
|
Name: n.ResourceKey.String(),
|
||||||
Output: &state,
|
Output: &state,
|
||||||
},
|
},
|
||||||
|
&EvalApplyPre{
|
||||||
|
Info: info,
|
||||||
|
State: &state,
|
||||||
|
Diff: &diff,
|
||||||
|
},
|
||||||
&EvalApply{
|
&EvalApply{
|
||||||
Info: info,
|
Info: info,
|
||||||
State: &state,
|
State: &state,
|
||||||
|
|
|
@ -500,6 +500,11 @@ func (n *graphNodeExpandedResource) managedResourceEvalNodes(resource *Resource,
|
||||||
Name: n.stateId(),
|
Name: n.stateId(),
|
||||||
Output: &state,
|
Output: &state,
|
||||||
},
|
},
|
||||||
|
&EvalApplyPre{
|
||||||
|
Info: info,
|
||||||
|
State: &state,
|
||||||
|
Diff: &diffApply,
|
||||||
|
},
|
||||||
&EvalApply{
|
&EvalApply{
|
||||||
Info: info,
|
Info: info,
|
||||||
State: &state,
|
State: &state,
|
||||||
|
|
|
@ -295,6 +295,9 @@ where `PROVISIONER` is:
|
||||||
provisioner NAME {
|
provisioner NAME {
|
||||||
CONFIG ...
|
CONFIG ...
|
||||||
|
|
||||||
|
[when = "create"|"destroy"]
|
||||||
|
[on_failure = "continue"|"fail"]
|
||||||
|
|
||||||
[CONNECTION]
|
[CONNECTION]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -3,15 +3,104 @@ layout: "docs"
|
||||||
page_title: "Provisioners"
|
page_title: "Provisioners"
|
||||||
sidebar_current: "docs-provisioners"
|
sidebar_current: "docs-provisioners"
|
||||||
description: |-
|
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
|
# Provisioners
|
||||||
|
|
||||||
When a resource is initially created, provisioners can be executed to
|
Provisioners are used to execute scripts on a local or remote machine
|
||||||
initialize that resource. This can be used to add resources to an inventory
|
as part of resource creation or destruction. Provisioners can be used to
|
||||||
management system, run a configuration management tool, bootstrap the
|
bootstrap a resource, cleanup before destroy, run configuration management, etc.
|
||||||
resource into a cluster, 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
|
plan will clearly state that the resource will be destroyed because
|
||||||
it is tainted.
|
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
|
## Next
|
||||||
|
|
||||||
Provisioning is important for being able to bootstrap instances.
|
Provisioning is important for being able to bootstrap instances.
|
||||||
|
|
Loading…
Reference in New Issue