github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/internal/getproviders/registry_source.go (about)

     1  package getproviders
     2  
     3  import (
     4  	"fmt"
     5  
     6  	svchost "github.com/hashicorp/terraform-svchost"
     7  	disco "github.com/hashicorp/terraform-svchost/disco"
     8  
     9  	"github.com/hashicorp/terraform/addrs"
    10  )
    11  
    12  // RegistrySource is a Source that knows how to find and install providers from
    13  // their originating provider registries.
    14  type RegistrySource struct {
    15  	services *disco.Disco
    16  }
    17  
    18  var _ Source = (*RegistrySource)(nil)
    19  
    20  // NewRegistrySource creates and returns a new source that will install
    21  // providers from their originating provider registries.
    22  func NewRegistrySource(services *disco.Disco) *RegistrySource {
    23  	return &RegistrySource{
    24  		services: services,
    25  	}
    26  }
    27  
    28  // AvailableVersions returns all of the versions available for the provider
    29  // with the given address, or an error if that result cannot be determined.
    30  //
    31  // If the request fails, the returned error might be an value of
    32  // ErrHostNoProviders, ErrHostUnreachable, ErrUnauthenticated,
    33  // ErrProviderNotKnown, or ErrQueryFailed. Callers must be defensive and
    34  // expect errors of other types too, to allow for future expansion.
    35  func (s *RegistrySource) AvailableVersions(provider addrs.Provider) (VersionList, error) {
    36  	client, err := s.registryClient(provider.Hostname)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  
    41  	versionStrs, err := client.ProviderVersions(provider)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	if len(versionStrs) == 0 {
    47  		return nil, nil
    48  	}
    49  
    50  	ret := make(VersionList, len(versionStrs))
    51  	for i, str := range versionStrs {
    52  		v, err := ParseVersion(str)
    53  		if err != nil {
    54  			return nil, ErrQueryFailed{
    55  				Provider: provider,
    56  				Wrapped:  fmt.Errorf("registry response includes invalid version string %q: %s", str, err),
    57  			}
    58  		}
    59  		ret[i] = v
    60  	}
    61  	ret.Sort() // lowest precedence first, preserving order when equal precedence
    62  	return ret, nil
    63  }
    64  
    65  // PackageMeta returns metadata about the location and capabilities of
    66  // a distribution package for a particular provider at a particular version
    67  // targeting a particular platform.
    68  //
    69  // Callers of PackageMeta should first call AvailableVersions and pass
    70  // one of the resulting versions to this function. This function cannot
    71  // distinguish between a version that is not available and an unsupported
    72  // target platform, so if it encounters either case it will return an error
    73  // suggesting that the target platform isn't supported under the assumption
    74  // that the caller already checked that the version is available at all.
    75  //
    76  // To find a package suitable for the platform where the provider installation
    77  // process is running, set the "target" argument to
    78  // getproviders.CurrentPlatform.
    79  //
    80  // If the request fails, the returned error might be an value of
    81  // ErrHostNoProviders, ErrHostUnreachable, ErrUnauthenticated,
    82  // ErrPlatformNotSupported, or ErrQueryFailed. Callers must be defensive and
    83  // expect errors of other types too, to allow for future expansion.
    84  func (s *RegistrySource) PackageMeta(provider addrs.Provider, version Version, target Platform) (PackageMeta, error) {
    85  	client, err := s.registryClient(provider.Hostname)
    86  	if err != nil {
    87  		return PackageMeta{}, err
    88  	}
    89  
    90  	return client.PackageMeta(provider, version, target)
    91  }
    92  
    93  // LookupLegacyProviderNamespace is a special method available only on
    94  // RegistrySource which can deal with legacy provider addresses that contain
    95  // only a type and leave the namespace implied.
    96  //
    97  // It asks the registry at the given hostname to provide a default namespace
    98  // for the given provider type, which can be combined with the given hostname
    99  // and type name to produce a fully-qualified provider address.
   100  //
   101  // Not all unqualified type names can be resolved to a default namespace. If
   102  // the request fails, this method returns an error describing the failure.
   103  //
   104  // This method exists only to allow compatibility with unqualified names
   105  // in older configurations. New configurations should be written so as not to
   106  // depend on it, and this fallback mechanism will likely be removed altogether
   107  // in a future Terraform version.
   108  func (s *RegistrySource) LookupLegacyProviderNamespace(hostname svchost.Hostname, typeName string) (string, error) {
   109  	client, err := s.registryClient(hostname)
   110  	if err != nil {
   111  		return "", err
   112  	}
   113  	return client.LegacyProviderDefaultNamespace(typeName)
   114  }
   115  
   116  func (s *RegistrySource) registryClient(hostname svchost.Hostname) (*registryClient, error) {
   117  	host, err := s.services.Discover(hostname)
   118  	if err != nil {
   119  		return nil, ErrHostUnreachable{
   120  			Hostname: hostname,
   121  			Wrapped:  err,
   122  		}
   123  	}
   124  
   125  	url, err := host.ServiceURL("providers.v1")
   126  	switch err := err.(type) {
   127  	case nil:
   128  		// okay! We'll fall through and return below.
   129  	case *disco.ErrServiceNotProvided:
   130  		return nil, ErrHostNoProviders{
   131  			Hostname: hostname,
   132  		}
   133  	case *disco.ErrVersionNotSupported:
   134  		return nil, ErrHostNoProviders{
   135  			Hostname:        hostname,
   136  			HasOtherVersion: true,
   137  		}
   138  	default:
   139  		return nil, ErrHostUnreachable{
   140  			Hostname: hostname,
   141  			Wrapped:  err,
   142  		}
   143  	}
   144  
   145  	// Check if we have credentials configured for this hostname.
   146  	creds, err := s.services.CredentialsForHost(hostname)
   147  	if err != nil {
   148  		// This indicates that a credentials helper failed, which means we
   149  		// can't do anything better than just pass through the helper's
   150  		// own error message.
   151  		return nil, fmt.Errorf("failed to retrieve credentials for %s: %s", hostname, err)
   152  	}
   153  
   154  	return newRegistryClient(url, creds), nil
   155  }