1package addrs 2 3import ( 4 "fmt" 5 6 "github.com/hashicorp/terraform/tfdiags" 7 8 "github.com/hashicorp/hcl2/hcl" 9 "github.com/hashicorp/hcl2/hcl/hclsyntax" 10) 11 12// ProviderConfig is the address of a provider configuration. 13type ProviderConfig struct { 14 Type string 15 16 // If not empty, Alias identifies which non-default (aliased) provider 17 // configuration this address refers to. 18 Alias string 19} 20 21// NewDefaultProviderConfig returns the address of the default (un-aliased) 22// configuration for the provider with the given type name. 23func NewDefaultProviderConfig(typeName string) ProviderConfig { 24 return ProviderConfig{ 25 Type: typeName, 26 } 27} 28 29// ParseProviderConfigCompact parses the given absolute traversal as a relative 30// provider address in compact form. The following are examples of traversals 31// that can be successfully parsed as compact relative provider configuration 32// addresses: 33// 34// aws 35// aws.foo 36// 37// This function will panic if given a relative traversal. 38// 39// If the returned diagnostics contains errors then the result value is invalid 40// and must not be used. 41func ParseProviderConfigCompact(traversal hcl.Traversal) (ProviderConfig, tfdiags.Diagnostics) { 42 var diags tfdiags.Diagnostics 43 ret := ProviderConfig{ 44 Type: traversal.RootName(), 45 } 46 47 if len(traversal) < 2 { 48 // Just a type name, then. 49 return ret, diags 50 } 51 52 aliasStep := traversal[1] 53 switch ts := aliasStep.(type) { 54 case hcl.TraverseAttr: 55 ret.Alias = ts.Name 56 return ret, diags 57 default: 58 diags = diags.Append(&hcl.Diagnostic{ 59 Severity: hcl.DiagError, 60 Summary: "Invalid provider configuration address", 61 Detail: "The provider type name must either stand alone or be followed by an alias name separated with a dot.", 62 Subject: aliasStep.SourceRange().Ptr(), 63 }) 64 } 65 66 if len(traversal) > 2 { 67 diags = diags.Append(&hcl.Diagnostic{ 68 Severity: hcl.DiagError, 69 Summary: "Invalid provider configuration address", 70 Detail: "Extraneous extra operators after provider configuration address.", 71 Subject: traversal[2:].SourceRange().Ptr(), 72 }) 73 } 74 75 return ret, diags 76} 77 78// ParseProviderConfigCompactStr is a helper wrapper around ParseProviderConfigCompact 79// that takes a string and parses it with the HCL native syntax traversal parser 80// before interpreting it. 81// 82// This should be used only in specialized situations since it will cause the 83// created references to not have any meaningful source location information. 84// If a reference string is coming from a source that should be identified in 85// error messages then the caller should instead parse it directly using a 86// suitable function from the HCL API and pass the traversal itself to 87// ParseProviderConfigCompact. 88// 89// Error diagnostics are returned if either the parsing fails or the analysis 90// of the traversal fails. There is no way for the caller to distinguish the 91// two kinds of diagnostics programmatically. If error diagnostics are returned 92// then the returned address is invalid. 93func ParseProviderConfigCompactStr(str string) (ProviderConfig, tfdiags.Diagnostics) { 94 var diags tfdiags.Diagnostics 95 96 traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) 97 diags = diags.Append(parseDiags) 98 if parseDiags.HasErrors() { 99 return ProviderConfig{}, diags 100 } 101 102 addr, addrDiags := ParseProviderConfigCompact(traversal) 103 diags = diags.Append(addrDiags) 104 return addr, diags 105} 106 107// Absolute returns an AbsProviderConfig from the receiver and the given module 108// instance address. 109func (pc ProviderConfig) Absolute(module ModuleInstance) AbsProviderConfig { 110 return AbsProviderConfig{ 111 Module: module, 112 ProviderConfig: pc, 113 } 114} 115 116func (pc ProviderConfig) String() string { 117 if pc.Type == "" { 118 // Should never happen; always indicates a bug 119 return "provider.<invalid>" 120 } 121 122 if pc.Alias != "" { 123 return fmt.Sprintf("provider.%s.%s", pc.Type, pc.Alias) 124 } 125 126 return "provider." + pc.Type 127} 128 129// StringCompact is an alternative to String that returns the form that can 130// be parsed by ParseProviderConfigCompact, without the "provider." prefix. 131func (pc ProviderConfig) StringCompact() string { 132 if pc.Alias != "" { 133 return fmt.Sprintf("%s.%s", pc.Type, pc.Alias) 134 } 135 return pc.Type 136} 137 138// AbsProviderConfig is the absolute address of a provider configuration 139// within a particular module instance. 140type AbsProviderConfig struct { 141 Module ModuleInstance 142 ProviderConfig ProviderConfig 143} 144 145// ParseAbsProviderConfig parses the given traversal as an absolute provider 146// address. The following are examples of traversals that can be successfully 147// parsed as absolute provider configuration addresses: 148// 149// provider.aws 150// provider.aws.foo 151// module.bar.provider.aws 152// module.bar.module.baz.provider.aws.foo 153// module.foo[1].provider.aws.foo 154// 155// This type of address is used, for example, to record the relationships 156// between resources and provider configurations in the state structure. 157// This type of address is not generally used in the UI, except in error 158// messages that refer to provider configurations. 159func ParseAbsProviderConfig(traversal hcl.Traversal) (AbsProviderConfig, tfdiags.Diagnostics) { 160 modInst, remain, diags := parseModuleInstancePrefix(traversal) 161 ret := AbsProviderConfig{ 162 Module: modInst, 163 } 164 if len(remain) < 2 || remain.RootName() != "provider" { 165 diags = diags.Append(&hcl.Diagnostic{ 166 Severity: hcl.DiagError, 167 Summary: "Invalid provider configuration address", 168 Detail: "Provider address must begin with \"provider.\", followed by a provider type name.", 169 Subject: remain.SourceRange().Ptr(), 170 }) 171 return ret, diags 172 } 173 if len(remain) > 3 { 174 diags = diags.Append(&hcl.Diagnostic{ 175 Severity: hcl.DiagError, 176 Summary: "Invalid provider configuration address", 177 Detail: "Extraneous operators after provider configuration alias.", 178 Subject: hcl.Traversal(remain[3:]).SourceRange().Ptr(), 179 }) 180 return ret, diags 181 } 182 183 if tt, ok := remain[1].(hcl.TraverseAttr); ok { 184 ret.ProviderConfig.Type = tt.Name 185 } else { 186 diags = diags.Append(&hcl.Diagnostic{ 187 Severity: hcl.DiagError, 188 Summary: "Invalid provider configuration address", 189 Detail: "The prefix \"provider.\" must be followed by a provider type name.", 190 Subject: remain[1].SourceRange().Ptr(), 191 }) 192 return ret, diags 193 } 194 195 if len(remain) == 3 { 196 if tt, ok := remain[2].(hcl.TraverseAttr); ok { 197 ret.ProviderConfig.Alias = tt.Name 198 } else { 199 diags = diags.Append(&hcl.Diagnostic{ 200 Severity: hcl.DiagError, 201 Summary: "Invalid provider configuration address", 202 Detail: "Provider type name must be followed by a configuration alias name.", 203 Subject: remain[2].SourceRange().Ptr(), 204 }) 205 return ret, diags 206 } 207 } 208 209 return ret, diags 210} 211 212// ParseAbsProviderConfigStr is a helper wrapper around ParseAbsProviderConfig 213// that takes a string and parses it with the HCL native syntax traversal parser 214// before interpreting it. 215// 216// This should be used only in specialized situations since it will cause the 217// created references to not have any meaningful source location information. 218// If a reference string is coming from a source that should be identified in 219// error messages then the caller should instead parse it directly using a 220// suitable function from the HCL API and pass the traversal itself to 221// ParseAbsProviderConfig. 222// 223// Error diagnostics are returned if either the parsing fails or the analysis 224// of the traversal fails. There is no way for the caller to distinguish the 225// two kinds of diagnostics programmatically. If error diagnostics are returned 226// the returned address is invalid. 227func ParseAbsProviderConfigStr(str string) (AbsProviderConfig, tfdiags.Diagnostics) { 228 var diags tfdiags.Diagnostics 229 230 traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) 231 diags = diags.Append(parseDiags) 232 if parseDiags.HasErrors() { 233 return AbsProviderConfig{}, diags 234 } 235 236 addr, addrDiags := ParseAbsProviderConfig(traversal) 237 diags = diags.Append(addrDiags) 238 return addr, diags 239} 240 241// ProviderConfigDefault returns the address of the default provider config 242// of the given type inside the recieving module instance. 243func (m ModuleInstance) ProviderConfigDefault(name string) AbsProviderConfig { 244 return AbsProviderConfig{ 245 Module: m, 246 ProviderConfig: ProviderConfig{ 247 Type: name, 248 }, 249 } 250} 251 252// ProviderConfigAliased returns the address of an aliased provider config 253// of with given type and alias inside the recieving module instance. 254func (m ModuleInstance) ProviderConfigAliased(name, alias string) AbsProviderConfig { 255 return AbsProviderConfig{ 256 Module: m, 257 ProviderConfig: ProviderConfig{ 258 Type: name, 259 Alias: alias, 260 }, 261 } 262} 263 264// Inherited returns an address that the receiving configuration address might 265// inherit from in a parent module. The second bool return value indicates if 266// such inheritance is possible, and thus whether the returned address is valid. 267// 268// Inheritance is possible only for default (un-aliased) providers in modules 269// other than the root module. Even if a valid address is returned, inheritence 270// may not be performed for other reasons, such as if the calling module 271// provided explicit provider configurations within the call for this module. 272// The ProviderTransformer graph transform in the main terraform module has 273// the authoritative logic for provider inheritance, and this method is here 274// mainly just for its benefit. 275func (pc AbsProviderConfig) Inherited() (AbsProviderConfig, bool) { 276 // Can't inherit if we're already in the root. 277 if len(pc.Module) == 0 { 278 return AbsProviderConfig{}, false 279 } 280 281 // Can't inherit if we have an alias. 282 if pc.ProviderConfig.Alias != "" { 283 return AbsProviderConfig{}, false 284 } 285 286 // Otherwise, we might inherit from a configuration with the same 287 // provider name in the parent module instance. 288 parentMod := pc.Module.Parent() 289 return pc.ProviderConfig.Absolute(parentMod), true 290} 291 292func (pc AbsProviderConfig) String() string { 293 if len(pc.Module) == 0 { 294 return pc.ProviderConfig.String() 295 } 296 return fmt.Sprintf("%s.%s", pc.Module.String(), pc.ProviderConfig.String()) 297} 298