configs: Provisioners in "removed" blocks

When the removed_provisioners experiment is active, removed blocks
referring to managed resources are allowed to include "connection" and
"provisioner" blocks, as long as all of the "provisioner" blocks specify
when = destroy to indicate that they should execute as part of the
resource's "destroy" action.

This commit only deals with parsing the configuration. The logic to react
to this during the apply phase will follow in later commits.
pull/35230/head
Martin Atkins 2 years ago committed by James Bardin
parent c61201dd10
commit 5e545ff427

@ -4,6 +4,8 @@
package configs
import (
"fmt"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/hcl/v2"
@ -19,6 +21,12 @@ type Removed struct {
// from state. Defaults to true.
Destroy bool
// Managed captures a number of metadata fields that are applicable only
// for managed resources, and not for other resource modes.
//
// "removed" blocks support only a subset of the fields in [ManagedResource].
Managed *ManagedResource
DeclRange hcl.Range
}
@ -31,6 +39,8 @@ func decodeRemovedBlock(block *hcl.Block) (*Removed, hcl.Diagnostics) {
content, moreDiags := block.Body.Content(removedBlockSchema)
diags = append(diags, moreDiags...)
var targetKind addrs.RemoveTargetKind
var resourceMode addrs.ResourceMode // only valid if targetKind is addrs.RemoveTargetResource
if attr, exists := content.Attributes["from"]; exists {
from, traversalDiags := hcl.AbsTraversalForExpr(attr.Expr)
diags = append(diags, traversalDiags...)
@ -38,11 +48,21 @@ func decodeRemovedBlock(block *hcl.Block) (*Removed, hcl.Diagnostics) {
from, fromDiags := addrs.ParseRemoveTarget(from)
diags = append(diags, fromDiags.ToHCL()...)
removed.From = from
if removed.From != nil {
targetKind = removed.From.ObjectKind()
if targetKind == addrs.RemoveTargetResource {
resourceMode = removed.From.RelSubject.(addrs.ConfigResource).Resource.Mode
}
}
}
}
removed.Destroy = true
if resourceMode == addrs.ManagedResourceMode {
removed.Managed = &ManagedResource{}
}
var seenConnection *hcl.Block
for _, block := range content.Blocks {
switch block.Type {
case "lifecycle":
@ -53,6 +73,61 @@ func decodeRemovedBlock(block *hcl.Block) (*Removed, hcl.Diagnostics) {
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &removed.Destroy)
diags = append(diags, valDiags...)
}
case "connection":
if removed.Managed == nil {
// target is not a managed resource, then
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid connection block",
Detail: "Provisioner connection configuration is valid only when a removed block targets a managed resource.",
Subject: &block.DefRange,
})
continue
}
if seenConnection != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Duplicate connection block",
Detail: fmt.Sprintf("This \"removed\" block already has a connection block at %s.", seenConnection.DefRange),
Subject: &block.DefRange,
})
continue
}
seenConnection = block
removed.Managed.Connection = &Connection{
Config: block.Body,
DeclRange: block.DefRange,
}
case "provisioner":
if removed.Managed == nil {
// target is not a managed resource, then
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provisioner block",
Detail: "Provisioners are valid only when a removed block targets a managed resource.",
Subject: &block.DefRange,
})
continue
}
pv, pvDiags := decodeProvisionerBlock(block)
diags = append(diags, pvDiags...)
if pv != nil {
removed.Managed.Provisioners = append(removed.Managed.Provisioners, pv)
if pv.When != ProvisionerWhenDestroy {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provisioner block",
Detail: "Only destroy-time provisioners are valid in \"removed\" blocks. To declare a destroy-time provisioner, use:\n when = destroy",
Subject: &block.DefRange,
})
}
}
}
}
@ -67,9 +142,9 @@ var removedBlockSchema = &hcl.BodySchema{
},
},
Blocks: []hcl.BlockHeaderSchema{
{
Type: "lifecycle",
},
{Type: "lifecycle"},
{Type: "connection"},
{Type: "provisioner", LabelNames: []string{"type"}},
},
}

