2018-02-03 02:22:25 +01:00
package configs
import (
"fmt"
2019-09-10 00:58:44 +02:00
"github.com/hashicorp/hcl/v2"
2018-02-03 02:22:25 +01:00
)
// 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
2020-08-26 16:46:04 +02:00
switch pv . Type {
case "chef" , "habitat" , "puppet" , "salt-masterless" :
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagWarning ,
Summary : fmt . Sprintf ( "The \"%s\" provisioner is deprecated" , pv . Type ) ,
Detail : fmt . Sprintf ( "The \"%s\" provisioner is deprecated and will be removed from future versions of 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 ,
} )
}
2018-02-03 02:22:25 +01:00
if attr , exists := content . Attributes [ "when" ] ; exists {
2018-02-15 19:17:36 +01:00
expr , shimDiags := shimTraversalInString ( attr . Expr , true )
diags = append ( diags , shimDiags ... )
switch hcl . ExprAsKeyword ( expr ) {
2018-02-03 02:22:25 +01:00
case "create" :
pv . When = ProvisionerWhenCreate
case "destroy" :
pv . When = ProvisionerWhenDestroy
default :
2018-02-15 19:17:36 +01:00
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 ( ) ,
} )
2018-02-03 02:22:25 +01:00
}
}
2019-12-03 19:23:07 +01:00
// destroy provisioners can only refer to self
if pv . When == ProvisionerWhenDestroy {
diags = append ( diags , onlySelfRefs ( config ) ... )
}
2018-02-03 02:22:25 +01:00
if attr , exists := content . Attributes [ "on_failure" ] ; exists {
2018-02-15 19:17:36 +01:00
expr , shimDiags := shimTraversalInString ( attr . Expr , true )
diags = append ( diags , shimDiags ... )
switch hcl . ExprAsKeyword ( expr ) {
2018-02-03 02:22:25 +01:00
case "continue" :
pv . OnFailure = ProvisionerOnFailureContinue
case "fail" :
pv . OnFailure = ProvisionerOnFailureFail
default :
2018-02-15 19:17:36 +01:00
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 ( ) ,
} )
2018-02-03 02:22:25 +01:00
}
}
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
2019-12-03 19:23:07 +01:00
// destroy provisioners can only refer to self
if pv . When == ProvisionerWhenDestroy {
diags = append ( diags , onlySelfRefs ( block . Body ) ... )
}
2018-05-29 20:58:28 +02:00
pv . Connection = & Connection {
Config : block . Body ,
DeclRange : block . DefRange ,
}
2018-02-03 02:22:25 +01:00
default :
2018-11-20 20:53:45 +01:00
// 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 ,
} )
2018-02-03 02:22:25 +01:00
}
}
return pv , diags
}
2019-12-03 19:23:07 +01:00
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 ( ) {
2020-01-15 17:39:01 +01:00
case "self" , "path" , "terraform" :
2019-12-03 19:23:07 +01:00
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 {
2019-12-12 22:26:42 +01:00
Severity : hcl . DiagError ,
Summary : "Invalid reference from destroy provisioner" ,
2019-12-06 15:45:19 +01:00
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 ( ) ,
2019-12-03 19:23:07 +01:00
} )
}
}
}
return diags
}
2018-02-03 02:22:25 +01:00
// 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
2019-10-17 22:17:23 +02:00
//go:generate go run golang.org/x/tools/cmd/stringer -type ProvisionerWhen
2018-02-03 02:22:25 +01:00
const (
ProvisionerWhenInvalid ProvisionerWhen = iota
ProvisionerWhenCreate
ProvisionerWhenDestroy
)
// ProvisionerOnFailure is an enum for valid values for on_failure options
// for provisioners.
type ProvisionerOnFailure int
2019-10-17 22:17:23 +02:00
//go:generate go run golang.org/x/tools/cmd/stringer -type ProvisionerOnFailure
2018-02-03 02:22:25 +01:00
const (
ProvisionerOnFailureInvalid ProvisionerOnFailure = iota
ProvisionerOnFailureContinue
ProvisionerOnFailureFail
)
var provisionerBlockSchema = & hcl . BodySchema {
Attributes : [ ] hcl . AttributeSchema {
2018-11-20 20:53:45 +01:00
{ Name : "when" } ,
{ Name : "on_failure" } ,
2018-02-03 02:22:25 +01:00
} ,
Blocks : [ ] hcl . BlockHeaderSchema {
2018-11-20 20:53:45 +01:00
{ Type : "connection" } ,
{ Type : "lifecycle" } , // reserved for future use
2018-02-03 02:22:25 +01:00
} ,
}