mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-23 23:50:12 -06:00
31349a9c3a
This is part of a general effort to move all of Terraform's non-library package surface under internal in order to reinforce that these are for internal use within Terraform only. If you were previously importing packages under this prefix into an external codebase, you could pin to an earlier release tag as an interim solution until you've make a plan to achieve the same functionality some other way.
238 lines
6.9 KiB
Go
238 lines
6.9 KiB
Go
package configs
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
)
|
|
|
|
// Provisioner represents a "provisioner" block when used within a
|
|
// "resource" block in a module or file.
|
|
type Provisioner struct {
|
|
Type string
|
|
Config hcl.Body
|
|
Connection *Connection
|
|
When ProvisionerWhen
|
|
OnFailure ProvisionerOnFailure
|
|
|
|
DeclRange hcl.Range
|
|
TypeRange hcl.Range
|
|
}
|
|
|
|
func decodeProvisionerBlock(block *hcl.Block) (*Provisioner, hcl.Diagnostics) {
|
|
pv := &Provisioner{
|
|
Type: block.Labels[0],
|
|
TypeRange: block.LabelRanges[0],
|
|
DeclRange: block.DefRange,
|
|
When: ProvisionerWhenCreate,
|
|
OnFailure: ProvisionerOnFailureFail,
|
|
}
|
|
|
|
content, config, diags := block.Body.PartialContent(provisionerBlockSchema)
|
|
pv.Config = config
|
|
|
|
switch pv.Type {
|
|
case "chef", "habitat", "puppet", "salt-masterless":
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: fmt.Sprintf("The \"%s\" provisioner has been removed", pv.Type),
|
|
Detail: fmt.Sprintf("The \"%s\" provisioner was deprecated in Terraform 0.13.4 has been removed from Terraform. Visit https://learn.hashicorp.com/collections/terraform/provision for alternatives to using provisioners that are a better fit for the Terraform workflow.", pv.Type),
|
|
Subject: &pv.TypeRange,
|
|
})
|
|
return nil, diags
|
|
}
|
|
|
|
if attr, exists := content.Attributes["when"]; exists {
|
|
expr, shimDiags := shimTraversalInString(attr.Expr, true)
|
|
diags = append(diags, shimDiags...)
|
|
|
|
switch hcl.ExprAsKeyword(expr) {
|
|
case "create":
|
|
pv.When = ProvisionerWhenCreate
|
|
case "destroy":
|
|
pv.When = ProvisionerWhenDestroy
|
|
default:
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid \"when\" keyword",
|
|
Detail: "The \"when\" argument requires one of the following keywords: create or destroy.",
|
|
Subject: expr.Range().Ptr(),
|
|
})
|
|
}
|
|
}
|
|
|
|
// destroy provisioners can only refer to self
|
|
if pv.When == ProvisionerWhenDestroy {
|
|
diags = append(diags, onlySelfRefs(config)...)
|
|
}
|
|
|
|
if attr, exists := content.Attributes["on_failure"]; exists {
|
|
expr, shimDiags := shimTraversalInString(attr.Expr, true)
|
|
diags = append(diags, shimDiags...)
|
|
|
|
switch hcl.ExprAsKeyword(expr) {
|
|
case "continue":
|
|
pv.OnFailure = ProvisionerOnFailureContinue
|
|
case "fail":
|
|
pv.OnFailure = ProvisionerOnFailureFail
|
|
default:
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid \"on_failure\" keyword",
|
|
Detail: "The \"on_failure\" argument requires one of the following keywords: continue or fail.",
|
|
Subject: attr.Expr.Range().Ptr(),
|
|
})
|
|
}
|
|
}
|
|
|
|
var seenConnection *hcl.Block
|
|
var seenEscapeBlock *hcl.Block
|
|
for _, block := range content.Blocks {
|
|
switch block.Type {
|
|
case "_":
|
|
if seenEscapeBlock != nil {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Duplicate escaping block",
|
|
Detail: fmt.Sprintf(
|
|
"The special block type \"_\" can be used to force particular arguments to be interpreted as provisioner-typpe-specific rather than as meta-arguments, but each provisioner block can have only one such block. The first escaping block was at %s.",
|
|
seenEscapeBlock.DefRange,
|
|
),
|
|
Subject: &block.DefRange,
|
|
})
|
|
continue
|
|
}
|
|
seenEscapeBlock = block
|
|
|
|
// When there's an escaping block its content merges with the
|
|
// existing config we extracted earlier, so later decoding
|
|
// will see a blend of both.
|
|
pv.Config = hcl.MergeBodies([]hcl.Body{pv.Config, block.Body})
|
|
|
|
case "connection":
|
|
if seenConnection != nil {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Duplicate connection block",
|
|
Detail: fmt.Sprintf("This provisioner already has a connection block at %s.", seenConnection.DefRange),
|
|
Subject: &block.DefRange,
|
|
})
|
|
continue
|
|
}
|
|
seenConnection = block
|
|
|
|
// destroy provisioners can only refer to self
|
|
if pv.When == ProvisionerWhenDestroy {
|
|
diags = append(diags, onlySelfRefs(block.Body)...)
|
|
}
|
|
|
|
pv.Connection = &Connection{
|
|
Config: block.Body,
|
|
DeclRange: block.DefRange,
|
|
}
|
|
|
|
default:
|
|
// Any other block types are ones we've reserved for future use,
|
|
// so they get a generic message.
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Reserved block type name in provisioner block",
|
|
Detail: fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
|
|
Subject: &block.TypeRange,
|
|
})
|
|
}
|
|
}
|
|
|
|
return pv, diags
|
|
}
|
|
|
|
func onlySelfRefs(body hcl.Body) hcl.Diagnostics {
|
|
var diags hcl.Diagnostics
|
|
|
|
// Provisioners currently do not use any blocks in their configuration.
|
|
// Blocks are likely to remain solely for meta parameters, but in the case
|
|
// that blocks are supported for provisioners, we will want to extend this
|
|
// to find variables in nested blocks.
|
|
attrs, _ := body.JustAttributes()
|
|
for _, attr := range attrs {
|
|
for _, v := range attr.Expr.Variables() {
|
|
valid := false
|
|
switch v.RootName() {
|
|
case "self", "path", "terraform":
|
|
valid = true
|
|
case "count":
|
|
// count must use "index"
|
|
if len(v) == 2 {
|
|
if t, ok := v[1].(hcl.TraverseAttr); ok && t.Name == "index" {
|
|
valid = true
|
|
}
|
|
}
|
|
|
|
case "each":
|
|
if len(v) == 2 {
|
|
if t, ok := v[1].(hcl.TraverseAttr); ok && t.Name == "key" {
|
|
valid = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if !valid {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid reference from destroy provisioner",
|
|
Detail: "Destroy-time provisioners and their connection configurations may only " +
|
|
"reference attributes of the related resource, via 'self', 'count.index', " +
|
|
"or 'each.key'.\n\nReferences to other resources during the destroy phase " +
|
|
"can cause dependency cycles and interact poorly with create_before_destroy.",
|
|
Subject: attr.Expr.Range().Ptr(),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return diags
|
|
}
|
|
|
|
// Connection represents a "connection" block when used within either a
|
|
// "resource" or "provisioner" block in a module or file.
|
|
type Connection struct {
|
|
Config hcl.Body
|
|
|
|
DeclRange hcl.Range
|
|
}
|
|
|
|
// ProvisionerWhen is an enum for valid values for when to run provisioners.
|
|
type ProvisionerWhen int
|
|
|
|
//go:generate go run golang.org/x/tools/cmd/stringer -type ProvisionerWhen
|
|
|
|
const (
|
|
ProvisionerWhenInvalid ProvisionerWhen = iota
|
|
ProvisionerWhenCreate
|
|
ProvisionerWhenDestroy
|
|
)
|
|
|
|
// ProvisionerOnFailure is an enum for valid values for on_failure options
|
|
// for provisioners.
|
|
type ProvisionerOnFailure int
|
|
|
|
//go:generate go run golang.org/x/tools/cmd/stringer -type ProvisionerOnFailure
|
|
|
|
const (
|
|
ProvisionerOnFailureInvalid ProvisionerOnFailure = iota
|
|
ProvisionerOnFailureContinue
|
|
ProvisionerOnFailureFail
|
|
)
|
|
|
|
var provisionerBlockSchema = &hcl.BodySchema{
|
|
Attributes: []hcl.AttributeSchema{
|
|
{Name: "when"},
|
|
{Name: "on_failure"},
|
|
},
|
|
Blocks: []hcl.BlockHeaderSchema{
|
|
{Type: "_"}, // meta-argument escaping block
|
|
|
|
{Type: "connection"},
|
|
{Type: "lifecycle"}, // reserved for future use
|
|
},
|
|
}
|