@ -60,6 +60,7 @@ func TestRemovedBlock_decode(t *testing.T) {
&Removed{
From: mustRemoveEndpointFromExpr(foo_expr),
Destroy: true,
Managed: &ManagedResource{},
DeclRange: blockRange,
},
``,
@ -93,10 +94,155 @@ func TestRemovedBlock_decode(t *testing.T) {
&Removed{
From: mustRemoveEndpointFromExpr(foo_expr),
Destroy: false,
Managed: &ManagedResource{},
DeclRange: blockRange,
},
``,
},
"provisioner when = destroy": {
&hcl.Block{
Type: "removed",
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcl.Attributes{
"from": {
Name: "from",
Expr: foo_expr,
},
},
Blocks: hcl.Blocks{
&hcl.Block{
Type: "provisioner",
Labels: []string{"remote-exec"},
LabelRanges: []hcl.Range{{}},
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcl.Attributes{
"when": {
Name: "when",
Expr: hcltest.MockExprTraversalSrc("destroy"),
},
},
}),
},
},
}),
DefRange: blockRange,
},
&Removed{
From: mustRemoveEndpointFromExpr(foo_expr),
Destroy: true,
Managed: &ManagedResource{
Provisioners: []*Provisioner{
{
Type: "remote-exec",
Config: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcl.Attributes{},
Blocks: hcl.Blocks{},
}),
When: ProvisionerWhenDestroy,
OnFailure: ProvisionerOnFailureFail,
},
},
},
DeclRange: blockRange,
},
``,
},
"provisioner when = create": {
&hcl.Block{
Type: "removed",
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcl.Attributes{
"from": {
Name: "from",
Expr: foo_expr,
},
},
Blocks: hcl.Blocks{
&hcl.Block{
Type: "provisioner",
Labels: []string{"local-exec"},
LabelRanges: []hcl.Range{{}},
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcl.Attributes{
"when": {
Name: "when",
Expr: hcltest.MockExprTraversalSrc("create"),
},
},
}),
},
},
}),
DefRange: blockRange,
},
&Removed{
From: mustRemoveEndpointFromExpr(foo_expr),
Destroy: true,
Managed: &ManagedResource{
Provisioners: []*Provisioner{
{
Type: "local-exec",
Config: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcl.Attributes{},
Blocks: hcl.Blocks{},
}),
When: ProvisionerWhenCreate,
OnFailure: ProvisionerOnFailureFail,
},
},
},
DeclRange: blockRange,
},
`Invalid provisioner block`,
},
"provisioner no when": {
&hcl.Block{
Type: "removed",
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcl.Attributes{
"from": {
Name: "from",
Expr: foo_expr,
},
},
Blocks: hcl.Blocks{
&hcl.Block{
Type: "connection",
Body: hcltest.MockBody(&hcl.BodyContent{}),
},
&hcl.Block{
Type: "provisioner",
Labels: []string{"local-exec"},
LabelRanges: []hcl.Range{{}},
Body: hcltest.MockBody(&hcl.BodyContent{}),
},
},
}),
DefRange: blockRange,
},
&Removed{
From: mustRemoveEndpointFromExpr(foo_expr),
Destroy: true,
Managed: &ManagedResource{
Connection: &Connection{
Config: hcltest.MockBody(&hcl.BodyContent{}),
},
Provisioners: []*Provisioner{
{
Type: "local-exec",
Config: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcl.Attributes{},
Blocks: hcl.Blocks{},
}),
When: ProvisionerWhenCreate,
OnFailure: ProvisionerOnFailureFail,
},
},
},
DeclRange: blockRange,
},
`Invalid provisioner block`,
},
"modules": {
&hcl.Block{
Type: "removed",
@ -130,6 +276,67 @@ func TestRemovedBlock_decode(t *testing.T) {
},
``,
},
"provisioner for module": {
&hcl.Block{
Type: "removed",
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcl.Attributes{
"from": {
Name: "from",
Expr: mod_foo_expr,
},
},
Blocks: hcl.Blocks{
&hcl.Block{
Type: "provisioner",
Labels: []string{"local-exec"},
LabelRanges: []hcl.Range{{}},
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcl.Attributes{
"when": {
Name: "when",
Expr: hcltest.MockExprTraversalSrc("destroy"),
},
},
}),
},
},
}),
DefRange: blockRange,
},
&Removed{
From: mustRemoveEndpointFromExpr(mod_foo_expr),
Destroy: true,
DeclRange: blockRange,
},
`Invalid provisioner block`,
},
"connection for module": {
&hcl.Block{
Type: "removed",
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcl.Attributes{
"from": {
Name: "from",
Expr: mod_foo_expr,
},
},
Blocks: hcl.Blocks{
&hcl.Block{
Type: "connection",
Body: hcltest.MockBody(&hcl.BodyContent{}),
},
},
}),
DefRange: blockRange,
},
&Removed{
From: mustRemoveEndpointFromExpr(mod_foo_expr),
Destroy: true,
DeclRange: blockRange,
},
`Invalid connection block`,
},
// KEM Unspecified behaviour
"no lifecycle block": {
&hcl.Block{
@ -147,6 +354,7 @@ func TestRemovedBlock_decode(t *testing.T) {
&Removed{
From: mustRemoveEndpointFromExpr(foo_expr),
Destroy: true,
Managed: &ManagedResource{},
DeclRange: blockRange,
},
``,

Loading…
Cancel
Save