opentofu/addrs/provider_config.go
Martin Atkins ba894ee05c addrs: More string parsing helpers for addresses
Our main "parse" methods in this package work with hcl.Traversals, but
we're gradually adding helpers to parse these directly froms strings since
the visual noise of doing the traversal parse first is inconvenient in
situations where addresses are coming from non-config locations where
no source information is available anyway.
2018-10-16 18:49:20 -07:00

298 lines
9.9 KiB
Go

package addrs
import (
"fmt"
"github.com/hashicorp/terraform/tfdiags"
"github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hcl/hclsyntax"
)
// ProviderConfig is the address of a provider configuration.
type ProviderConfig struct {
Type string
// If not empty, Alias identifies which non-default (aliased) provider
// configuration this address refers to.
Alias string
}
// NewDefaultProviderConfig returns the address of the default (un-aliased)
// configuration for the provider with the given type name.
func NewDefaultProviderConfig(typeName string) ProviderConfig {
return ProviderConfig{
Type: typeName,
}
}
// ParseProviderConfigCompact parses the given absolute traversal as a relative
// provider address in compact form. The following are examples of traversals
// that can be successfully parsed as compact relative provider configuration
// addresses:
//
// aws
// aws.foo
//
// This function will panic if given a relative traversal.
//
// If the returned diagnostics contains errors then the result value is invalid
// and must not be used.
func ParseProviderConfigCompact(traversal hcl.Traversal) (ProviderConfig, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
ret := ProviderConfig{
Type: traversal.RootName(),
}
if len(traversal) < 2 {
// Just a type name, then.
return ret, diags
}
aliasStep := traversal[1]
switch ts := aliasStep.(type) {
case hcl.TraverseAttr:
ret.Alias = ts.Name
return ret, diags
default:
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider configuration address",
Detail: "The provider type name must either stand alone or be followed by an alias name separated with a dot.",
Subject: aliasStep.SourceRange().Ptr(),
})
}
if len(traversal) > 2 {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider configuration address",
Detail: "Extraneous extra operators after provider configuration address.",
Subject: traversal[2:].SourceRange().Ptr(),
})
}
return ret, diags
}
// ParseProviderConfigCompactStr is a helper wrapper around ParseProviderConfigCompact
// that takes a string and parses it with the HCL native syntax traversal parser
// before interpreting it.
//
// This should be used only in specialized situations since it will cause the
// created references to not have any meaningful source location information.
// If a reference string is coming from a source that should be identified in
// error messages then the caller should instead parse it directly using a
// suitable function from the HCL API and pass the traversal itself to
// ParseProviderConfigCompact.
//
// Error diagnostics are returned if either the parsing fails or the analysis
// of the traversal fails. There is no way for the caller to distinguish the
// two kinds of diagnostics programmatically. If error diagnostics are returned
// then the returned address is invalid.
func ParseProviderConfigCompactStr(str string) (ProviderConfig, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
diags = diags.Append(parseDiags)
if parseDiags.HasErrors() {
return ProviderConfig{}, diags
}
addr, addrDiags := ParseProviderConfigCompact(traversal)
diags = diags.Append(addrDiags)
return addr, diags
}
// Absolute returns an AbsProviderConfig from the receiver and the given module
// instance address.
func (pc ProviderConfig) Absolute(module ModuleInstance) AbsProviderConfig {
return AbsProviderConfig{
Module: module,
ProviderConfig: pc,
}
}
func (pc ProviderConfig) String() string {
if pc.Type == "" {
// Should never happen; always indicates a bug
return "provider.<invalid>"
}
if pc.Alias != "" {
return fmt.Sprintf("provider.%s.%s", pc.Type, pc.Alias)
}
return "provider." + pc.Type
}
// StringCompact is an alternative to String that returns the form that can
// be parsed by ParseProviderConfigCompact, without the "provider." prefix.
func (pc ProviderConfig) StringCompact() string {
if pc.Alias != "" {
return fmt.Sprintf("%s.%s", pc.Type, pc.Alias)
}
return pc.Type
}
// AbsProviderConfig is the absolute address of a provider configuration
// within a particular module instance.
type AbsProviderConfig struct {
Module ModuleInstance
ProviderConfig ProviderConfig
}
// ParseAbsProviderConfig parses the given traversal as an absolute provider
// address. The following are examples of traversals that can be successfully
// parsed as absolute provider configuration addresses:
//
// provider.aws
// provider.aws.foo
// module.bar.provider.aws
// module.bar.module.baz.provider.aws.foo
// module.foo[1].provider.aws.foo
//
// This type of address is used, for example, to record the relationships
// between resources and provider configurations in the state structure.
// This type of address is not generally used in the UI, except in error
// messages that refer to provider configurations.
func ParseAbsProviderConfig(traversal hcl.Traversal) (AbsProviderConfig, tfdiags.Diagnostics) {
modInst, remain, diags := parseModuleInstancePrefix(traversal)
ret := AbsProviderConfig{
Module: modInst,
}
if len(remain) < 2 || remain.RootName() != "provider" {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider configuration address",
Detail: "Provider address must begin with \"provider.\", followed by a provider type name.",
Subject: remain.SourceRange().Ptr(),
})
return ret, diags
}
if len(remain) > 3 {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider configuration address",
Detail: "Extraneous operators after provider configuration alias.",
Subject: hcl.Traversal(remain[3:]).SourceRange().Ptr(),
})
return ret, diags
}
if tt, ok := remain[1].(hcl.TraverseAttr); ok {
ret.ProviderConfig.Type = tt.Name
} else {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider configuration address",
Detail: "The prefix \"provider.\" must be followed by a provider type name.",
Subject: remain[1].SourceRange().Ptr(),
})
return ret, diags
}
if len(remain) == 3 {
if tt, ok := remain[2].(hcl.TraverseAttr); ok {
ret.ProviderConfig.Alias = tt.Name
} else {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider configuration address",
Detail: "Provider type name must be followed by a configuration alias name.",
Subject: remain[2].SourceRange().Ptr(),
})
return ret, diags
}
}
return ret, diags
}
// ParseAbsProviderConfigStr is a helper wrapper around ParseAbsProviderConfig
// that takes a string and parses it with the HCL native syntax traversal parser
// before interpreting it.
//
// This should be used only in specialized situations since it will cause the
// created references to not have any meaningful source location information.
// If a reference string is coming from a source that should be identified in
// error messages then the caller should instead parse it directly using a
// suitable function from the HCL API and pass the traversal itself to
// ParseAbsProviderConfig.
//
// Error diagnostics are returned if either the parsing fails or the analysis
// of the traversal fails. There is no way for the caller to distinguish the
// two kinds of diagnostics programmatically. If error diagnostics are returned
// the returned address is invalid.
func ParseAbsProviderConfigStr(str string) (AbsProviderConfig, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
diags = diags.Append(parseDiags)
if parseDiags.HasErrors() {
return AbsProviderConfig{}, diags
}
addr, addrDiags := ParseAbsProviderConfig(traversal)
diags = diags.Append(addrDiags)
return addr, diags
}
// ProviderConfigDefault returns the address of the default provider config
// of the given type inside the recieving module instance.
func (m ModuleInstance) ProviderConfigDefault(name string) AbsProviderConfig {
return AbsProviderConfig{
Module: m,
ProviderConfig: ProviderConfig{
Type: name,
},
}
}
// ProviderConfigAliased returns the address of an aliased provider config
// of with given type and alias inside the recieving module instance.
func (m ModuleInstance) ProviderConfigAliased(name, alias string) AbsProviderConfig {
return AbsProviderConfig{
Module: m,
ProviderConfig: ProviderConfig{
Type: name,
Alias: alias,
},
}
}
// Inherited returns an address that the receiving configuration address might
// inherit from in a parent module. The second bool return value indicates if
// such inheritance is possible, and thus whether the returned address is valid.
//
// Inheritance is possible only for default (un-aliased) providers in modules
// other than the root module. Even if a valid address is returned, inheritence
// may not be performed for other reasons, such as if the calling module
// provided explicit provider configurations within the call for this module.
// The ProviderTransformer graph transform in the main terraform module has
// the authoritative logic for provider inheritance, and this method is here
// mainly just for its benefit.
func (pc AbsProviderConfig) Inherited() (AbsProviderConfig, bool) {
// Can't inherit if we're already in the root.
if len(pc.Module) == 0 {
return AbsProviderConfig{}, false
}
// Can't inherit if we have an alias.
if pc.ProviderConfig.Alias != "" {
return AbsProviderConfig{}, false
}
// Otherwise, we might inherit from a configuration with the same
// provider name in the parent module instance.
parentMod := pc.Module.Parent()
return pc.ProviderConfig.Absolute(parentMod), true
}
func (pc AbsProviderConfig) String() string {
if len(pc.Module) == 0 {
return pc.ProviderConfig.String()
}
return fmt.Sprintf("%s.%s", pc.Module.String(), pc.ProviderConfig.String())
}