Merge pull request #27549 from hashicorp/jbardin/provisioner-lifecycle
Provisioner lifecycle
This commit is contained in:
commit
0403d89083
|
@ -181,6 +181,5 @@ func (p *provisioner) Stop() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *provisioner) Close() error {
|
func (p *provisioner) Close() error {
|
||||||
p.cancel()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,6 @@ func (p *provisioner) Stop() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *provisioner) Close() error {
|
func (p *provisioner) Close() error {
|
||||||
p.cancel()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,6 @@ func (p *provisioner) Stop() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *provisioner) Close() error {
|
func (p *provisioner) Close() error {
|
||||||
p.cancel()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package e2etest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/e2e"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestProviderDevOverrides is a test that terraform can execute a 3rd party
|
||||||
|
// provisioner plugin.
|
||||||
|
func TestProvisioner(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// This test reaches out to releases.hashicorp.com to download the
|
||||||
|
// template and null providers, so it can only run if network access is
|
||||||
|
// allowed.
|
||||||
|
skipIfCannotAccessNetwork(t)
|
||||||
|
|
||||||
|
tf := e2e.NewBinary(terraformBin, "testdata/provisioner")
|
||||||
|
defer tf.Close()
|
||||||
|
|
||||||
|
//// INIT
|
||||||
|
_, stderr, err := tf.Run("init")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
//// PLAN
|
||||||
|
_, stderr, err = tf.Run("plan", "-out=tfplan")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
//// APPLY
|
||||||
|
stdout, stderr, err := tf.Run("apply", "tfplan")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout, "HelloProvisioner") {
|
||||||
|
t.Fatalf("missing provisioner output:\n%s", stdout)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
resource "null_resource" "a" {
|
||||||
|
provisioner "local-exec" {
|
||||||
|
command = "echo HelloProvisioner"
|
||||||
|
}
|
||||||
|
}
|
|
@ -9801,6 +9801,7 @@ func TestContext2Apply_plannedConnectionRefs(t *testing.T) {
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provisionerFactory := func() (provisioners.Interface, error) {
|
||||||
pr := testProvisioner()
|
pr := testProvisioner()
|
||||||
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
||||||
host := req.Connection.GetAttr("host")
|
host := req.Connection.GetAttr("host")
|
||||||
|
@ -9810,13 +9811,15 @@ func TestContext2Apply_plannedConnectionRefs(t *testing.T) {
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
return pr, nil
|
||||||
|
}
|
||||||
|
|
||||||
Providers := map[addrs.Provider]providers.Factory{
|
Providers := map[addrs.Provider]providers.Factory{
|
||||||
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
||||||
}
|
}
|
||||||
|
|
||||||
provisioners := map[string]provisioners.Factory{
|
provisioners := map[string]provisioners.Factory{
|
||||||
"shell": testProvisionerFuncFixed(pr),
|
"shell": provisionerFactory,
|
||||||
}
|
}
|
||||||
|
|
||||||
hook := &testHook{}
|
hook := &testHook{}
|
||||||
|
@ -12163,6 +12166,7 @@ output "out" {
|
||||||
func TestContext2Apply_provisionerSensitive(t *testing.T) {
|
func TestContext2Apply_provisionerSensitive(t *testing.T) {
|
||||||
m := testModule(t, "apply-provisioner-sensitive")
|
m := testModule(t, "apply-provisioner-sensitive")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
|
||||||
pr := testProvisioner()
|
pr := testProvisioner()
|
||||||
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
||||||
if req.Config.ContainsMarked() {
|
if req.Config.ContainsMarked() {
|
||||||
|
@ -12201,6 +12205,9 @@ func TestContext2Apply_provisionerSensitive(t *testing.T) {
|
||||||
t.Fatal("plan failed")
|
t.Fatal("plan failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "restart" provisioner
|
||||||
|
pr.CloseCalled = false
|
||||||
|
|
||||||
state, diags := ctx.Apply()
|
state, diags := ctx.Apply()
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
logDiagnostics(t, diags)
|
logDiagnostics(t, diags)
|
||||||
|
|
|
@ -77,22 +77,16 @@ type EvalContext interface {
|
||||||
ProviderInput(addrs.AbsProviderConfig) map[string]cty.Value
|
ProviderInput(addrs.AbsProviderConfig) map[string]cty.Value
|
||||||
SetProviderInput(addrs.AbsProviderConfig, map[string]cty.Value)
|
SetProviderInput(addrs.AbsProviderConfig, map[string]cty.Value)
|
||||||
|
|
||||||
// InitProvisioner initializes the provisioner with the given name.
|
// Provisioner gets the provisioner instance with the given name.
|
||||||
// It is an error to initialize the same provisioner more than once.
|
Provisioner(string) (provisioners.Interface, error)
|
||||||
InitProvisioner(string) error
|
|
||||||
|
|
||||||
// Provisioner gets the provisioner instance with the given name (already
|
|
||||||
// initialized) or returns nil if the provisioner isn't initialized.
|
|
||||||
Provisioner(string) provisioners.Interface
|
|
||||||
|
|
||||||
// ProvisionerSchema retrieves the main configuration schema for a
|
// ProvisionerSchema retrieves the main configuration schema for a
|
||||||
// particular provisioner, which must have already been initialized with
|
// particular provisioner, which must have already been initialized with
|
||||||
// InitProvisioner.
|
// InitProvisioner.
|
||||||
ProvisionerSchema(string) *configschema.Block
|
ProvisionerSchema(string) *configschema.Block
|
||||||
|
|
||||||
// CloseProvisioner closes provisioner connections that aren't needed
|
// CloseProvisioner closes all provisioner plugins.
|
||||||
// anymore.
|
CloseProvisioners() error
|
||||||
CloseProvisioner(string) error
|
|
||||||
|
|
||||||
// EvaluateBlock takes the given raw configuration block and associated
|
// EvaluateBlock takes the given raw configuration block and associated
|
||||||
// schema and evaluates it to produce a value of an object type that
|
// schema and evaluates it to produce a value of an object type that
|
||||||
|
|
|
@ -228,48 +228,41 @@ func (ctx *BuiltinEvalContext) SetProviderInput(pc addrs.AbsProviderConfig, c ma
|
||||||
ctx.ProviderLock.Unlock()
|
ctx.ProviderLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *BuiltinEvalContext) InitProvisioner(n string) error {
|
func (ctx *BuiltinEvalContext) Provisioner(n string) (provisioners.Interface, error) {
|
||||||
// If we already initialized, it is an error
|
|
||||||
if p := ctx.Provisioner(n); p != nil {
|
|
||||||
return fmt.Errorf("Provisioner '%s' already initialized", n)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warning: make sure to acquire these locks AFTER the call to Provisioner
|
|
||||||
// above, since it also acquires locks.
|
|
||||||
ctx.ProvisionerLock.Lock()
|
ctx.ProvisionerLock.Lock()
|
||||||
defer ctx.ProvisionerLock.Unlock()
|
defer ctx.ProvisionerLock.Unlock()
|
||||||
|
|
||||||
p, err := ctx.Components.ResourceProvisioner(n)
|
p, ok := ctx.ProvisionerCache[n]
|
||||||
|
if !ok {
|
||||||
|
var err error
|
||||||
|
p, err = ctx.Components.ResourceProvisioner(n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.ProvisionerCache[n] = p
|
ctx.ProvisionerCache[n] = p
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *BuiltinEvalContext) Provisioner(n string) provisioners.Interface {
|
return p, nil
|
||||||
ctx.ProvisionerLock.Lock()
|
|
||||||
defer ctx.ProvisionerLock.Unlock()
|
|
||||||
|
|
||||||
return ctx.ProvisionerCache[n]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *BuiltinEvalContext) ProvisionerSchema(n string) *configschema.Block {
|
func (ctx *BuiltinEvalContext) ProvisionerSchema(n string) *configschema.Block {
|
||||||
return ctx.Schemas.ProvisionerConfig(n)
|
return ctx.Schemas.ProvisionerConfig(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *BuiltinEvalContext) CloseProvisioner(n string) error {
|
func (ctx *BuiltinEvalContext) CloseProvisioners() error {
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
ctx.ProvisionerLock.Lock()
|
ctx.ProvisionerLock.Lock()
|
||||||
defer ctx.ProvisionerLock.Unlock()
|
defer ctx.ProvisionerLock.Unlock()
|
||||||
|
|
||||||
prov := ctx.ProvisionerCache[n]
|
for name, prov := range ctx.ProvisionerCache {
|
||||||
if prov != nil {
|
err := prov.Close()
|
||||||
return prov.Close()
|
if err != nil {
|
||||||
|
diags = diags.Append(fmt.Errorf("provisioner.Close %s: %s", name, err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return diags.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *BuiltinEvalContext) EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
|
func (ctx *BuiltinEvalContext) EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
|
||||||
|
|
|
@ -63,11 +63,6 @@ type MockEvalContext struct {
|
||||||
ConfigureProviderConfig cty.Value
|
ConfigureProviderConfig cty.Value
|
||||||
ConfigureProviderDiags tfdiags.Diagnostics
|
ConfigureProviderDiags tfdiags.Diagnostics
|
||||||
|
|
||||||
InitProvisionerCalled bool
|
|
||||||
InitProvisionerName string
|
|
||||||
InitProvisionerProvisioner provisioners.Interface
|
|
||||||
InitProvisionerError error
|
|
||||||
|
|
||||||
ProvisionerCalled bool
|
ProvisionerCalled bool
|
||||||
ProvisionerName string
|
ProvisionerName string
|
||||||
ProvisionerProvisioner provisioners.Interface
|
ProvisionerProvisioner provisioners.Interface
|
||||||
|
@ -76,9 +71,7 @@ type MockEvalContext struct {
|
||||||
ProvisionerSchemaName string
|
ProvisionerSchemaName string
|
||||||
ProvisionerSchemaSchema *configschema.Block
|
ProvisionerSchemaSchema *configschema.Block
|
||||||
|
|
||||||
CloseProvisionerCalled bool
|
CloseProvisionersCalled bool
|
||||||
CloseProvisionerName string
|
|
||||||
CloseProvisionerProvisioner provisioners.Interface
|
|
||||||
|
|
||||||
EvaluateBlockCalled bool
|
EvaluateBlockCalled bool
|
||||||
EvaluateBlockBody hcl.Body
|
EvaluateBlockBody hcl.Body
|
||||||
|
@ -208,16 +201,10 @@ func (c *MockEvalContext) SetProviderInput(addr addrs.AbsProviderConfig, vals ma
|
||||||
c.SetProviderInputValues = vals
|
c.SetProviderInputValues = vals
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *MockEvalContext) InitProvisioner(n string) error {
|
func (c *MockEvalContext) Provisioner(n string) (provisioners.Interface, error) {
|
||||||
c.InitProvisionerCalled = true
|
|
||||||
c.InitProvisionerName = n
|
|
||||||
return c.InitProvisionerError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockEvalContext) Provisioner(n string) provisioners.Interface {
|
|
||||||
c.ProvisionerCalled = true
|
c.ProvisionerCalled = true
|
||||||
c.ProvisionerName = n
|
c.ProvisionerName = n
|
||||||
return c.ProvisionerProvisioner
|
return c.ProvisionerProvisioner, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *MockEvalContext) ProvisionerSchema(n string) *configschema.Block {
|
func (c *MockEvalContext) ProvisionerSchema(n string) *configschema.Block {
|
||||||
|
@ -226,9 +213,8 @@ func (c *MockEvalContext) ProvisionerSchema(n string) *configschema.Block {
|
||||||
return c.ProvisionerSchemaSchema
|
return c.ProvisionerSchemaSchema
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *MockEvalContext) CloseProvisioner(n string) error {
|
func (c *MockEvalContext) CloseProvisioners() error {
|
||||||
c.CloseProvisionerCalled = true
|
c.CloseProvisionersCalled = true
|
||||||
c.CloseProvisionerName = n
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -108,10 +108,6 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
|
||||||
// Attach the configuration to any resources
|
// Attach the configuration to any resources
|
||||||
&AttachResourceConfigTransformer{Config: b.Config},
|
&AttachResourceConfigTransformer{Config: b.Config},
|
||||||
|
|
||||||
// Provisioner-related transformations
|
|
||||||
&MissingProvisionerTransformer{Provisioners: b.Components.ResourceProvisioners()},
|
|
||||||
&ProvisionerTransformer{},
|
|
||||||
|
|
||||||
// add providers
|
// add providers
|
||||||
TransformProviders(b.Components.ResourceProviders(), concreteProvider, b.Config),
|
TransformProviders(b.Components.ResourceProviders(), concreteProvider, b.Config),
|
||||||
|
|
||||||
|
@ -162,7 +158,6 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
|
||||||
|
|
||||||
// Close opened plugin connections
|
// Close opened plugin connections
|
||||||
&CloseProviderTransformer{},
|
&CloseProviderTransformer{},
|
||||||
&CloseProvisionerTransformer{},
|
|
||||||
|
|
||||||
// close the root module
|
// close the root module
|
||||||
&CloseRootModuleTransformer{},
|
&CloseRootModuleTransformer{},
|
||||||
|
|
|
@ -423,70 +423,6 @@ func TestApplyGraphBuilder_moduleDestroy(t *testing.T) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApplyGraphBuilder_provisioner(t *testing.T) {
|
|
||||||
changes := &plans.Changes{
|
|
||||||
Resources: []*plans.ResourceInstanceChangeSrc{
|
|
||||||
{
|
|
||||||
Addr: mustResourceInstanceAddr("test_object.foo"),
|
|
||||||
ChangeSrc: plans.ChangeSrc{
|
|
||||||
Action: plans.Create,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
b := &ApplyGraphBuilder{
|
|
||||||
Config: testModule(t, "graph-builder-apply-provisioner"),
|
|
||||||
Changes: changes,
|
|
||||||
Components: simpleMockComponentFactory(),
|
|
||||||
Schemas: simpleTestSchemas(),
|
|
||||||
}
|
|
||||||
|
|
||||||
g, err := b.Build(addrs.RootModuleInstance)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
testGraphContains(t, g, "provisioner.test")
|
|
||||||
testGraphHappensBefore(
|
|
||||||
t, g,
|
|
||||||
"provisioner.test",
|
|
||||||
"test_object.foo",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestApplyGraphBuilder_provisionerDestroy(t *testing.T) {
|
|
||||||
changes := &plans.Changes{
|
|
||||||
Resources: []*plans.ResourceInstanceChangeSrc{
|
|
||||||
{
|
|
||||||
Addr: mustResourceInstanceAddr("test_object.foo"),
|
|
||||||
ChangeSrc: plans.ChangeSrc{
|
|
||||||
Action: plans.Delete,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
b := &ApplyGraphBuilder{
|
|
||||||
Config: testModule(t, "graph-builder-apply-provisioner"),
|
|
||||||
Changes: changes,
|
|
||||||
Components: simpleMockComponentFactory(),
|
|
||||||
Schemas: simpleTestSchemas(),
|
|
||||||
}
|
|
||||||
|
|
||||||
g, err := b.Build(addrs.RootModuleInstance)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
testGraphContains(t, g, "provisioner.test")
|
|
||||||
testGraphHappensBefore(
|
|
||||||
t, g,
|
|
||||||
"provisioner.test",
|
|
||||||
"test_object.foo (destroy)",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestApplyGraphBuilder_targetModule(t *testing.T) {
|
func TestApplyGraphBuilder_targetModule(t *testing.T) {
|
||||||
changes := &plans.Changes{
|
changes := &plans.Changes{
|
||||||
Resources: []*plans.ResourceInstanceChangeSrc{
|
Resources: []*plans.ResourceInstanceChangeSrc{
|
||||||
|
@ -784,7 +720,6 @@ module.child.test_object.create
|
||||||
module.child.test_object.create (expand)
|
module.child.test_object.create (expand)
|
||||||
module.child (expand)
|
module.child (expand)
|
||||||
provider["registry.terraform.io/hashicorp/test"]
|
provider["registry.terraform.io/hashicorp/test"]
|
||||||
provisioner.test
|
|
||||||
module.child.test_object.other
|
module.child.test_object.other
|
||||||
module.child.test_object.create
|
module.child.test_object.create
|
||||||
module.child.test_object.other (expand)
|
module.child.test_object.other (expand)
|
||||||
|
@ -795,13 +730,9 @@ provider["registry.terraform.io/hashicorp/test"]
|
||||||
provider["registry.terraform.io/hashicorp/test"] (close)
|
provider["registry.terraform.io/hashicorp/test"] (close)
|
||||||
module.child.test_object.other
|
module.child.test_object.other
|
||||||
test_object.other
|
test_object.other
|
||||||
provisioner.test
|
|
||||||
provisioner.test (close)
|
|
||||||
module.child.test_object.create
|
|
||||||
root
|
root
|
||||||
meta.count-boundary (EachMode fixup)
|
meta.count-boundary (EachMode fixup)
|
||||||
provider["registry.terraform.io/hashicorp/test"] (close)
|
provider["registry.terraform.io/hashicorp/test"] (close)
|
||||||
provisioner.test (close)
|
|
||||||
test_object.create
|
test_object.create
|
||||||
test_object.create (expand)
|
test_object.create (expand)
|
||||||
test_object.create (expand)
|
test_object.create (expand)
|
||||||
|
|
|
@ -115,10 +115,6 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
||||||
// Attach the configuration to any resources
|
// Attach the configuration to any resources
|
||||||
&AttachResourceConfigTransformer{Config: b.Config},
|
&AttachResourceConfigTransformer{Config: b.Config},
|
||||||
|
|
||||||
// Provisioner-related transformations
|
|
||||||
&MissingProvisionerTransformer{Provisioners: b.Components.ResourceProvisioners()},
|
|
||||||
&ProvisionerTransformer{},
|
|
||||||
|
|
||||||
// add providers
|
// add providers
|
||||||
TransformProviders(b.Components.ResourceProviders(), b.ConcreteProvider, b.Config),
|
TransformProviders(b.Components.ResourceProviders(), b.ConcreteProvider, b.Config),
|
||||||
|
|
||||||
|
|
|
@ -141,6 +141,8 @@ func (n *nodeExpandModule) Execute(ctx EvalContext, op walkOperation) (diags tfd
|
||||||
// Besides providing a root node for dependency ordering, nodeCloseModule also
|
// Besides providing a root node for dependency ordering, nodeCloseModule also
|
||||||
// cleans up state after all the module nodes have been evaluated, removing
|
// cleans up state after all the module nodes have been evaluated, removing
|
||||||
// empty resources and modules from the state.
|
// empty resources and modules from the state.
|
||||||
|
// The root module instance also closes any remaining provisioner plugins which
|
||||||
|
// do not have a lifecycle controlled by individual graph nodes.
|
||||||
type nodeCloseModule struct {
|
type nodeCloseModule struct {
|
||||||
Addr addrs.Module
|
Addr addrs.Module
|
||||||
}
|
}
|
||||||
|
@ -174,6 +176,12 @@ func (n *nodeCloseModule) Name() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *nodeCloseModule) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
|
func (n *nodeCloseModule) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
|
||||||
|
if n.Addr.IsRoot() {
|
||||||
|
// If this is the root module, we are cleaning up the walk, so close
|
||||||
|
// any running provisioners
|
||||||
|
diags = diags.Append(ctx.CloseProvisioners())
|
||||||
|
}
|
||||||
|
|
||||||
switch op {
|
switch op {
|
||||||
case walkApply, walkDestroy:
|
case walkApply, walkDestroy:
|
||||||
state := ctx.State().Lock()
|
state := ctx.State().Lock()
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NodeProvisioner represents a provider that has no associated operations.
|
|
||||||
// It registers all the common interfaces across operations for providers.
|
|
||||||
type NodeProvisioner struct {
|
|
||||||
NameValue string
|
|
||||||
PathValue addrs.ModuleInstance
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ GraphNodeModuleInstance = (*NodeProvisioner)(nil)
|
|
||||||
_ GraphNodeProvisioner = (*NodeProvisioner)(nil)
|
|
||||||
_ GraphNodeExecutable = (*NodeProvisioner)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (n *NodeProvisioner) Name() string {
|
|
||||||
result := fmt.Sprintf("provisioner.%s", n.NameValue)
|
|
||||||
if len(n.PathValue) > 0 {
|
|
||||||
result = fmt.Sprintf("%s.%s", n.PathValue.String(), result)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeModuleInstance
|
|
||||||
func (n *NodeProvisioner) Path() addrs.ModuleInstance {
|
|
||||||
return n.PathValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeProvisioner
|
|
||||||
func (n *NodeProvisioner) ProvisionerName() string {
|
|
||||||
return n.NameValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeExecutable impl.
|
|
||||||
func (n *NodeProvisioner) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
|
|
||||||
return diags.Append(ctx.InitProvisioner(n.NameValue))
|
|
||||||
}
|
|
|
@ -1638,7 +1638,12 @@ func (n *NodeAbstractResourceInstance) applyProvisioners(ctx EvalContext, state
|
||||||
log.Printf("[TRACE] applyProvisioners: provisioning %s with %q", n.Addr, prov.Type)
|
log.Printf("[TRACE] applyProvisioners: provisioning %s with %q", n.Addr, prov.Type)
|
||||||
|
|
||||||
// Get the provisioner
|
// Get the provisioner
|
||||||
provisioner := ctx.Provisioner(prov.Type)
|
provisioner, err := ctx.Provisioner(prov.Type)
|
||||||
|
if err != nil {
|
||||||
|
diags = diags.Append(err)
|
||||||
|
return diags.Err()
|
||||||
|
}
|
||||||
|
|
||||||
schema := ctx.ProvisionerSchema(prov.Type)
|
schema := ctx.ProvisionerSchema(prov.Type)
|
||||||
|
|
||||||
config, configDiags := evalScope(ctx, prov.Config, self, schema)
|
config, configDiags := evalScope(ctx, prov.Config, self, schema)
|
||||||
|
|
|
@ -67,7 +67,12 @@ func (n *NodeValidatableResource) Execute(ctx EvalContext, op walkOperation) (di
|
||||||
func (n *NodeValidatableResource) validateProvisioner(ctx EvalContext, p *configs.Provisioner, hasCount, hasForEach bool) tfdiags.Diagnostics {
|
func (n *NodeValidatableResource) validateProvisioner(ctx EvalContext, p *configs.Provisioner, hasCount, hasForEach bool) tfdiags.Diagnostics {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
provisioner := ctx.Provisioner(p.Type)
|
provisioner, err := ctx.Provisioner(p.Type)
|
||||||
|
if err != nil {
|
||||||
|
diags = diags.Append(err)
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
|
||||||
if provisioner == nil {
|
if provisioner == nil {
|
||||||
return diags.Append(fmt.Errorf("provisioner %s not initialized", p.Type))
|
return diags.Append(fmt.Errorf("provisioner %s not initialized", p.Type))
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,8 +170,10 @@ func testProviderFuncFixed(rp providers.Interface) providers.Factory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testProvisionerFuncFixed(rp provisioners.Interface) provisioners.Factory {
|
func testProvisionerFuncFixed(rp *MockProvisioner) provisioners.Factory {
|
||||||
return func() (provisioners.Interface, error) {
|
return func() (provisioners.Interface, error) {
|
||||||
|
// make sure this provisioner has has not been closed
|
||||||
|
rp.CloseCalled = false
|
||||||
return rp, nil
|
return rp, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,182 +1,8 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
|
||||||
"github.com/hashicorp/terraform/dag"
|
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GraphNodeProvisioner is an interface that nodes that can be a provisioner
|
|
||||||
// must implement. The ProvisionerName returned is the name of the provisioner
|
|
||||||
// they satisfy.
|
|
||||||
type GraphNodeProvisioner interface {
|
|
||||||
ProvisionerName() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeCloseProvisioner is an interface that nodes that can be a close
|
|
||||||
// provisioner must implement. The CloseProvisionerName returned is the name
|
|
||||||
// of the provisioner they satisfy.
|
|
||||||
type GraphNodeCloseProvisioner interface {
|
|
||||||
CloseProvisionerName() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeProvisionerConsumer is an interface that nodes that require
|
// GraphNodeProvisionerConsumer is an interface that nodes that require
|
||||||
// a provisioner must implement. ProvisionedBy must return the names of the
|
// a provisioner must implement. ProvisionedBy must return the names of the
|
||||||
// provisioners to use.
|
// provisioners to use.
|
||||||
type GraphNodeProvisionerConsumer interface {
|
type GraphNodeProvisionerConsumer interface {
|
||||||
ProvisionedBy() []string
|
ProvisionedBy() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProvisionerTransformer is a GraphTransformer that maps resources to
|
|
||||||
// provisioners within the graph. This will error if there are any resources
|
|
||||||
// that don't map to proper resources.
|
|
||||||
type ProvisionerTransformer struct{}
|
|
||||||
|
|
||||||
func (t *ProvisionerTransformer) Transform(g *Graph) error {
|
|
||||||
// Go through the other nodes and match them to provisioners they need
|
|
||||||
var err error
|
|
||||||
m := provisionerVertexMap(g)
|
|
||||||
for _, v := range g.Vertices() {
|
|
||||||
if pv, ok := v.(GraphNodeProvisionerConsumer); ok {
|
|
||||||
for _, p := range pv.ProvisionedBy() {
|
|
||||||
if m[p] == nil {
|
|
||||||
err = multierror.Append(err, fmt.Errorf(
|
|
||||||
"%s: provisioner %s couldn't be found",
|
|
||||||
dag.VertexName(v), p))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[TRACE] ProvisionerTransformer: %s is provisioned by %s (%q)", dag.VertexName(v), p, dag.VertexName(m[p]))
|
|
||||||
g.Connect(dag.BasicEdge(v, m[p]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MissingProvisionerTransformer is a GraphTransformer that adds nodes
|
|
||||||
// for missing provisioners into the graph.
|
|
||||||
type MissingProvisionerTransformer struct {
|
|
||||||
// Provisioners is the list of provisioners we support.
|
|
||||||
Provisioners []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *MissingProvisionerTransformer) Transform(g *Graph) error {
|
|
||||||
// Create a set of our supported provisioners
|
|
||||||
supported := make(map[string]struct{}, len(t.Provisioners))
|
|
||||||
for _, v := range t.Provisioners {
|
|
||||||
supported[v] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the map of provisioners we already have in our graph
|
|
||||||
m := provisionerVertexMap(g)
|
|
||||||
|
|
||||||
// Go through all the provisioner consumers and make sure we add
|
|
||||||
// that provisioner if it is missing.
|
|
||||||
for _, v := range g.Vertices() {
|
|
||||||
pv, ok := v.(GraphNodeProvisionerConsumer)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range pv.ProvisionedBy() {
|
|
||||||
if _, ok := m[p]; ok {
|
|
||||||
// This provisioner already exists as a configure node
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := supported[p]; !ok {
|
|
||||||
// If we don't support the provisioner type, we skip it.
|
|
||||||
// Validation later will catch this as an error.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the vertex
|
|
||||||
var newV dag.Vertex = &NodeProvisioner{
|
|
||||||
NameValue: p,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the missing provisioner node to the graph
|
|
||||||
m[p] = g.Add(newV)
|
|
||||||
log.Printf("[TRACE] MissingProviderTransformer: added implicit provisioner %s, first implied by %s", p, dag.VertexName(v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseProvisionerTransformer is a GraphTransformer that adds nodes to the
|
|
||||||
// graph that will close open provisioner connections that aren't needed
|
|
||||||
// anymore. A provisioner connection is not needed anymore once all depended
|
|
||||||
// resources in the graph are evaluated.
|
|
||||||
type CloseProvisionerTransformer struct{}
|
|
||||||
|
|
||||||
func (t *CloseProvisionerTransformer) Transform(g *Graph) error {
|
|
||||||
m := closeProvisionerVertexMap(g)
|
|
||||||
for _, v := range g.Vertices() {
|
|
||||||
if pv, ok := v.(GraphNodeProvisionerConsumer); ok {
|
|
||||||
for _, p := range pv.ProvisionedBy() {
|
|
||||||
source := m[p]
|
|
||||||
|
|
||||||
if source == nil {
|
|
||||||
// Create a new graphNodeCloseProvisioner and add it to the graph
|
|
||||||
source = &graphNodeCloseProvisioner{ProvisionerNameValue: p}
|
|
||||||
g.Add(source)
|
|
||||||
|
|
||||||
// Make sure we also add the new graphNodeCloseProvisioner to the map
|
|
||||||
// so we don't create and add any duplicate graphNodeCloseProvisioners.
|
|
||||||
m[p] = source
|
|
||||||
}
|
|
||||||
|
|
||||||
g.Connect(dag.BasicEdge(source, v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func provisionerVertexMap(g *Graph) map[string]dag.Vertex {
|
|
||||||
m := make(map[string]dag.Vertex)
|
|
||||||
for _, v := range g.Vertices() {
|
|
||||||
if pv, ok := v.(GraphNodeProvisioner); ok {
|
|
||||||
m[pv.ProvisionerName()] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func closeProvisionerVertexMap(g *Graph) map[string]dag.Vertex {
|
|
||||||
m := make(map[string]dag.Vertex)
|
|
||||||
for _, v := range g.Vertices() {
|
|
||||||
if pv, ok := v.(GraphNodeCloseProvisioner); ok {
|
|
||||||
m[pv.CloseProvisionerName()] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
type graphNodeCloseProvisioner struct {
|
|
||||||
ProvisionerNameValue string
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ GraphNodeExecutable = (*graphNodeCloseProvisioner)(nil)
|
|
||||||
|
|
||||||
func (n *graphNodeCloseProvisioner) Name() string {
|
|
||||||
return fmt.Sprintf("provisioner.%s (close)", n.ProvisionerNameValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeExecutable impl.
|
|
||||||
func (n *graphNodeCloseProvisioner) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
|
|
||||||
return diags.Append(ctx.CloseProvisioner(n.ProvisionerNameValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeCloseProvisioner) CloseProvisionerName() string {
|
|
||||||
return n.ProvisionerNameValue
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,203 +0,0 @@
|
||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
|
||||||
"github.com/hashicorp/terraform/dag"
|
|
||||||
"github.com/hashicorp/terraform/states"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMissingProvisionerTransformer(t *testing.T) {
|
|
||||||
mod := testModule(t, "transform-provisioner-basic")
|
|
||||||
|
|
||||||
g := Graph{Path: addrs.RootModuleInstance}
|
|
||||||
{
|
|
||||||
tf := &ConfigTransformer{Config: mod}
|
|
||||||
if err := tf.Transform(&g); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
transform := &AttachResourceConfigTransformer{Config: mod}
|
|
||||||
if err := transform.Transform(&g); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
transform := &MissingProvisionerTransformer{Provisioners: []string{"shell"}}
|
|
||||||
if err := transform.Transform(&g); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
transform := &ProvisionerTransformer{}
|
|
||||||
if err := transform.Transform(&g); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := strings.TrimSpace(g.String())
|
|
||||||
expected := strings.TrimSpace(testTransformMissingProvisionerBasicStr)
|
|
||||||
if actual != expected {
|
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMissingProvisionerTransformer_module(t *testing.T) {
|
|
||||||
mod := testModule(t, "transform-provisioner-module")
|
|
||||||
|
|
||||||
g := Graph{Path: addrs.RootModuleInstance}
|
|
||||||
{
|
|
||||||
concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
state := states.BuildState(func(s *states.SyncState) {
|
|
||||||
s.SetResourceInstanceCurrent(
|
|
||||||
addrs.Resource{
|
|
||||||
Mode: addrs.ManagedResourceMode,
|
|
||||||
Type: "aws_instance",
|
|
||||||
Name: "foo",
|
|
||||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
||||||
&states.ResourceInstanceObjectSrc{
|
|
||||||
AttrsFlat: map[string]string{
|
|
||||||
"id": "foo",
|
|
||||||
},
|
|
||||||
Status: states.ObjectReady,
|
|
||||||
},
|
|
||||||
addrs.AbsProviderConfig{
|
|
||||||
Provider: addrs.NewDefaultProvider("aws"),
|
|
||||||
Module: addrs.RootModule,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
s.SetResourceInstanceCurrent(
|
|
||||||
addrs.Resource{
|
|
||||||
Mode: addrs.ManagedResourceMode,
|
|
||||||
Type: "aws_instance",
|
|
||||||
Name: "foo",
|
|
||||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)),
|
|
||||||
&states.ResourceInstanceObjectSrc{
|
|
||||||
AttrsFlat: map[string]string{
|
|
||||||
"id": "foo",
|
|
||||||
},
|
|
||||||
Status: states.ObjectReady,
|
|
||||||
},
|
|
||||||
addrs.AbsProviderConfig{
|
|
||||||
Provider: addrs.NewDefaultProvider("aws"),
|
|
||||||
Module: addrs.RootModule,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
tf := &StateTransformer{
|
|
||||||
ConcreteCurrent: concreteResource,
|
|
||||||
State: state,
|
|
||||||
}
|
|
||||||
if err := tf.Transform(&g); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
t.Logf("graph after StateTransformer:\n%s", g.StringWithNodeTypes())
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
transform := &AttachResourceConfigTransformer{Config: mod}
|
|
||||||
if err := transform.Transform(&g); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
transform := &MissingProvisionerTransformer{Provisioners: []string{"shell"}}
|
|
||||||
if err := transform.Transform(&g); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
t.Logf("graph after MissingProvisionerTransformer:\n%s", g.StringWithNodeTypes())
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
transform := &ProvisionerTransformer{}
|
|
||||||
if err := transform.Transform(&g); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
t.Logf("graph after ProvisionerTransformer:\n%s", g.StringWithNodeTypes())
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := strings.TrimSpace(g.String())
|
|
||||||
expected := strings.TrimSpace(testTransformMissingProvisionerModuleStr)
|
|
||||||
if actual != expected {
|
|
||||||
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCloseProvisionerTransformer(t *testing.T) {
|
|
||||||
mod := testModule(t, "transform-provisioner-basic")
|
|
||||||
|
|
||||||
g := Graph{Path: addrs.RootModuleInstance}
|
|
||||||
{
|
|
||||||
tf := &ConfigTransformer{Config: mod}
|
|
||||||
if err := tf.Transform(&g); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
transform := &AttachResourceConfigTransformer{Config: mod}
|
|
||||||
if err := transform.Transform(&g); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
transform := &MissingProvisionerTransformer{Provisioners: []string{"shell"}}
|
|
||||||
if err := transform.Transform(&g); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
transform := &ProvisionerTransformer{}
|
|
||||||
if err := transform.Transform(&g); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
transform := &CloseProvisionerTransformer{}
|
|
||||||
if err := transform.Transform(&g); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := strings.TrimSpace(g.String())
|
|
||||||
expected := strings.TrimSpace(testTransformCloseProvisionerBasicStr)
|
|
||||||
if actual != expected {
|
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const testTransformMissingProvisionerBasicStr = `
|
|
||||||
aws_instance.web
|
|
||||||
provisioner.shell
|
|
||||||
provisioner.shell
|
|
||||||
`
|
|
||||||
|
|
||||||
const testTransformMissingProvisionerModuleStr = `
|
|
||||||
aws_instance.foo
|
|
||||||
provisioner.shell
|
|
||||||
module.child.aws_instance.foo
|
|
||||||
provisioner.shell
|
|
||||||
provisioner.shell
|
|
||||||
`
|
|
||||||
|
|
||||||
const testTransformCloseProvisionerBasicStr = `
|
|
||||||
aws_instance.web
|
|
||||||
provisioner.shell
|
|
||||||
provisioner.shell
|
|
||||||
provisioner.shell (close)
|
|
||||||
aws_instance.web
|
|
||||||
`
|
|
Loading…
Reference in New Issue