kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/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 switch pv.Type { 35 case "chef", "habitat", "puppet", "salt-masterless": 36 diags = append(diags, &hcl.Diagnostic{ 37 Severity: hcl.DiagError, 38 Summary: fmt.Sprintf("The \"%s\" provisioner has been removed", pv.Type), 39 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), 40 Subject: &pv.TypeRange, 41 }) 42 return nil, diags 43 } 44 45 if attr, exists := content.Attributes["when"]; exists { 46 expr, shimDiags := shimTraversalInString(attr.Expr, true) 47 diags = append(diags, shimDiags...) 48 49 switch hcl.ExprAsKeyword(expr) { 50 case "create": 51 pv.When = ProvisionerWhenCreate 52 case "destroy": 53 pv.When = ProvisionerWhenDestroy 54 default: 55 diags = append(diags, &hcl.Diagnostic{ 56 Severity: hcl.DiagError, 57 Summary: "Invalid \"when\" keyword", 58 Detail: "The \"when\" argument requires one of the following keywords: create or destroy.", 59 Subject: expr.Range().Ptr(), 60 }) 61 } 62 } 63 64 // destroy provisioners can only refer to self 65 if pv.When == ProvisionerWhenDestroy { 66 diags = append(diags, onlySelfRefs(config)...) 67 } 68 69 if attr, exists := content.Attributes["on_failure"]; exists { 70 expr, shimDiags := shimTraversalInString(attr.Expr, true) 71 diags = append(diags, shimDiags...) 72 73 switch hcl.ExprAsKeyword(expr) { 74 case "continue": 75 pv.OnFailure = ProvisionerOnFailureContinue 76 case "fail": 77 pv.OnFailure = ProvisionerOnFailureFail 78 default: 79 diags = append(diags, &hcl.Diagnostic{ 80 Severity: hcl.DiagError, 81 Summary: "Invalid \"on_failure\" keyword", 82 Detail: "The \"on_failure\" argument requires one of the following keywords: continue or fail.", 83 Subject: attr.Expr.Range().Ptr(), 84 }) 85 } 86 } 87 88 var seenConnection *hcl.Block 89 var seenEscapeBlock *hcl.Block 90 for _, block := range content.Blocks { 91 switch block.Type { 92 case "_": 93 if seenEscapeBlock != nil { 94 diags = append(diags, &hcl.Diagnostic{ 95 Severity: hcl.DiagError, 96 Summary: "Duplicate escaping block", 97 Detail: fmt.Sprintf( 98 "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.", 99 seenEscapeBlock.DefRange, 100 ), 101 Subject: &block.DefRange, 102 }) 103 continue 104 } 105 seenEscapeBlock = block 106 107 // When there's an escaping block its content merges with the 108 // existing config we extracted earlier, so later decoding 109 // will see a blend of both. 110 pv.Config = hcl.MergeBodies([]hcl.Body{pv.Config, block.Body}) 111 112 case "connection": 113 if seenConnection != nil { 114 diags = append(diags, &hcl.Diagnostic{ 115 Severity: hcl.DiagError, 116 Summary: "Duplicate connection block", 117 Detail: fmt.Sprintf("This provisioner already has a connection block at %s.", seenConnection.DefRange), 118 Subject: &block.DefRange, 119 }) 120 continue 121 } 122 seenConnection = block 123 124 // destroy provisioners can only refer to self 125 if pv.When == ProvisionerWhenDestroy { 126 diags = append(diags, onlySelfRefs(block.Body)...) 127 } 128 129 pv.Connection = &Connection{ 130 Config: block.Body, 131 DeclRange: block.DefRange, 132 } 133 134 default: 135 // Any other block types are ones we've reserved for future use, 136 // so they get a generic message. 137 diags = append(diags, &hcl.Diagnostic{ 138 Severity: hcl.DiagError, 139 Summary: "Reserved block type name in provisioner block", 140 Detail: fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type), 141 Subject: &block.TypeRange, 142 }) 143 } 144 } 145 146 return pv, diags 147 } 148 149 func onlySelfRefs(body hcl.Body) hcl.Diagnostics { 150 var diags hcl.Diagnostics 151 152 // Provisioners currently do not use any blocks in their configuration. 153 // Blocks are likely to remain solely for meta parameters, but in the case 154 // that blocks are supported for provisioners, we will want to extend this 155 // to find variables in nested blocks. 156 attrs, _ := body.JustAttributes() 157 for _, attr := range attrs { 158 for _, v := range attr.Expr.Variables() { 159 valid := false 160 switch v.RootName() { 161 case "self", "path", "terraform": 162 valid = true 163 case "count": 164 // count must use "index" 165 if len(v) == 2 { 166 if t, ok := v[1].(hcl.TraverseAttr); ok && t.Name == "index" { 167 valid = true 168 } 169 } 170 171 case "each": 172 if len(v) == 2 { 173 if t, ok := v[1].(hcl.TraverseAttr); ok && t.Name == "key" { 174 valid = true 175 } 176 } 177 } 178 179 if !valid { 180 diags = append(diags, &hcl.Diagnostic{ 181 Severity: hcl.DiagError, 182 Summary: "Invalid reference from destroy provisioner", 183 Detail: "Destroy-time provisioners and their connection configurations may only " + 184 "reference attributes of the related resource, via 'self', 'count.index', " + 185 "or 'each.key'.\n\nReferences to other resources during the destroy phase " + 186 "can cause dependency cycles and interact poorly with create_before_destroy.", 187 Subject: attr.Expr.Range().Ptr(), 188 }) 189 } 190 } 191 } 192 return diags 193 } 194 195 // Connection represents a "connection" block when used within either a 196 // "resource" or "provisioner" block in a module or file. 197 type Connection struct { 198 Config hcl.Body 199 200 DeclRange hcl.Range 201 } 202 203 // ProvisionerWhen is an enum for valid values for when to run provisioners. 204 type ProvisionerWhen int 205 206 //go:generate go run golang.org/x/tools/cmd/stringer -type ProvisionerWhen 207 208 const ( 209 ProvisionerWhenInvalid ProvisionerWhen = iota 210 ProvisionerWhenCreate 211 ProvisionerWhenDestroy 212 ) 213 214 // ProvisionerOnFailure is an enum for valid values for on_failure options 215 // for provisioners. 216 type ProvisionerOnFailure int 217 218 //go:generate go run golang.org/x/tools/cmd/stringer -type ProvisionerOnFailure 219 220 const ( 221 ProvisionerOnFailureInvalid ProvisionerOnFailure = iota 222 ProvisionerOnFailureContinue 223 ProvisionerOnFailureFail 224 ) 225 226 var provisionerBlockSchema = &hcl.BodySchema{ 227 Attributes: []hcl.AttributeSchema{ 228 {Name: "when"}, 229 {Name: "on_failure"}, 230 }, 231 Blocks: []hcl.BlockHeaderSchema{ 232 {Type: "_"}, // meta-argument escaping block 233 234 {Type: "connection"}, 235 {Type: "lifecycle"}, // reserved for future use 236 }, 237 }