mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-26 17:01:04 -06:00
209 lines
8.2 KiB
Go
209 lines
8.2 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package addrs
|
|
|
|
import (
|
|
"github.com/hashicorp/hcl/v2"
|
|
tfaddr "github.com/hashicorp/terraform-registry-address"
|
|
svchost "github.com/hashicorp/terraform-svchost"
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
)
|
|
|
|
// Provider encapsulates a single provider type. In the future this will be
|
|
// extended to include additional fields including Namespace and SourceHost
|
|
type Provider = tfaddr.Provider
|
|
|
|
// DefaultProviderRegistryHost is the hostname used for provider addresses that do
|
|
// not have an explicit hostname.
|
|
const DefaultProviderRegistryHost = tfaddr.DefaultProviderRegistryHost
|
|
|
|
// BuiltInProviderHost is the pseudo-hostname used for the "built-in" provider
|
|
// namespace. Built-in provider addresses must also have their namespace set
|
|
// to BuiltInProviderNamespace in order to be considered as built-in.
|
|
const BuiltInProviderHost = tfaddr.BuiltInProviderHost
|
|
|
|
// BuiltInProviderNamespace is the provider namespace used for "built-in"
|
|
// providers. Built-in provider addresses must also have their hostname
|
|
// set to BuiltInProviderHost in order to be considered as built-in.
|
|
//
|
|
// The this namespace is literally named "builtin", in the hope that users
|
|
// who see FQNs containing this will be able to infer the way in which they are
|
|
// special, even if they haven't encountered the concept formally yet.
|
|
const BuiltInProviderNamespace = tfaddr.BuiltInProviderNamespace
|
|
|
|
// LegacyProviderNamespace is the special string used in the Namespace field
|
|
// of type Provider to mark a legacy provider address. This special namespace
|
|
// value would normally be invalid, and can be used only when the hostname is
|
|
// DefaultRegistryHost because that host owns the mapping from legacy name to
|
|
// FQN.
|
|
const LegacyProviderNamespace = tfaddr.LegacyProviderNamespace
|
|
|
|
func IsDefaultProvider(addr Provider) bool {
|
|
return addr.Hostname == DefaultProviderRegistryHost && addr.Namespace == "hashicorp"
|
|
}
|
|
|
|
// NewProvider constructs a provider address from its parts, and normalizes
|
|
// the namespace and type parts to lowercase using unicode case folding rules
|
|
// so that resulting addrs.Provider values can be compared using standard
|
|
// Go equality rules (==).
|
|
//
|
|
// The hostname is given as a svchost.Hostname, which is required by the
|
|
// contract of that type to have already been normalized for equality testing.
|
|
//
|
|
// This function will panic if the given namespace or type name are not valid.
|
|
// When accepting namespace or type values from outside the program, use
|
|
// ParseProviderPart first to check that the given value is valid.
|
|
func NewProvider(hostname svchost.Hostname, namespace, typeName string) Provider {
|
|
return tfaddr.NewProvider(hostname, namespace, typeName)
|
|
}
|
|
|
|
// ImpliedProviderForUnqualifiedType represents the rules for inferring what
|
|
// provider FQN a user intended when only a naked type name is available.
|
|
//
|
|
// For all except the type name "terraform" this returns a so-called "default"
|
|
// provider, which is under the registry.terraform.io/hashicorp/ namespace.
|
|
//
|
|
// As a special case, the string "terraform" maps to
|
|
// "terraform.io/builtin/terraform" because that is the more likely user
|
|
// intent than the now-unmaintained "registry.terraform.io/hashicorp/terraform"
|
|
// which remains only for compatibility with older OpenTofu versions.
|
|
func ImpliedProviderForUnqualifiedType(typeName string) Provider {
|
|
switch typeName {
|
|
case "terraform":
|
|
// Note for future maintainers: any additional strings we add here
|
|
// as implied to be builtin must never also be use as provider names
|
|
// in the registry.terraform.io/hashicorp/... namespace, because
|
|
// otherwise older versions of OpenTofu could implicitly select
|
|
// the registry name instead of the internal one.
|
|
return NewBuiltInProvider(typeName)
|
|
default:
|
|
return NewDefaultProvider(typeName)
|
|
}
|
|
}
|
|
|
|
// NewDefaultProvider returns the default address of a HashiCorp-maintained,
|
|
// Registry-hosted provider.
|
|
func NewDefaultProvider(name string) Provider {
|
|
return tfaddr.Provider{
|
|
Type: MustParseProviderPart(name),
|
|
Namespace: "hashicorp",
|
|
Hostname: DefaultProviderRegistryHost,
|
|
}
|
|
}
|
|
|
|
// NewBuiltInProvider returns the address of a "built-in" provider. See
|
|
// the docs for Provider.IsBuiltIn for more information.
|
|
func NewBuiltInProvider(name string) Provider {
|
|
return tfaddr.Provider{
|
|
Type: MustParseProviderPart(name),
|
|
Namespace: BuiltInProviderNamespace,
|
|
Hostname: BuiltInProviderHost,
|
|
}
|
|
}
|
|
|
|
// NewLegacyProvider returns a mock address for a provider.
|
|
// This will be removed when ProviderType is fully integrated.
|
|
func NewLegacyProvider(name string) Provider {
|
|
return Provider{
|
|
// We intentionally don't normalize and validate the legacy names,
|
|
// because existing code expects legacy provider names to pass through
|
|
// verbatim, even if not compliant with our new naming rules.
|
|
Type: name,
|
|
Namespace: LegacyProviderNamespace,
|
|
Hostname: DefaultProviderRegistryHost,
|
|
}
|
|
}
|
|
|
|
// ParseProviderSourceString parses a value of the form expected in the "source"
|
|
// argument of a required_providers entry and returns the corresponding
|
|
// fully-qualified provider address. This is intended primarily to parse the
|
|
// FQN-like strings returned by terraform-config-inspect.
|
|
//
|
|
// The following are valid source string formats:
|
|
//
|
|
// - name
|
|
// - namespace/name
|
|
// - hostname/namespace/name
|
|
func ParseProviderSourceString(str string) (tfaddr.Provider, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
ret, err := tfaddr.ParseProviderSource(str)
|
|
if pe, ok := err.(*tfaddr.ParserError); ok {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: pe.Summary,
|
|
Detail: pe.Detail,
|
|
})
|
|
return ret, diags
|
|
}
|
|
|
|
if !ret.HasKnownNamespace() {
|
|
ret.Namespace = "hashicorp"
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// MustParseProviderSourceString is a wrapper around ParseProviderSourceString that panics if
|
|
// it returns an error.
|
|
func MustParseProviderSourceString(str string) Provider {
|
|
result, diags := ParseProviderSourceString(str)
|
|
if diags.HasErrors() {
|
|
panic(diags.Err().Error())
|
|
}
|
|
return result
|
|
}
|
|
|
|
// ParseProviderPart processes an addrs.Provider namespace or type string
|
|
// provided by an end-user, producing a normalized version if possible or
|
|
// an error if the string contains invalid characters.
|
|
//
|
|
// A provider part is processed in the same way as an individual label in a DNS
|
|
// domain name: it is transformed to lowercase per the usual DNS case mapping
|
|
// and normalization rules and may contain only letters, digits, and dashes.
|
|
// Additionally, dashes may not appear at the start or end of the string.
|
|
//
|
|
// These restrictions are intended to allow these names to appear in fussy
|
|
// contexts such as directory/file names on case-insensitive filesystems,
|
|
// repository names on GitHub, etc. We're using the DNS rules in particular,
|
|
// rather than some similar rules defined locally, because the hostname part
|
|
// of an addrs.Provider is already a hostname and it's ideal to use exactly
|
|
// the same case folding and normalization rules for all of the parts.
|
|
//
|
|
// In practice a provider type string conventionally does not contain dashes
|
|
// either. Such names are permitted, but providers with such type names will be
|
|
// hard to use because their resource type names will not be able to contain
|
|
// the provider type name and thus each resource will need an explicit provider
|
|
// address specified. (A real-world example of such a provider is the
|
|
// "google-beta" variant of the GCP provider, which has resource types that
|
|
// start with the "google_" prefix instead.)
|
|
//
|
|
// It's valid to pass the result of this function as the argument to a
|
|
// subsequent call, in which case the result will be identical.
|
|
func ParseProviderPart(given string) (string, error) {
|
|
return tfaddr.ParseProviderPart(given)
|
|
}
|
|
|
|
// MustParseProviderPart is a wrapper around ParseProviderPart that panics if
|
|
// it returns an error.
|
|
func MustParseProviderPart(given string) string {
|
|
result, err := ParseProviderPart(given)
|
|
if err != nil {
|
|
panic(err.Error())
|
|
}
|
|
return result
|
|
}
|
|
|
|
// IsProviderPartNormalized compares a given string to the result of ParseProviderPart(string)
|
|
func IsProviderPartNormalized(str string) (bool, error) {
|
|
normalized, err := ParseProviderPart(str)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if str == normalized {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|