1package addrs 2 3import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/terraform/internal/tfdiags" 8 "github.com/zclconf/go-cty/cty" 9 10 "github.com/hashicorp/hcl/v2" 11 "github.com/hashicorp/hcl/v2/hclsyntax" 12) 13 14// ProviderConfig is an interface type whose dynamic type can be either 15// LocalProviderConfig or AbsProviderConfig, in order to represent situations 16// where a value might either be module-local or absolute but the decision 17// cannot be made until runtime. 18// 19// Where possible, use either LocalProviderConfig or AbsProviderConfig directly 20// instead, to make intent more clear. ProviderConfig can be used only in 21// situations where the recipient of the value has some out-of-band way to 22// determine a "current module" to use if the value turns out to be 23// a LocalProviderConfig. 24// 25// Recipients of non-nil ProviderConfig values that actually need 26// AbsProviderConfig values should call ResolveAbsProviderAddr on the 27// *configs.Config value representing the root module configuration, which 28// handles the translation from local to fully-qualified using mapping tables 29// defined in the configuration. 30// 31// Recipients of a ProviderConfig value can assume it can contain only a 32// LocalProviderConfig value, an AbsProviderConfigValue, or nil to represent 33// the absense of a provider config in situations where that is meaningful. 34type ProviderConfig interface { 35 providerConfig() 36} 37 38// LocalProviderConfig is the address of a provider configuration from the 39// perspective of references in a particular module. 40// 41// Finding the corresponding AbsProviderConfig will require looking up the 42// LocalName in the providers table in the module's configuration; there is 43// no syntax-only translation between these types. 44type LocalProviderConfig struct { 45 LocalName string 46 47 // If not empty, Alias identifies which non-default (aliased) provider 48 // configuration this address refers to. 49 Alias string 50} 51 52var _ ProviderConfig = LocalProviderConfig{} 53 54// NewDefaultLocalProviderConfig returns the address of the default (un-aliased) 55// configuration for the provider with the given local type name. 56func NewDefaultLocalProviderConfig(LocalNameName string) LocalProviderConfig { 57 return LocalProviderConfig{ 58 LocalName: LocalNameName, 59 } 60} 61 62// providerConfig Implements addrs.ProviderConfig. 63func (pc LocalProviderConfig) providerConfig() {} 64 65func (pc LocalProviderConfig) String() string { 66 if pc.LocalName == "" { 67 // Should never happen; always indicates a bug 68 return "provider.<invalid>" 69 } 70 71 if pc.Alias != "" { 72 return fmt.Sprintf("provider.%s.%s", pc.LocalName, pc.Alias) 73 } 74 75 return "provider." + pc.LocalName 76} 77 78// StringCompact is an alternative to String that returns the form that can 79// be parsed by ParseProviderConfigCompact, without the "provider." prefix. 80func (pc LocalProviderConfig) StringCompact() string { 81 if pc.Alias != "" { 82 return fmt.Sprintf("%s.%s", pc.LocalName, pc.Alias) 83 } 84 return pc.LocalName 85} 86 87// AbsProviderConfig is the absolute address of a provider configuration 88// within a particular module instance. 89type AbsProviderConfig struct { 90 Module Module 91 Provider Provider 92 Alias string 93} 94 95var _ ProviderConfig = AbsProviderConfig{} 96 97// ParseAbsProviderConfig parses the given traversal as an absolute provider 98// address. The following are examples of traversals that can be successfully 99// parsed as absolute provider configuration addresses: 100// 101// provider["registry.terraform.io/hashicorp/aws"] 102// provider["registry.terraform.io/hashicorp/aws"].foo 103// module.bar.provider["registry.terraform.io/hashicorp/aws"] 104// module.bar.module.baz.provider["registry.terraform.io/hashicorp/aws"].foo 105// 106// This type of address is used, for example, to record the relationships 107// between resources and provider configurations in the state structure. 108// This type of address is not generally used in the UI, except in error 109// messages that refer to provider configurations. 110func ParseAbsProviderConfig(traversal hcl.Traversal) (AbsProviderConfig, tfdiags.Diagnostics) { 111 modInst, remain, diags := parseModuleInstancePrefix(traversal) 112 var ret AbsProviderConfig 113 114 // Providers cannot resolve within module instances, so verify that there 115 // are no instance keys in the module path before converting to a Module. 116 for _, step := range modInst { 117 if step.InstanceKey != NoKey { 118 diags = diags.Append(&hcl.Diagnostic{ 119 Severity: hcl.DiagError, 120 Summary: "Invalid provider configuration address", 121 Detail: "Provider address cannot contain module indexes", 122 Subject: remain.SourceRange().Ptr(), 123 }) 124 return ret, diags 125 } 126 } 127 ret.Module = modInst.Module() 128 129 if len(remain) < 2 || remain.RootName() != "provider" { 130 diags = diags.Append(&hcl.Diagnostic{ 131 Severity: hcl.DiagError, 132 Summary: "Invalid provider configuration address", 133 Detail: "Provider address must begin with \"provider.\", followed by a provider type name.", 134 Subject: remain.SourceRange().Ptr(), 135 }) 136 return ret, diags 137 } 138 if len(remain) > 3 { 139 diags = diags.Append(&hcl.Diagnostic{ 140 Severity: hcl.DiagError, 141 Summary: "Invalid provider configuration address", 142 Detail: "Extraneous operators after provider configuration alias.", 143 Subject: hcl.Traversal(remain[3:]).SourceRange().Ptr(), 144 }) 145 return ret, diags 146 } 147 148 if tt, ok := remain[1].(hcl.TraverseIndex); ok { 149 if !tt.Key.Type().Equals(cty.String) { 150 diags = diags.Append(&hcl.Diagnostic{ 151 Severity: hcl.DiagError, 152 Summary: "Invalid provider configuration address", 153 Detail: "The prefix \"provider.\" must be followed by a provider type name.", 154 Subject: remain[1].SourceRange().Ptr(), 155 }) 156 return ret, diags 157 } 158 p, sourceDiags := ParseProviderSourceString(tt.Key.AsString()) 159 ret.Provider = p 160 if sourceDiags.HasErrors() { 161 diags = diags.Append(sourceDiags) 162 return ret, diags 163 } 164 } else { 165 diags = diags.Append(&hcl.Diagnostic{ 166 Severity: hcl.DiagError, 167 Summary: "Invalid provider configuration address", 168 Detail: "The prefix \"provider.\" must be followed by a provider type name.", 169 Subject: remain[1].SourceRange().Ptr(), 170 }) 171 return ret, diags 172 } 173 174 if len(remain) == 3 { 175 if tt, ok := remain[2].(hcl.TraverseAttr); ok { 176 ret.Alias = tt.Name 177 } else { 178 diags = diags.Append(&hcl.Diagnostic{ 179 Severity: hcl.DiagError, 180 Summary: "Invalid provider configuration address", 181 Detail: "Provider type name must be followed by a configuration alias name.", 182 Subject: remain[2].SourceRange().Ptr(), 183 }) 184 return ret, diags 185 } 186 } 187 188 return ret, diags 189} 190 191// ParseAbsProviderConfigStr is a helper wrapper around ParseAbsProviderConfig 192// that takes a string and parses it with the HCL native syntax traversal parser 193// before interpreting it. 194// 195// This should be used only in specialized situations since it will cause the 196// created references to not have any meaningful source location information. 197// If a reference string is coming from a source that should be identified in 198// error messages then the caller should instead parse it directly using a 199// suitable function from the HCL API and pass the traversal itself to 200// ParseAbsProviderConfig. 201// 202// Error diagnostics are returned if either the parsing fails or the analysis 203// of the traversal fails. There is no way for the caller to distinguish the 204// two kinds of diagnostics programmatically. If error diagnostics are returned 205// the returned address is invalid. 206func ParseAbsProviderConfigStr(str string) (AbsProviderConfig, tfdiags.Diagnostics) { 207 var diags tfdiags.Diagnostics 208 traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) 209 diags = diags.Append(parseDiags) 210 if parseDiags.HasErrors() { 211 return AbsProviderConfig{}, diags 212 } 213 addr, addrDiags := ParseAbsProviderConfig(traversal) 214 diags = diags.Append(addrDiags) 215 return addr, diags 216} 217 218func ParseLegacyAbsProviderConfigStr(str string) (AbsProviderConfig, tfdiags.Diagnostics) { 219 var diags tfdiags.Diagnostics 220 221 traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) 222 diags = diags.Append(parseDiags) 223 if parseDiags.HasErrors() { 224 return AbsProviderConfig{}, diags 225 } 226 227 addr, addrDiags := ParseLegacyAbsProviderConfig(traversal) 228 diags = diags.Append(addrDiags) 229 return addr, diags 230} 231 232// ParseLegacyAbsProviderConfig parses the given traversal as an absolute 233// provider address. The following are examples of traversals that can be 234// successfully parsed as legacy absolute provider configuration addresses: 235// 236// provider.aws 237// provider.aws.foo 238// module.bar.provider.aws 239// module.bar.module.baz.provider.aws.foo 240// 241// This type of address is used in legacy state and may appear in state v4 if 242// the provider config addresses have not been normalized to include provider 243// FQN. 244func ParseLegacyAbsProviderConfig(traversal hcl.Traversal) (AbsProviderConfig, tfdiags.Diagnostics) { 245 modInst, remain, diags := parseModuleInstancePrefix(traversal) 246 var ret AbsProviderConfig 247 248 // Providers cannot resolve within module instances, so verify that there 249 // are no instance keys in the module path before converting to a Module. 250 for _, step := range modInst { 251 if step.InstanceKey != NoKey { 252 diags = diags.Append(&hcl.Diagnostic{ 253 Severity: hcl.DiagError, 254 Summary: "Invalid provider configuration address", 255 Detail: "Provider address cannot contain module indexes", 256 Subject: remain.SourceRange().Ptr(), 257 }) 258 return ret, diags 259 } 260 } 261 ret.Module = modInst.Module() 262 263 if len(remain) < 2 || remain.RootName() != "provider" { 264 diags = diags.Append(&hcl.Diagnostic{ 265 Severity: hcl.DiagError, 266 Summary: "Invalid provider configuration address", 267 Detail: "Provider address must begin with \"provider.\", followed by a provider type name.", 268 Subject: remain.SourceRange().Ptr(), 269 }) 270 return ret, diags 271 } 272 if len(remain) > 3 { 273 diags = diags.Append(&hcl.Diagnostic{ 274 Severity: hcl.DiagError, 275 Summary: "Invalid provider configuration address", 276 Detail: "Extraneous operators after provider configuration alias.", 277 Subject: hcl.Traversal(remain[3:]).SourceRange().Ptr(), 278 }) 279 return ret, diags 280 } 281 282 // We always assume legacy-style providers in legacy state ... 283 if tt, ok := remain[1].(hcl.TraverseAttr); ok { 284 // ... unless it's the builtin "terraform" provider, a special case. 285 if tt.Name == "terraform" { 286 ret.Provider = NewBuiltInProvider(tt.Name) 287 } else { 288 ret.Provider = NewLegacyProvider(tt.Name) 289 } 290 } else { 291 diags = diags.Append(&hcl.Diagnostic{ 292 Severity: hcl.DiagError, 293 Summary: "Invalid provider configuration address", 294 Detail: "The prefix \"provider.\" must be followed by a provider type name.", 295 Subject: remain[1].SourceRange().Ptr(), 296 }) 297 return ret, diags 298 } 299 300 if len(remain) == 3 { 301 if tt, ok := remain[2].(hcl.TraverseAttr); ok { 302 ret.Alias = tt.Name 303 } else { 304 diags = diags.Append(&hcl.Diagnostic{ 305 Severity: hcl.DiagError, 306 Summary: "Invalid provider configuration address", 307 Detail: "Provider type name must be followed by a configuration alias name.", 308 Subject: remain[2].SourceRange().Ptr(), 309 }) 310 return ret, diags 311 } 312 } 313 314 return ret, diags 315} 316 317// ProviderConfigDefault returns the address of the default provider config of 318// the given type inside the recieving module instance. 319func (m ModuleInstance) ProviderConfigDefault(provider Provider) AbsProviderConfig { 320 return AbsProviderConfig{ 321 Module: m.Module(), 322 Provider: provider, 323 } 324} 325 326// ProviderConfigAliased returns the address of an aliased provider config of 327// the given type and alias inside the recieving module instance. 328func (m ModuleInstance) ProviderConfigAliased(provider Provider, alias string) AbsProviderConfig { 329 return AbsProviderConfig{ 330 Module: m.Module(), 331 Provider: provider, 332 Alias: alias, 333 } 334} 335 336// providerConfig Implements addrs.ProviderConfig. 337func (pc AbsProviderConfig) providerConfig() {} 338 339// Inherited returns an address that the receiving configuration address might 340// inherit from in a parent module. The second bool return value indicates if 341// such inheritance is possible, and thus whether the returned address is valid. 342// 343// Inheritance is possible only for default (un-aliased) providers in modules 344// other than the root module. Even if a valid address is returned, inheritence 345// may not be performed for other reasons, such as if the calling module 346// provided explicit provider configurations within the call for this module. 347// The ProviderTransformer graph transform in the main terraform module has the 348// authoritative logic for provider inheritance, and this method is here mainly 349// just for its benefit. 350func (pc AbsProviderConfig) Inherited() (AbsProviderConfig, bool) { 351 // Can't inherit if we're already in the root. 352 if len(pc.Module) == 0 { 353 return AbsProviderConfig{}, false 354 } 355 356 // Can't inherit if we have an alias. 357 if pc.Alias != "" { 358 return AbsProviderConfig{}, false 359 } 360 361 // Otherwise, we might inherit from a configuration with the same 362 // provider type in the parent module instance. 363 parentMod := pc.Module.Parent() 364 return AbsProviderConfig{ 365 Module: parentMod, 366 Provider: pc.Provider, 367 }, true 368 369} 370 371// LegacyString() returns a legacy-style AbsProviderConfig string and should only be used for legacy state shimming. 372func (pc AbsProviderConfig) LegacyString() string { 373 if pc.Alias != "" { 374 if len(pc.Module) == 0 { 375 return fmt.Sprintf("%s.%s.%s", "provider", pc.Provider.LegacyString(), pc.Alias) 376 } else { 377 return fmt.Sprintf("%s.%s.%s.%s", pc.Module.String(), "provider", pc.Provider.LegacyString(), pc.Alias) 378 } 379 } 380 if len(pc.Module) == 0 { 381 return fmt.Sprintf("%s.%s", "provider", pc.Provider.LegacyString()) 382 } 383 return fmt.Sprintf("%s.%s.%s", pc.Module.String(), "provider", pc.Provider.LegacyString()) 384} 385 386// String() returns a string representation of an AbsProviderConfig in the following format: 387// 388// provider["example.com/namespace/name"] 389// provider["example.com/namespace/name"].alias 390// module.module-name.provider["example.com/namespace/name"] 391// module.module-name.provider["example.com/namespace/name"].alias 392func (pc AbsProviderConfig) String() string { 393 var parts []string 394 if len(pc.Module) > 0 { 395 parts = append(parts, pc.Module.String()) 396 } 397 398 parts = append(parts, fmt.Sprintf("provider[%q]", pc.Provider)) 399 400 if pc.Alias != "" { 401 parts = append(parts, pc.Alias) 402 } 403 404 return strings.Join(parts, ".") 405} 406