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

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package addrs
     5  
     6  import (
     7  	"github.com/hashicorp/hcl/v2"
     8  	tfaddr "github.com/hashicorp/terraform-registry-address"
     9  	svchost "github.com/hashicorp/terraform-svchost"
    10  	"github.com/terramate-io/tf/tfdiags"
    11  )
    12  
    13  // Provider encapsulates a single provider type. In the future this will be
    14  // extended to include additional fields including Namespace and SourceHost
    15  type Provider = tfaddr.Provider
    16  
    17  // DefaultProviderRegistryHost is the hostname used for provider addresses that do
    18  // not have an explicit hostname.
    19  const DefaultProviderRegistryHost = tfaddr.DefaultProviderRegistryHost
    20  
    21  // BuiltInProviderHost is the pseudo-hostname used for the "built-in" provider
    22  // namespace. Built-in provider addresses must also have their namespace set
    23  // to BuiltInProviderNamespace in order to be considered as built-in.
    24  const BuiltInProviderHost = tfaddr.BuiltInProviderHost
    25  
    26  // BuiltInProviderNamespace is the provider namespace used for "built-in"
    27  // providers. Built-in provider addresses must also have their hostname
    28  // set to BuiltInProviderHost in order to be considered as built-in.
    29  //
    30  // The this namespace is literally named "builtin", in the hope that users
    31  // who see FQNs containing this will be able to infer the way in which they are
    32  // special, even if they haven't encountered the concept formally yet.
    33  const BuiltInProviderNamespace = tfaddr.BuiltInProviderNamespace
    34  
    35  // LegacyProviderNamespace is the special string used in the Namespace field
    36  // of type Provider to mark a legacy provider address. This special namespace
    37  // value would normally be invalid, and can be used only when the hostname is
    38  // DefaultRegistryHost because that host owns the mapping from legacy name to
    39  // FQN.
    40  const LegacyProviderNamespace = tfaddr.LegacyProviderNamespace
    41  
    42  func IsDefaultProvider(addr Provider) bool {
    43  	return addr.Hostname == DefaultProviderRegistryHost && addr.Namespace == "hashicorp"
    44  }
    45  
    46  // NewProvider constructs a provider address from its parts, and normalizes
    47  // the namespace and type parts to lowercase using unicode case folding rules
    48  // so that resulting addrs.Provider values can be compared using standard
    49  // Go equality rules (==).
    50  //
    51  // The hostname is given as a svchost.Hostname, which is required by the
    52  // contract of that type to have already been normalized for equality testing.
    53  //
    54  // This function will panic if the given namespace or type name are not valid.
    55  // When accepting namespace or type values from outside the program, use
    56  // ParseProviderPart first to check that the given value is valid.
    57  func NewProvider(hostname svchost.Hostname, namespace, typeName string) Provider {
    58  	return tfaddr.NewProvider(hostname, namespace, typeName)
    59  }
    60  
    61  // ImpliedProviderForUnqualifiedType represents the rules for inferring what
    62  // provider FQN a user intended when only a naked type name is available.
    63  //
    64  // For all except the type name "terraform" this returns a so-called "default"
    65  // provider, which is under the registry.terraform.io/hashicorp/ namespace.
    66  //
    67  // As a special case, the string "terraform" maps to
    68  // "terraform.io/builtin/terraform" because that is the more likely user
    69  // intent than the now-unmaintained "registry.terraform.io/hashicorp/terraform"
    70  // which remains only for compatibility with older Terraform versions.
    71  func ImpliedProviderForUnqualifiedType(typeName string) Provider {
    72  	switch typeName {
    73  	case "terraform":
    74  		// Note for future maintainers: any additional strings we add here
    75  		// as implied to be builtin must never also be use as provider names
    76  		// in the registry.terraform.io/hashicorp/... namespace, because
    77  		// otherwise older versions of Terraform could implicitly select
    78  		// the registry name instead of the internal one.
    79  		return NewBuiltInProvider(typeName)
    80  	default:
    81  		return NewDefaultProvider(typeName)
    82  	}
    83  }
    84  
    85  // NewDefaultProvider returns the default address of a HashiCorp-maintained,
    86  // Registry-hosted provider.
    87  func NewDefaultProvider(name string) Provider {
    88  	return tfaddr.Provider{
    89  		Type:      MustParseProviderPart(name),
    90  		Namespace: "hashicorp",
    91  		Hostname:  DefaultProviderRegistryHost,
    92  	}
    93  }
    94  
    95  // NewBuiltInProvider returns the address of a "built-in" provider. See
    96  // the docs for Provider.IsBuiltIn for more information.
    97  func NewBuiltInProvider(name string) Provider {
    98  	return tfaddr.Provider{
    99  		Type:      MustParseProviderPart(name),
   100  		Namespace: BuiltInProviderNamespace,
   101  		Hostname:  BuiltInProviderHost,
   102  	}
   103  }
   104  
   105  // NewLegacyProvider returns a mock address for a provider.
   106  // This will be removed when ProviderType is fully integrated.
   107  func NewLegacyProvider(name string) Provider {
   108  	return Provider{
   109  		// We intentionally don't normalize and validate the legacy names,
   110  		// because existing code expects legacy provider names to pass through
   111  		// verbatim, even if not compliant with our new naming rules.
   112  		Type:      name,
   113  		Namespace: LegacyProviderNamespace,
   114  		Hostname:  DefaultProviderRegistryHost,
   115  	}
   116  }
   117  
   118  // ParseProviderSourceString parses a value of the form expected in the "source"
   119  // argument of a required_providers entry and returns the corresponding
   120  // fully-qualified provider address. This is intended primarily to parse the
   121  // FQN-like strings returned by terraform-config-inspect.
   122  //
   123  // The following are valid source string formats:
   124  //
   125  //   - name
   126  //   - namespace/name
   127  //   - hostname/namespace/name
   128  func ParseProviderSourceString(str string) (tfaddr.Provider, tfdiags.Diagnostics) {
   129  	var diags tfdiags.Diagnostics
   130  
   131  	ret, err := tfaddr.ParseProviderSource(str)
   132  	if pe, ok := err.(*tfaddr.ParserError); ok {
   133  		diags = diags.Append(&hcl.Diagnostic{
   134  			Severity: hcl.DiagError,
   135  			Summary:  pe.Summary,
   136  			Detail:   pe.Detail,
   137  		})
   138  		return ret, diags
   139  	}
   140  
   141  	if !ret.HasKnownNamespace() {
   142  		ret.Namespace = "hashicorp"
   143  	}
   144  
   145  	return ret, nil
   146  }
   147  
   148  // MustParseProviderSourceString is a wrapper around ParseProviderSourceString that panics if
   149  // it returns an error.
   150  func MustParseProviderSourceString(str string) Provider {
   151  	result, diags := ParseProviderSourceString(str)
   152  	if diags.HasErrors() {
   153  		panic(diags.Err().Error())
   154  	}
   155  	return result
   156  }
   157  
   158  // ParseProviderPart processes an addrs.Provider namespace or type string
   159  // provided by an end-user, producing a normalized version if possible or
   160  // an error if the string contains invalid characters.
   161  //
   162  // A provider part is processed in the same way as an individual label in a DNS
   163  // domain name: it is transformed to lowercase per the usual DNS case mapping
   164  // and normalization rules and may contain only letters, digits, and dashes.
   165  // Additionally, dashes may not appear at the start or end of the string.
   166  //
   167  // These restrictions are intended to allow these names to appear in fussy
   168  // contexts such as directory/file names on case-insensitive filesystems,
   169  // repository names on GitHub, etc. We're using the DNS rules in particular,
   170  // rather than some similar rules defined locally, because the hostname part
   171  // of an addrs.Provider is already a hostname and it's ideal to use exactly
   172  // the same case folding and normalization rules for all of the parts.
   173  //
   174  // In practice a provider type string conventionally does not contain dashes
   175  // either. Such names are permitted, but providers with such type names will be
   176  // hard to use because their resource type names will not be able to contain
   177  // the provider type name and thus each resource will need an explicit provider
   178  // address specified. (A real-world example of such a provider is the
   179  // "google-beta" variant of the GCP provider, which has resource types that
   180  // start with the "google_" prefix instead.)
   181  //
   182  // It's valid to pass the result of this function as the argument to a
   183  // subsequent call, in which case the result will be identical.
   184  func ParseProviderPart(given string) (string, error) {
   185  	return tfaddr.ParseProviderPart(given)
   186  }
   187  
   188  // MustParseProviderPart is a wrapper around ParseProviderPart that panics if
   189  // it returns an error.
   190  func MustParseProviderPart(given string) string {
   191  	result, err := ParseProviderPart(given)
   192  	if err != nil {
   193  		panic(err.Error())
   194  	}
   195  	return result
   196  }
   197  
   198  // IsProviderPartNormalized compares a given string to the result of ParseProviderPart(string)
   199  func IsProviderPartNormalized(str string) (bool, error) {
   200  	normalized, err := ParseProviderPart(str)
   201  	if err != nil {
   202  		return false, err
   203  	}
   204  	if str == normalized {
   205  		return true, nil
   206  	}
   207  	return false, nil
   208  }