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