Files
opentf/internal/getproviders/errors.go
Martin Atkins d2bef1fd47 Adopt OpenTofu's own "svchost" module
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>
2025-06-12 09:37:59 -07:00

252 lines
8.4 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 getproviders
import (
"fmt"
"net/url"
"github.com/opentofu/svchost"
"github.com/opentofu/opentofu/internal/addrs"
)
// ErrHostNoProviders is an error type used to indicate that a hostname given
// in a provider address does not support the provider registry protocol.
type ErrHostNoProviders struct {
Hostname svchost.Hostname
// HasOtherVersion is set to true if the discovery process detected
// declarations of services named "providers" whose version numbers did not
// match any version supported by the current version of OpenTofu.
//
// If this is set, it's helpful to hint to the user in an error message
// that the provider host may be expecting an older or a newer version
// of OpenTofu, rather than that it isn't a provider registry host at all.
HasOtherVersion bool
}
func (err ErrHostNoProviders) Error() string {
switch {
case err.HasOtherVersion:
return fmt.Sprintf("host %s does not support the provider registry protocol required by this OpenTofu version, but may be compatible with a different OpenTofu version", err.Hostname.ForDisplay())
default:
return fmt.Sprintf("host %s does not offer a OpenTofu provider registry", err.Hostname.ForDisplay())
}
}
// ErrHostUnreachable is an error type used to indicate that a hostname
// given in a provider address did not resolve in DNS, did not respond to an
// HTTPS request for service discovery, or otherwise failed to correctly speak
// the service discovery protocol.
type ErrHostUnreachable struct {
Hostname svchost.Hostname
Wrapped error
}
func (err ErrHostUnreachable) Error() string {
return fmt.Sprintf("could not connect to %s: %s", err.Hostname.ForDisplay(), err.Wrapped.Error())
}
// Unwrap returns the underlying error that occurred when trying to reach the
// indicated host.
func (err ErrHostUnreachable) Unwrap() error {
return err.Wrapped
}
// ErrUnauthorized is an error type used to indicate that a hostname
// given in a provider address returned a "401 Unauthorized" or "403 Forbidden"
// error response when we tried to access it.
type ErrUnauthorized struct {
Hostname svchost.Hostname
// HaveCredentials is true when the request that failed included some
// credentials, and thus it seems that those credentials were invalid.
// Conversely, HaveCredentials is false if the request did not include
// credentials at all, in which case it seems that credentials must be
// provided.
HaveCredentials bool
}
func (err ErrUnauthorized) Error() string {
switch {
case err.HaveCredentials:
return fmt.Sprintf("host %s rejected the given authentication credentials", err.Hostname)
default:
return fmt.Sprintf("host %s requires authentication credentials", err.Hostname)
}
}
// ErrProviderNotFound is an error type used to indicate that requested provider
// was not found in the source(s) included in the Description field. This can be
// used to produce user-friendly error messages.
type ErrProviderNotFound struct {
Provider addrs.Provider
Sources []string
}
func (err ErrProviderNotFound) Error() string {
return fmt.Sprintf(
"provider %s was not found in any of the search locations",
err.Provider,
)
}
// ErrRegistryProviderNotKnown is an error type used to indicate that the hostname
// given in a provider address does appear to be a provider registry but that
// registry does not know about the given provider namespace or type.
//
// A caller serving requests from an end-user should recognize this error type
// and use it to produce user-friendly hints for common errors such as failing
// to specify an explicit source for a provider not in the default namespace
// (one not under registry.opentofu.org/hashicorp/). The default error message
// for this type is a direct description of the problem with no such hints,
// because we expect that the caller will have better context to decide what
// hints are appropriate, e.g. by looking at the configuration given by the
// user.
type ErrRegistryProviderNotKnown struct {
Provider addrs.Provider
}
func (err ErrRegistryProviderNotKnown) Error() string {
return fmt.Sprintf(
"provider registry %s does not have a provider named %s",
err.Provider.Hostname.ForDisplay(),
err.Provider,
)
}
// ErrPlatformNotSupported is an error type used to indicate that a particular
// version of a provider isn't available for a particular target platform.
//
// This is returned when DownloadLocation encounters a 404 Not Found response
// from the underlying registry, because it presumes that a caller will only
// ask for the DownloadLocation for a version it already found the existence
// of via AvailableVersions.
type ErrPlatformNotSupported struct {
Provider addrs.Provider
Version Version
Platform Platform
// MirrorURL, if non-nil, is the base URL of the mirror that serviced
// the request in place of the provider's origin registry. MirrorURL
// is nil for a direct query.
MirrorURL *url.URL
}
func (err ErrPlatformNotSupported) Error() string {
if err.MirrorURL != nil {
return fmt.Sprintf(
"provider mirror %s does not have a package of %s %s for %s",
err.MirrorURL.String(),
err.Provider,
err.Version,
err.Platform,
)
}
return fmt.Sprintf(
"provider %s %s is not available for %s",
err.Provider,
err.Version,
err.Platform,
)
}
// ErrProtocolNotSupported is an error type used to indicate that a particular
// version of a provider is not supported by the current version of OpenTofu.
//
// Specifically, this is returned when the version's plugin protocol is not supported.
//
// When available, the error will include a suggested version that can be displayed to
// the user. Otherwise it will return UnspecifiedVersion
type ErrProtocolNotSupported struct {
Provider addrs.Provider
Version Version
Suggestion Version
}
func (err ErrProtocolNotSupported) Error() string {
return fmt.Sprintf(
"provider %s %s is not supported by this version of OpenTofu",
err.Provider,
err.Version,
)
}
// ErrQueryFailed is an error type used to indicate that the hostname given
// in a provider address does appear to be a provider registry but that when
// we queried it for metadata for the given provider the server returned an
// unexpected error.
//
// This is used for any error responses other than "Not Found", which would
// indicate the absence of a provider and is thus reported using
// ErrProviderNotKnown instead.
type ErrQueryFailed struct {
Provider addrs.Provider
Wrapped error
// MirrorURL, if non-nil, is the base URL of the mirror that serviced
// the request in place of the provider's origin registry. MirrorURL
// is nil for a direct query.
MirrorURL *url.URL
}
func (err ErrQueryFailed) Error() string {
if err.MirrorURL != nil {
return fmt.Sprintf(
"failed to query provider mirror %s for %s: %s",
err.MirrorURL.String(),
err.Provider.String(),
err.Wrapped.Error(),
)
}
return fmt.Sprintf(
"could not query provider registry for %s: %s",
err.Provider.String(),
err.Wrapped.Error(),
)
}
// Unwrap returns the underlying error that occurred when trying to reach the
// indicated host.
func (err ErrQueryFailed) Unwrap() error {
return err.Wrapped
}
// ErrRequestCanceled is an error type used to indicate that an operation
// failed due to being cancelled via the given context.Context object.
//
// This error type doesn't include information about what was cancelled,
// because the expected treatment of this error type is to quickly abort and
// exit with minimal ceremony.
type ErrRequestCanceled struct {
}
func (err ErrRequestCanceled) Error() string {
return "request canceled"
}
// ErrIsNotExist returns true if and only if the given error is one of the
// errors from this package that represents an affirmative response that a
// requested object does not exist.
//
// This is as opposed to errors indicating that the source is unavailable
// or misconfigured in some way, where we therefore cannot say for certain
// whether the requested object exists.
//
// If a caller needs to take a special action based on something not existing,
// such as falling back on some other source, use this function rather than
// direct type assertions so that the set of possible "not exist" errors can
// grow in future.
func ErrIsNotExist(err error) bool {
switch err.(type) {
case ErrProviderNotFound, ErrRegistryProviderNotKnown, ErrPlatformNotSupported:
return true
default:
return false
}
}