terraform/configs/provisioner.go

174 lines
4.1 KiB
Go

package configs
import (
"fmt"
"github.com/hashicorp/hcl2/gohcl"
"github.com/hashicorp/hcl2/hcl"
)
// 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
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(),
})
}
}
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
for _, block := range content.Blocks {
switch block.Type {
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
conn, connDiags := decodeConnectionBlock(block)
diags = append(diags, connDiags...)
pv.Connection = conn
default:
// Should never happen because there are no other block types
// declared in our schema.
}
}
return pv, diags
}
// Connection represents a "connection" block when used within either a
// "resource" or "provisioner" block in a module or file.
type Connection struct {
Type string
Config hcl.Body
DeclRange hcl.Range
TypeRange *hcl.Range // nil if type is not set
}
func decodeConnectionBlock(block *hcl.Block) (*Connection, hcl.Diagnostics) {
content, config, diags := block.Body.PartialContent(&hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "type",
},
},
})
conn := &Connection{
Type: "ssh",
Config: config,
DeclRange: block.DefRange,
}
if attr, exists := content.Attributes["type"]; exists {
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &conn.Type)
diags = append(diags, valDiags...)
conn.TypeRange = attr.Expr.Range().Ptr()
}
return conn, diags
}
// ProvisionerWhen is an enum for valid values for when to run provisioners.
type ProvisionerWhen int
//go:generate 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 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: "connection",
},
},
}