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