mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
Previously we were using a third-party library, but that doesn't have any
support for passing context.Context through its API and so isn't suitable
for our goals of adding OpenTelemetry tracing for all outgoing network
requests.
We now have our own fork that is updated to use context.Context. It also
has a slightly reduced scope no longer including various details that
are tightly-coupled to our cliconfig mechanism and so better placed in the
main OpenTofu codebase so we can evolve it in future without making
lockstep library releases.
The "registry-address" library also uses svchost and uses some of its types
in its public API, so this also incorporates v2 of that library that is
updated to use our own svchost module.
Unfortunately this commit is a mix of mechanical updates to the new
libraries and some new code dealing with the functionality that is removed
in our fork of svchost. The new code is primarily in the "svcauthconfig"
package, which is similar in purpose "ociauthconfig" but for OpenTofu's
own auth mechanism instead of the OCI Distribution protocol's auth
mechanism.
This includes some additional plumbing of context.Context where it was
possible to do so without broad changes to files that would not otherwise
have been included in this commit, but there are a few leftover spots that
are context.TODO() which we'll address separately in later commits.
This removes the temporary workaround from d079da6e9e, since we are now
able to plumb the OpenTelemetry span tree all the way to the service
discovery requests.
Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
220 lines
8.7 KiB
Go
220 lines
8.7 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package addrs
|
|
|
|
import (
|
|
"github.com/hashicorp/hcl/v2"
|
|
regaddr "github.com/opentofu/registry-address/v2"
|
|
"github.com/opentofu/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 = regaddr.Provider
|
|
|
|
// DefaultProviderRegistryHost is the hostname used for provider addresses that do
|
|
// not have an explicit hostname.
|
|
const DefaultProviderRegistryHost = regaddr.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.
|
|
//
|
|
// Since we currently only have one built-in provider and it was inherited
|
|
// from OpenTofu's predecessor, we currently exclusively use the "transitional"
|
|
// builtin provider host that matches what the predecessor used, thereby
|
|
// helping with cross-compatibility. If we introduce any OpenTofu-specific
|
|
// built-in providers in future then we should consider using
|
|
// [regaddr.BuiltInProviderHost] for those ones instead, since that one
|
|
// uses a hostname that belongs to the OpenTofu project.
|
|
const BuiltInProviderHost = regaddr.TransitionalBuiltInProviderHost
|
|
|
|
// 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 = regaddr.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 = regaddr.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 regaddr.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 regaddr.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 regaddr.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) (regaddr.Provider, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
ret, err := regaddr.ParseProviderSource(str)
|
|
if pe, ok := err.(*regaddr.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 regaddr.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
|
|
}
|