github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/addrs/provider_config.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package addrs
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/terramate-io/tf/tfdiags"
    11  	"github.com/zclconf/go-cty/cty"
    12  
    13  	"github.com/hashicorp/hcl/v2"
    14  	"github.com/hashicorp/hcl/v2/hclsyntax"
    15  )
    16  
    17  // ProviderConfig is an interface type whose dynamic type can be either
    18  // LocalProviderConfig or AbsProviderConfig, in order to represent situations
    19  // where a value might either be module-local or absolute but the decision
    20  // cannot be made until runtime.
    21  //
    22  // Where possible, use either LocalProviderConfig or AbsProviderConfig directly
    23  // instead, to make intent more clear. ProviderConfig can be used only in
    24  // situations where the recipient of the value has some out-of-band way to
    25  // determine a "current module" to use if the value turns out to be
    26  // a LocalProviderConfig.
    27  //
    28  // Recipients of non-nil ProviderConfig values that actually need
    29  // AbsProviderConfig values should call ResolveAbsProviderAddr on the
    30  // *configs.Config value representing the root module configuration, which
    31  // handles the translation from local to fully-qualified using mapping tables
    32  // defined in the configuration.
    33  //
    34  // Recipients of a ProviderConfig value can assume it can contain only a
    35  // LocalProviderConfig value, an AbsProviderConfigValue, or nil to represent
    36  // the absense of a provider config in situations where that is meaningful.
    37  type ProviderConfig interface {
    38  	providerConfig()
    39  }
    40  
    41  // LocalProviderConfig is the address of a provider configuration from the
    42  // perspective of references in a particular module.
    43  //
    44  // Finding the corresponding AbsProviderConfig will require looking up the
    45  // LocalName in the providers table in the module's configuration; there is
    46  // no syntax-only translation between these types.
    47  type LocalProviderConfig struct {
    48  	LocalName string
    49  
    50  	// If not empty, Alias identifies which non-default (aliased) provider
    51  	// configuration this address refers to.
    52  	Alias string
    53  }
    54  
    55  var _ ProviderConfig = LocalProviderConfig{}
    56  
    57  // NewDefaultLocalProviderConfig returns the address of the default (un-aliased)
    58  // configuration for the provider with the given local type name.
    59  func NewDefaultLocalProviderConfig(LocalNameName string) LocalProviderConfig {
    60  	return LocalProviderConfig{
    61  		LocalName: LocalNameName,
    62  	}
    63  }
    64  
    65  // providerConfig Implements addrs.ProviderConfig.
    66  func (pc LocalProviderConfig) providerConfig() {}
    67  
    68  func (pc LocalProviderConfig) String() string {
    69  	if pc.LocalName == "" {
    70  		// Should never happen; always indicates a bug
    71  		return "provider.<invalid>"
    72  	}
    73  
    74  	if pc.Alias != "" {
    75  		return fmt.Sprintf("provider.%s.%s", pc.LocalName, pc.Alias)
    76  	}
    77  
    78  	return "provider." + pc.LocalName
    79  }
    80  
    81  // StringCompact is an alternative to String that returns the form that can
    82  // be parsed by ParseProviderConfigCompact, without the "provider." prefix.
    83  func (pc LocalProviderConfig) StringCompact() string {
    84  	if pc.Alias != "" {
    85  		return fmt.Sprintf("%s.%s", pc.LocalName, pc.Alias)
    86  	}
    87  	return pc.LocalName
    88  }
    89  
    90  // AbsProviderConfig is the absolute address of a provider configuration
    91  // within a particular module instance.
    92  type AbsProviderConfig struct {
    93  	Module   Module
    94  	Provider Provider
    95  	Alias    string
    96  }
    97  
    98  var _ ProviderConfig = AbsProviderConfig{}
    99  
   100  // ParseAbsProviderConfig parses the given traversal as an absolute provider
   101  // configuration address. The following are examples of traversals that can be
   102  // successfully parsed as absolute provider configuration addresses:
   103  //
   104  //   - provider["registry.terraform.io/hashicorp/aws"]
   105  //   - provider["registry.terraform.io/hashicorp/aws"].foo
   106  //   - module.bar.provider["registry.terraform.io/hashicorp/aws"]
   107  //   - module.bar.module.baz.provider["registry.terraform.io/hashicorp/aws"].foo
   108  //
   109  // This type of address is used, for example, to record the relationships
   110  // between resources and provider configurations in the state structure.
   111  // This type of address is typically not used prominently in the UI, except in
   112  // error messages that refer to provider configurations.
   113  func ParseAbsProviderConfig(traversal hcl.Traversal) (AbsProviderConfig, tfdiags.Diagnostics) {
   114  	modInst, remain, diags := parseModuleInstancePrefix(traversal)
   115  	var ret AbsProviderConfig
   116  
   117  	// Providers cannot resolve within module instances, so verify that there
   118  	// are no instance keys in the module path before converting to a Module.
   119  	for _, step := range modInst {
   120  		if step.InstanceKey != NoKey {
   121  			diags = diags.Append(&hcl.Diagnostic{
   122  				Severity: hcl.DiagError,
   123  				Summary:  "Invalid provider configuration address",
   124  				Detail:   "Provider address cannot contain module indexes",
   125  				Subject:  remain.SourceRange().Ptr(),
   126  			})
   127  			return ret, diags
   128  		}
   129  	}
   130  	ret.Module = modInst.Module()
   131  
   132  	if len(remain) < 2 || remain.RootName() != "provider" {
   133  		diags = diags.Append(&hcl.Diagnostic{
   134  			Severity: hcl.DiagError,
   135  			Summary:  "Invalid provider configuration address",
   136  			Detail:   "Provider address must begin with \"provider.\", followed by a provider type name.",
   137  			Subject:  remain.SourceRange().Ptr(),
   138  		})
   139  		return ret, diags
   140  	}
   141  	if len(remain) > 3 {
   142  		diags = diags.Append(&hcl.Diagnostic{
   143  			Severity: hcl.DiagError,
   144  			Summary:  "Invalid provider configuration address",
   145  			Detail:   "Extraneous operators after provider configuration alias.",
   146  			Subject:  hcl.Traversal(remain[3:]).SourceRange().Ptr(),
   147  		})
   148  		return ret, diags
   149  	}
   150  
   151  	if tt, ok := remain[1].(hcl.TraverseIndex); ok {
   152  		if !tt.Key.Type().Equals(cty.String) {
   153  			diags = diags.Append(&hcl.Diagnostic{
   154  				Severity: hcl.DiagError,
   155  				Summary:  "Invalid provider configuration address",
   156  				Detail:   "The prefix \"provider.\" must be followed by a provider type name.",
   157  				Subject:  remain[1].SourceRange().Ptr(),
   158  			})
   159  			return ret, diags
   160  		}
   161  		p, sourceDiags := ParseProviderSourceString(tt.Key.AsString())
   162  		ret.Provider = p
   163  		if sourceDiags.HasErrors() {
   164  			diags = diags.Append(sourceDiags)
   165  			return ret, diags
   166  		}
   167  	} else {
   168  		diags = diags.Append(&hcl.Diagnostic{
   169  			Severity: hcl.DiagError,
   170  			Summary:  "Invalid provider configuration address",
   171  			Detail:   "The prefix \"provider.\" must be followed by a provider type name.",
   172  			Subject:  remain[1].SourceRange().Ptr(),
   173  		})
   174  		return ret, diags
   175  	}
   176  
   177  	if len(remain) == 3 {
   178  		if tt, ok := remain[2].(hcl.TraverseAttr); ok {
   179  			ret.Alias = tt.Name
   180  		} else {
   181  			diags = diags.Append(&hcl.Diagnostic{
   182  				Severity: hcl.DiagError,
   183  				Summary:  "Invalid provider configuration address",
   184  				Detail:   "Provider type name must be followed by a configuration alias name.",
   185  				Subject:  remain[2].SourceRange().Ptr(),
   186  			})
   187  			return ret, diags
   188  		}
   189  	}
   190  
   191  	return ret, diags
   192  }
   193  
   194  // ParseAbsProviderConfigStr is a helper wrapper around ParseAbsProviderConfig
   195  // that takes a string and parses it with the HCL native syntax traversal parser
   196  // before interpreting it.
   197  //
   198  // This should be used only in specialized situations since it will cause the
   199  // created references to not have any meaningful source location information.
   200  // If a reference string is coming from a source that should be identified in
   201  // error messages then the caller should instead parse it directly using a
   202  // suitable function from the HCL API and pass the traversal itself to
   203  // ParseAbsProviderConfig.
   204  //
   205  // Error diagnostics are returned if either the parsing fails or the analysis
   206  // of the traversal fails. There is no way for the caller to distinguish the
   207  // two kinds of diagnostics programmatically. If error diagnostics are returned
   208  // the returned address is invalid.
   209  func ParseAbsProviderConfigStr(str string) (AbsProviderConfig, tfdiags.Diagnostics) {
   210  	var diags tfdiags.Diagnostics
   211  	traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
   212  	diags = diags.Append(parseDiags)
   213  	if parseDiags.HasErrors() {
   214  		return AbsProviderConfig{}, diags
   215  	}
   216  	addr, addrDiags := ParseAbsProviderConfig(traversal)
   217  	diags = diags.Append(addrDiags)
   218  	return addr, diags
   219  }
   220  
   221  func ParseLegacyAbsProviderConfigStr(str string) (AbsProviderConfig, tfdiags.Diagnostics) {
   222  	var diags tfdiags.Diagnostics
   223  
   224  	traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
   225  	diags = diags.Append(parseDiags)
   226  	if parseDiags.HasErrors() {
   227  		return AbsProviderConfig{}, diags
   228  	}
   229  
   230  	addr, addrDiags := ParseLegacyAbsProviderConfig(traversal)
   231  	diags = diags.Append(addrDiags)
   232  	return addr, diags
   233  }
   234  
   235  // ParseLegacyAbsProviderConfig parses the given traversal as an absolute
   236  // provider address in the legacy form used by Terraform v0.12 and earlier.
   237  // The following are examples of traversals that can be successfully parsed as
   238  // legacy absolute provider configuration addresses:
   239  //
   240  //   - provider.aws
   241  //   - provider.aws.foo
   242  //   - module.bar.provider.aws
   243  //   - module.bar.module.baz.provider.aws.foo
   244  //
   245  // We can encounter this kind of address in a historical state snapshot that
   246  // hasn't yet been upgraded by refreshing or applying a plan with
   247  // Terraform v0.13. Later versions of Terraform reject state snapshots using
   248  // this format, and so users must follow the Terraform v0.13 upgrade guide
   249  // in that case.
   250  //
   251  // We will not use this address form for any new file formats.
   252  func ParseLegacyAbsProviderConfig(traversal hcl.Traversal) (AbsProviderConfig, tfdiags.Diagnostics) {
   253  	modInst, remain, diags := parseModuleInstancePrefix(traversal)
   254  	var ret AbsProviderConfig
   255  
   256  	// Providers cannot resolve within module instances, so verify that there
   257  	// are no instance keys in the module path before converting to a Module.
   258  	for _, step := range modInst {
   259  		if step.InstanceKey != NoKey {
   260  			diags = diags.Append(&hcl.Diagnostic{
   261  				Severity: hcl.DiagError,
   262  				Summary:  "Invalid provider configuration address",
   263  				Detail:   "Provider address cannot contain module indexes",
   264  				Subject:  remain.SourceRange().Ptr(),
   265  			})
   266  			return ret, diags
   267  		}
   268  	}
   269  	ret.Module = modInst.Module()
   270  
   271  	if len(remain) < 2 || remain.RootName() != "provider" {
   272  		diags = diags.Append(&hcl.Diagnostic{
   273  			Severity: hcl.DiagError,
   274  			Summary:  "Invalid provider configuration address",
   275  			Detail:   "Provider address must begin with \"provider.\", followed by a provider type name.",
   276  			Subject:  remain.SourceRange().Ptr(),
   277  		})
   278  		return ret, diags
   279  	}
   280  	if len(remain) > 3 {
   281  		diags = diags.Append(&hcl.Diagnostic{
   282  			Severity: hcl.DiagError,
   283  			Summary:  "Invalid provider configuration address",
   284  			Detail:   "Extraneous operators after provider configuration alias.",
   285  			Subject:  hcl.Traversal(remain[3:]).SourceRange().Ptr(),
   286  		})
   287  		return ret, diags
   288  	}
   289  
   290  	// We always assume legacy-style providers in legacy state ...
   291  	if tt, ok := remain[1].(hcl.TraverseAttr); ok {
   292  		// ... unless it's the builtin "terraform" provider, a special case.
   293  		if tt.Name == "terraform" {
   294  			ret.Provider = NewBuiltInProvider(tt.Name)
   295  		} else {
   296  			ret.Provider = NewLegacyProvider(tt.Name)
   297  		}
   298  	} else {
   299  		diags = diags.Append(&hcl.Diagnostic{
   300  			Severity: hcl.DiagError,
   301  			Summary:  "Invalid provider configuration address",
   302  			Detail:   "The prefix \"provider.\" must be followed by a provider type name.",
   303  			Subject:  remain[1].SourceRange().Ptr(),
   304  		})
   305  		return ret, diags
   306  	}
   307  
   308  	if len(remain) == 3 {
   309  		if tt, ok := remain[2].(hcl.TraverseAttr); ok {
   310  			ret.Alias = tt.Name
   311  		} else {
   312  			diags = diags.Append(&hcl.Diagnostic{
   313  				Severity: hcl.DiagError,
   314  				Summary:  "Invalid provider configuration address",
   315  				Detail:   "Provider type name must be followed by a configuration alias name.",
   316  				Subject:  remain[2].SourceRange().Ptr(),
   317  			})
   318  			return ret, diags
   319  		}
   320  	}
   321  
   322  	return ret, diags
   323  }
   324  
   325  // ProviderConfigDefault returns the address of the default provider config of
   326  // the given type inside the recieving module instance.
   327  func (m ModuleInstance) ProviderConfigDefault(provider Provider) AbsProviderConfig {
   328  	return AbsProviderConfig{
   329  		Module:   m.Module(),
   330  		Provider: provider,
   331  	}
   332  }
   333  
   334  // ProviderConfigAliased returns the address of an aliased provider config of
   335  // the given type and alias inside the recieving module instance.
   336  func (m ModuleInstance) ProviderConfigAliased(provider Provider, alias string) AbsProviderConfig {
   337  	return AbsProviderConfig{
   338  		Module:   m.Module(),
   339  		Provider: provider,
   340  		Alias:    alias,
   341  	}
   342  }
   343  
   344  // providerConfig Implements addrs.ProviderConfig.
   345  func (pc AbsProviderConfig) providerConfig() {}
   346  
   347  // Inherited returns an address that the receiving configuration address might
   348  // inherit from in a parent module. The second bool return value indicates if
   349  // such inheritance is possible, and thus whether the returned address is valid.
   350  //
   351  // Inheritance is possible only for default (un-aliased) providers in modules
   352  // other than the root module. Even if a valid address is returned, inheritence
   353  // may not be performed for other reasons, such as if the calling module
   354  // provided explicit provider configurations within the call for this module.
   355  // The ProviderTransformer graph transform in the main terraform module has the
   356  // authoritative logic for provider inheritance, and this method is here mainly
   357  // just for its benefit.
   358  func (pc AbsProviderConfig) Inherited() (AbsProviderConfig, bool) {
   359  	// Can't inherit if we're already in the root.
   360  	if len(pc.Module) == 0 {
   361  		return AbsProviderConfig{}, false
   362  	}
   363  
   364  	// Can't inherit if we have an alias.
   365  	if pc.Alias != "" {
   366  		return AbsProviderConfig{}, false
   367  	}
   368  
   369  	// Otherwise, we might inherit from a configuration with the same
   370  	// provider type in the parent module instance.
   371  	parentMod := pc.Module.Parent()
   372  	return AbsProviderConfig{
   373  		Module:   parentMod,
   374  		Provider: pc.Provider,
   375  	}, true
   376  
   377  }
   378  
   379  // LegacyString() returns a legacy-style AbsProviderConfig string and should only be used for legacy state shimming.
   380  func (pc AbsProviderConfig) LegacyString() string {
   381  	if pc.Alias != "" {
   382  		if len(pc.Module) == 0 {
   383  			return fmt.Sprintf("%s.%s.%s", "provider", pc.Provider.LegacyString(), pc.Alias)
   384  		} else {
   385  			return fmt.Sprintf("%s.%s.%s.%s", pc.Module.String(), "provider", pc.Provider.LegacyString(), pc.Alias)
   386  		}
   387  	}
   388  	if len(pc.Module) == 0 {
   389  		return fmt.Sprintf("%s.%s", "provider", pc.Provider.LegacyString())
   390  	}
   391  	return fmt.Sprintf("%s.%s.%s", pc.Module.String(), "provider", pc.Provider.LegacyString())
   392  }
   393  
   394  // String() returns a string representation of an AbsProviderConfig in a format like the following examples:
   395  //
   396  //   - provider["example.com/namespace/name"]
   397  //   - provider["example.com/namespace/name"].alias
   398  //   - module.module-name.provider["example.com/namespace/name"]
   399  //   - module.module-name.provider["example.com/namespace/name"].alias
   400  func (pc AbsProviderConfig) String() string {
   401  	var parts []string
   402  	if len(pc.Module) > 0 {
   403  		parts = append(parts, pc.Module.String())
   404  	}
   405  
   406  	parts = append(parts, fmt.Sprintf("provider[%q]", pc.Provider))
   407  
   408  	if pc.Alias != "" {
   409  		parts = append(parts, pc.Alias)
   410  	}
   411  
   412  	return strings.Join(parts, ".")
   413  }