github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/configs/provisioner.go (about) 1 package configs 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2" 7 ) 8 9 // Provisioner represents a "provisioner" block when used within a 10 // "resource" block in a module or file. 11 type Provisioner struct { 12 Type string 13 Config hcl.Body 14 Connection *Connection 15 When ProvisionerWhen 16 OnFailure ProvisionerOnFailure 17 18 DeclRange hcl.Range 19 TypeRange hcl.Range 20 } 21 22 func decodeProvisionerBlock(block *hcl.Block) (*Provisioner, hcl.Diagnostics) { 23 pv := &Provisioner{ 24 Type: block.Labels[0], 25 TypeRange: block.LabelRanges[0], 26 DeclRange: block.DefRange, 27 When: ProvisionerWhenCreate, 28 OnFailure: ProvisionerOnFailureFail, 29 } 30 31 content, config, diags := block.Body.PartialContent(provisionerBlockSchema) 32 pv.Config = config 33 34 if attr, exists := content.Attributes["when"]; exists { 35 expr, shimDiags := shimTraversalInString(attr.Expr, true) 36 diags = append(diags, shimDiags...) 37 38 switch hcl.ExprAsKeyword(expr) { 39 case "create": 40 pv.When = ProvisionerWhenCreate 41 case "destroy": 42 pv.When = ProvisionerWhenDestroy 43 default: 44 diags = append(diags, &hcl.Diagnostic{ 45 Severity: hcl.DiagError, 46 Summary: "Invalid \"when\" keyword", 47 Detail: "The \"when\" argument requires one of the following keywords: create or destroy.", 48 Subject: expr.Range().Ptr(), 49 }) 50 } 51 } 52 53 // destroy provisioners can only refer to self 54 if pv.When == ProvisionerWhenDestroy { 55 diags = append(diags, onlySelfRefs(config)...) 56 } 57 58 if attr, exists := content.Attributes["on_failure"]; exists { 59 expr, shimDiags := shimTraversalInString(attr.Expr, true) 60 diags = append(diags, shimDiags...) 61 62 switch hcl.ExprAsKeyword(expr) { 63 case "continue": 64 pv.OnFailure = ProvisionerOnFailureContinue 65 case "fail": 66 pv.OnFailure = ProvisionerOnFailureFail 67 default: 68 diags = append(diags, &hcl.Diagnostic{ 69 Severity: hcl.DiagError, 70 Summary: "Invalid \"on_failure\" keyword", 71 Detail: "The \"on_failure\" argument requires one of the following keywords: continue or fail.", 72 Subject: attr.Expr.Range().Ptr(), 73 }) 74 } 75 } 76 77 var seenConnection *hcl.Block 78 for _, block := range content.Blocks { 79 switch block.Type { 80 81 case "connection": 82 if seenConnection != nil { 83 diags = append(diags, &hcl.Diagnostic{ 84 Severity: hcl.DiagError, 85 Summary: "Duplicate connection block", 86 Detail: fmt.Sprintf("This provisioner already has a connection block at %s.", seenConnection.DefRange), 87 Subject: &block.DefRange, 88 }) 89 continue 90 } 91 seenConnection = block 92 93 // destroy provisioners can only refer to self 94 if pv.When == ProvisionerWhenDestroy { 95 diags = append(diags, onlySelfRefs(block.Body)...) 96 } 97 98 pv.Connection = &Connection{ 99 Config: block.Body, 100 DeclRange: block.DefRange, 101 } 102 103 default: 104 // Any other block types are ones we've reserved for future use, 105 // so they get a generic message. 106 diags = append(diags, &hcl.Diagnostic{ 107 Severity: hcl.DiagError, 108 Summary: "Reserved block type name in provisioner block", 109 Detail: fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type), 110 Subject: &block.TypeRange, 111 }) 112 } 113 } 114 115 return pv, diags 116 } 117 118 func onlySelfRefs(body hcl.Body) hcl.Diagnostics { 119 var diags hcl.Diagnostics 120 121 // Provisioners currently do not use any blocks in their configuration. 122 // Blocks are likely to remain solely for meta parameters, but in the case 123 // that blocks are supported for provisioners, we will want to extend this 124 // to find variables in nested blocks. 125 attrs, _ := body.JustAttributes() 126 for _, attr := range attrs { 127 for _, v := range attr.Expr.Variables() { 128 valid := false 129 switch v.RootName() { 130 case "self", "path", "terraform": 131 valid = true 132 case "count": 133 // count must use "index" 134 if len(v) == 2 { 135 if t, ok := v[1].(hcl.TraverseAttr); ok && t.Name == "index" { 136 valid = true 137 } 138 } 139 140 case "each": 141 if len(v) == 2 { 142 if t, ok := v[1].(hcl.TraverseAttr); ok && t.Name == "key" { 143 valid = true 144 } 145 } 146 } 147 148 if !valid { 149 diags = append(diags, &hcl.Diagnostic{ 150 Severity: hcl.DiagWarning, 151 Summary: "External references from destroy provisioners are deprecated", 152 Detail: "Destroy-time provisioners and their connection configurations may only " + 153 "reference attributes of the related resource, via 'self', 'count.index', " + 154 "or 'each.key'.\n\nReferences to other resources during the destroy phase " + 155 "can cause dependency cycles and interact poorly with create_before_destroy.", 156 Subject: attr.Expr.Range().Ptr(), 157 }) 158 } 159 } 160 } 161 return diags 162 } 163 164 // Connection represents a "connection" block when used within either a 165 // "resource" or "provisioner" block in a module or file. 166 type Connection struct { 167 Config hcl.Body 168 169 DeclRange hcl.Range 170 } 171 172 // ProvisionerWhen is an enum for valid values for when to run provisioners. 173 type ProvisionerWhen int 174 175 //go:generate go run golang.org/x/tools/cmd/stringer -type ProvisionerWhen 176 177 const ( 178 ProvisionerWhenInvalid ProvisionerWhen = iota 179 ProvisionerWhenCreate 180 ProvisionerWhenDestroy 181 ) 182 183 // ProvisionerOnFailure is an enum for valid values for on_failure options 184 // for provisioners. 185 type ProvisionerOnFailure int 186 187 //go:generate go run golang.org/x/tools/cmd/stringer -type ProvisionerOnFailure 188 189 const ( 190 ProvisionerOnFailureInvalid ProvisionerOnFailure = iota 191 ProvisionerOnFailureContinue 192 ProvisionerOnFailureFail 193 ) 194 195 var provisionerBlockSchema = &hcl.BodySchema{ 196 Attributes: []hcl.AttributeSchema{ 197 {Name: "when"}, 198 {Name: "on_failure"}, 199 }, 200 Blocks: []hcl.BlockHeaderSchema{ 201 {Type: "connection"}, 202 {Type: "lifecycle"}, // reserved for future use 203 }, 204 }