github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/addrs/provider_config.go (about)

     1  package addrs
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/cycloidio/terraform/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.
    34  type 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.
    44  type 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  
    52  var _ ProviderConfig = LocalProviderConfig{}
    53  
    54  // NewDefaultLocalProviderConfig returns the address of the default (un-aliased)
    55  // configuration for the provider with the given local type name.
    56  func NewDefaultLocalProviderConfig(LocalNameName string) LocalProviderConfig {
    57  	return LocalProviderConfig{
    58  		LocalName: LocalNameName,
    59  	}
    60  }
    61  
    62  // providerConfig Implements addrs.ProviderConfig.
    63  func (pc LocalProviderConfig) providerConfig() {}
    64  
    65  func (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.
    80  func (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.
    89  type AbsProviderConfig struct {
    90  	Module   Module
    91  	Provider Provider
    92  	Alias    string
    93  }
    94  
    95  var _ 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.
   110  func 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.
   206  func 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  
   218  func 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.
   244  func 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.
   319  func (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.
   328  func (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.
   337  func (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.
   350  func (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.
   372  func (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
   392  func (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  }