github.com/pulumi/terraform@v1.4.0/pkg/addrs/provider.go (about)

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