kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/configs/provider.go (about) 1 package configs 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2" 7 "github.com/hashicorp/hcl/v2/gohcl" 8 "github.com/hashicorp/hcl/v2/hclsyntax" 9 10 "kubeform.dev/terraform-backend-sdk/addrs" 11 "kubeform.dev/terraform-backend-sdk/tfdiags" 12 ) 13 14 // Provider represents a "provider" block in a module or file. A provider 15 // block is a provider configuration, and there can be zero or more 16 // configurations for each actual provider. 17 type Provider struct { 18 Name string 19 NameRange hcl.Range 20 Alias string 21 AliasRange *hcl.Range // nil if no alias set 22 23 Version VersionConstraint 24 25 Config hcl.Body 26 27 DeclRange hcl.Range 28 29 // TODO: this may not be set in some cases, so it is not yet suitable for 30 // use outside of this package. We currently only use it for internal 31 // validation, but once we verify that this can be set in all cases, we can 32 // export this so providers don't need to be re-resolved. 33 // This same field is also added to the ProviderConfigRef struct. 34 providerType addrs.Provider 35 } 36 37 func decodeProviderBlock(block *hcl.Block) (*Provider, hcl.Diagnostics) { 38 var diags hcl.Diagnostics 39 40 content, config, moreDiags := block.Body.PartialContent(providerBlockSchema) 41 diags = append(diags, moreDiags...) 42 43 // Provider names must be localized. Produce an error with a message 44 // indicating the action the user can take to fix this message if the local 45 // name is not localized. 46 name := block.Labels[0] 47 nameDiags := checkProviderNameNormalized(name, block.DefRange) 48 diags = append(diags, nameDiags...) 49 50 provider := &Provider{ 51 Name: name, 52 NameRange: block.LabelRanges[0], 53 Config: config, 54 DeclRange: block.DefRange, 55 } 56 57 if attr, exists := content.Attributes["alias"]; exists { 58 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &provider.Alias) 59 diags = append(diags, valDiags...) 60 provider.AliasRange = attr.Expr.Range().Ptr() 61 62 if !hclsyntax.ValidIdentifier(provider.Alias) { 63 diags = append(diags, &hcl.Diagnostic{ 64 Severity: hcl.DiagError, 65 Summary: "Invalid provider configuration alias", 66 Detail: fmt.Sprintf("An alias must be a valid name. %s", badIdentifierDetail), 67 }) 68 } 69 } 70 71 if attr, exists := content.Attributes["version"]; exists { 72 diags = append(diags, &hcl.Diagnostic{ 73 Severity: hcl.DiagWarning, 74 Summary: "Version constraints inside provider configuration blocks are deprecated", 75 Detail: "Terraform 0.13 and earlier allowed provider version constraints inside the provider configuration block, but that is now deprecated and will be removed in a future version of Terraform. To silence this warning, move the provider version constraint into the required_providers block.", 76 Subject: attr.Expr.Range().Ptr(), 77 }) 78 var versionDiags hcl.Diagnostics 79 provider.Version, versionDiags = decodeVersionConstraint(attr) 80 diags = append(diags, versionDiags...) 81 } 82 83 // Reserved attribute names 84 for _, name := range []string{"count", "depends_on", "for_each", "source"} { 85 if attr, exists := content.Attributes[name]; exists { 86 diags = append(diags, &hcl.Diagnostic{ 87 Severity: hcl.DiagError, 88 Summary: "Reserved argument name in provider block", 89 Detail: fmt.Sprintf("The provider argument name %q is reserved for use by Terraform in a future version.", name), 90 Subject: &attr.NameRange, 91 }) 92 } 93 } 94 95 var seenEscapeBlock *hcl.Block 96 for _, block := range content.Blocks { 97 switch block.Type { 98 case "_": 99 if seenEscapeBlock != nil { 100 diags = append(diags, &hcl.Diagnostic{ 101 Severity: hcl.DiagError, 102 Summary: "Duplicate escaping block", 103 Detail: fmt.Sprintf( 104 "The special block type \"_\" can be used to force particular arguments to be interpreted as provider-specific rather than as meta-arguments, but each provider block can have only one such block. The first escaping block was at %s.", 105 seenEscapeBlock.DefRange, 106 ), 107 Subject: &block.DefRange, 108 }) 109 continue 110 } 111 seenEscapeBlock = block 112 113 // When there's an escaping block its content merges with the 114 // existing config we extracted earlier, so later decoding 115 // will see a blend of both. 116 provider.Config = hcl.MergeBodies([]hcl.Body{provider.Config, block.Body}) 117 118 default: 119 // All of the other block types in our schema are reserved for 120 // future expansion. 121 diags = append(diags, &hcl.Diagnostic{ 122 Severity: hcl.DiagError, 123 Summary: "Reserved block type name in provider block", 124 Detail: fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type), 125 Subject: &block.TypeRange, 126 }) 127 } 128 } 129 130 return provider, diags 131 } 132 133 // Addr returns the address of the receiving provider configuration, relative 134 // to its containing module. 135 func (p *Provider) Addr() addrs.LocalProviderConfig { 136 return addrs.LocalProviderConfig{ 137 LocalName: p.Name, 138 Alias: p.Alias, 139 } 140 } 141 142 func (p *Provider) moduleUniqueKey() string { 143 if p.Alias != "" { 144 return fmt.Sprintf("%s.%s", p.Name, p.Alias) 145 } 146 return p.Name 147 } 148 149 // ParseProviderConfigCompact parses the given absolute traversal as a relative 150 // provider address in compact form. The following are examples of traversals 151 // that can be successfully parsed as compact relative provider configuration 152 // addresses: 153 // 154 // aws 155 // aws.foo 156 // 157 // This function will panic if given a relative traversal. 158 // 159 // If the returned diagnostics contains errors then the result value is invalid 160 // and must not be used. 161 func ParseProviderConfigCompact(traversal hcl.Traversal) (addrs.LocalProviderConfig, tfdiags.Diagnostics) { 162 var diags tfdiags.Diagnostics 163 ret := addrs.LocalProviderConfig{ 164 LocalName: traversal.RootName(), 165 } 166 167 if len(traversal) < 2 { 168 // Just a type name, then. 169 return ret, diags 170 } 171 172 aliasStep := traversal[1] 173 switch ts := aliasStep.(type) { 174 case hcl.TraverseAttr: 175 ret.Alias = ts.Name 176 return ret, diags 177 default: 178 diags = diags.Append(&hcl.Diagnostic{ 179 Severity: hcl.DiagError, 180 Summary: "Invalid provider configuration address", 181 Detail: "The provider type name must either stand alone or be followed by an alias name separated with a dot.", 182 Subject: aliasStep.SourceRange().Ptr(), 183 }) 184 } 185 186 if len(traversal) > 2 { 187 diags = diags.Append(&hcl.Diagnostic{ 188 Severity: hcl.DiagError, 189 Summary: "Invalid provider configuration address", 190 Detail: "Extraneous extra operators after provider configuration address.", 191 Subject: traversal[2:].SourceRange().Ptr(), 192 }) 193 } 194 195 return ret, diags 196 } 197 198 // ParseProviderConfigCompactStr is a helper wrapper around ParseProviderConfigCompact 199 // that takes a string and parses it with the HCL native syntax traversal parser 200 // before interpreting it. 201 // 202 // This should be used only in specialized situations since it will cause the 203 // created references to not have any meaningful source location information. 204 // If a reference string is coming from a source that should be identified in 205 // error messages then the caller should instead parse it directly using a 206 // suitable function from the HCL API and pass the traversal itself to 207 // ParseProviderConfigCompact. 208 // 209 // Error diagnostics are returned if either the parsing fails or the analysis 210 // of the traversal fails. There is no way for the caller to distinguish the 211 // two kinds of diagnostics programmatically. If error diagnostics are returned 212 // then the returned address is invalid. 213 func ParseProviderConfigCompactStr(str string) (addrs.LocalProviderConfig, tfdiags.Diagnostics) { 214 var diags tfdiags.Diagnostics 215 216 traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) 217 diags = diags.Append(parseDiags) 218 if parseDiags.HasErrors() { 219 return addrs.LocalProviderConfig{}, diags 220 } 221 222 addr, addrDiags := ParseProviderConfigCompact(traversal) 223 diags = diags.Append(addrDiags) 224 return addr, diags 225 } 226 227 var providerBlockSchema = &hcl.BodySchema{ 228 Attributes: []hcl.AttributeSchema{ 229 { 230 Name: "alias", 231 }, 232 { 233 Name: "version", 234 }, 235 236 // Attribute names reserved for future expansion. 237 {Name: "count"}, 238 {Name: "depends_on"}, 239 {Name: "for_each"}, 240 {Name: "source"}, 241 }, 242 Blocks: []hcl.BlockHeaderSchema{ 243 {Type: "_"}, // meta-argument escaping block 244 245 // The rest of these are reserved for future expansion. 246 {Type: "lifecycle"}, 247 {Type: "locals"}, 248 }, 249 } 250 251 // checkProviderNameNormalized verifies that the given string is already 252 // normalized and returns an error if not. 253 func checkProviderNameNormalized(name string, declrange hcl.Range) hcl.Diagnostics { 254 var diags hcl.Diagnostics 255 // verify that the provider local name is normalized 256 normalized, err := addrs.IsProviderPartNormalized(name) 257 if err != nil { 258 diags = append(diags, &hcl.Diagnostic{ 259 Severity: hcl.DiagError, 260 Summary: "Invalid provider local name", 261 Detail: fmt.Sprintf("%s is an invalid provider local name: %s", name, err), 262 Subject: &declrange, 263 }) 264 return diags 265 } 266 if !normalized { 267 // we would have returned this error already 268 normalizedProvider, _ := addrs.ParseProviderPart(name) 269 diags = append(diags, &hcl.Diagnostic{ 270 Severity: hcl.DiagError, 271 Summary: "Invalid provider local name", 272 Detail: fmt.Sprintf("Provider names must be normalized. Replace %q with %q to fix this error.", name, normalizedProvider), 273 Subject: &declrange, 274 }) 275 } 276 return diags 277 }