151 lines
5.3 KiB
Go
151 lines
5.3 KiB
Go
package getproviders
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
svchost "github.com/hashicorp/terraform-svchost"
|
|
disco "github.com/hashicorp/terraform-svchost/disco"
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
)
|
|
|
|
// RegistrySource is a Source that knows how to find and install providers from
|
|
// their originating provider registries.
|
|
type RegistrySource struct {
|
|
services *disco.Disco
|
|
}
|
|
|
|
var _ Source = (*RegistrySource)(nil)
|
|
|
|
// NewRegistrySource creates and returns a new source that will install
|
|
// providers from their originating provider registries.
|
|
func NewRegistrySource(services *disco.Disco) *RegistrySource {
|
|
return &RegistrySource{
|
|
services: services,
|
|
}
|
|
}
|
|
|
|
// AvailableVersions returns all of the versions available for the provider
|
|
// with the given address, or an error if that result cannot be determined.
|
|
//
|
|
// If the request fails, the returned error might be an value of
|
|
// ErrHostNoProviders, ErrHostUnreachable, ErrUnauthenticated,
|
|
// ErrProviderNotKnown, or ErrQueryFailed. Callers must be defensive and
|
|
// expect errors of other types too, to allow for future expansion.
|
|
func (s *RegistrySource) AvailableVersions(ctx context.Context, provider addrs.Provider) (VersionList, Warnings, error) {
|
|
client, err := s.registryClient(provider.Hostname)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
versionsResponse, warnings, err := client.ProviderVersions(ctx, provider)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if len(versionsResponse) == 0 {
|
|
return nil, warnings, nil
|
|
}
|
|
|
|
// We ignore protocols here because our goal is to find out which versions
|
|
// are available _at all_. Which ones are compatible with the current
|
|
// Terraform becomes relevant only once we've selected one, at which point
|
|
// we'll return an error if the selected one is incompatible.
|
|
//
|
|
// We intentionally produce an error on incompatibility, rather than
|
|
// silently ignoring an incompatible version, in order to give the user
|
|
// explicit feedback about why their selection wasn't valid and allow them
|
|
// to decide whether to fix that by changing the selection or by some other
|
|
// action such as upgrading Terraform, using a different OS to run
|
|
// Terraform, etc. Changes that affect compatibility are considered breaking
|
|
// changes from a provider API standpoint, so provider teams should change
|
|
// compatibility only in new major versions.
|
|
ret := make(VersionList, 0, len(versionsResponse))
|
|
for str := range versionsResponse {
|
|
v, err := ParseVersion(str)
|
|
if err != nil {
|
|
return nil, nil, ErrQueryFailed{
|
|
Provider: provider,
|
|
Wrapped: fmt.Errorf("registry response includes invalid version string %q: %s", str, err),
|
|
}
|
|
}
|
|
ret = append(ret, v)
|
|
}
|
|
ret.Sort() // lowest precedence first, preserving order when equal precedence
|
|
return ret, warnings, nil
|
|
}
|
|
|
|
// PackageMeta returns metadata about the location and capabilities of
|
|
// a distribution package for a particular provider at a particular version
|
|
// targeting a particular platform.
|
|
//
|
|
// Callers of PackageMeta should first call AvailableVersions and pass
|
|
// one of the resulting versions to this function. This function cannot
|
|
// distinguish between a version that is not available and an unsupported
|
|
// target platform, so if it encounters either case it will return an error
|
|
// suggesting that the target platform isn't supported under the assumption
|
|
// that the caller already checked that the version is available at all.
|
|
//
|
|
// To find a package suitable for the platform where the provider installation
|
|
// process is running, set the "target" argument to
|
|
// getproviders.CurrentPlatform.
|
|
//
|
|
// If the request fails, the returned error might be an value of
|
|
// ErrHostNoProviders, ErrHostUnreachable, ErrUnauthenticated,
|
|
// ErrPlatformNotSupported, or ErrQueryFailed. Callers must be defensive and
|
|
// expect errors of other types too, to allow for future expansion.
|
|
func (s *RegistrySource) PackageMeta(ctx context.Context, provider addrs.Provider, version Version, target Platform) (PackageMeta, error) {
|
|
client, err := s.registryClient(provider.Hostname)
|
|
if err != nil {
|
|
return PackageMeta{}, err
|
|
}
|
|
|
|
return client.PackageMeta(ctx, provider, version, target)
|
|
}
|
|
|
|
func (s *RegistrySource) registryClient(hostname svchost.Hostname) (*registryClient, error) {
|
|
host, err := s.services.Discover(hostname)
|
|
if err != nil {
|
|
return nil, ErrHostUnreachable{
|
|
Hostname: hostname,
|
|
Wrapped: err,
|
|
}
|
|
}
|
|
|
|
url, err := host.ServiceURL("providers.v1")
|
|
switch err := err.(type) {
|
|
case nil:
|
|
// okay! We'll fall through and return below.
|
|
case *disco.ErrServiceNotProvided:
|
|
return nil, ErrHostNoProviders{
|
|
Hostname: hostname,
|
|
}
|
|
case *disco.ErrVersionNotSupported:
|
|
return nil, ErrHostNoProviders{
|
|
Hostname: hostname,
|
|
HasOtherVersion: true,
|
|
}
|
|
default:
|
|
return nil, ErrHostUnreachable{
|
|
Hostname: hostname,
|
|
Wrapped: err,
|
|
}
|
|
}
|
|
|
|
// Check if we have credentials configured for this hostname.
|
|
creds, err := s.services.CredentialsForHost(hostname)
|
|
if err != nil {
|
|
// This indicates that a credentials helper failed, which means we
|
|
// can't do anything better than just pass through the helper's
|
|
// own error message.
|
|
return nil, fmt.Errorf("failed to retrieve credentials for %s: %s", hostname, err)
|
|
}
|
|
|
|
return newRegistryClient(url, creds), nil
|
|
}
|
|
|
|
func (s *RegistrySource) ForDisplay(provider addrs.Provider) string {
|
|
return fmt.Sprintf("registry %s", provider.Hostname.ForDisplay())
|
|
}
|