github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/getproviders/registry_source.go (about)

     1  package getproviders
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	svchost "github.com/hashicorp/terraform-svchost"
     8  	disco "github.com/hashicorp/terraform-svchost/disco"
     9  
    10  	"github.com/hashicorp/terraform/internal/addrs"
    11  )
    12  
    13  // RegistrySource is a Source that knows how to find and install providers from
    14  // their originating provider registries.
    15  type RegistrySource struct {
    16  	services *disco.Disco
    17  }
    18  
    19  var _ Source = (*RegistrySource)(nil)
    20  
    21  // NewRegistrySource creates and returns a new source that will install
    22  // providers from their originating provider registries.
    23  func NewRegistrySource(services *disco.Disco) *RegistrySource {
    24  	return &RegistrySource{
    25  		services: services,
    26  	}
    27  }
    28  
    29  // AvailableVersions returns all of the versions available for the provider
    30  // with the given address, or an error if that result cannot be determined.
    31  //
    32  // If the request fails, the returned error might be an value of
    33  // ErrHostNoProviders, ErrHostUnreachable, ErrUnauthenticated,
    34  // ErrProviderNotKnown, or ErrQueryFailed. Callers must be defensive and
    35  // expect errors of other types too, to allow for future expansion.
    36  func (s *RegistrySource) AvailableVersions(ctx context.Context, provider addrs.Provider) (VersionList, Warnings, error) {
    37  	client, err := s.registryClient(provider.Hostname)
    38  	if err != nil {
    39  		return nil, nil, err
    40  	}
    41  
    42  	versionsResponse, warnings, err := client.ProviderVersions(ctx, provider)
    43  	if err != nil {
    44  		return nil, nil, err
    45  	}
    46  
    47  	if len(versionsResponse) == 0 {
    48  		return nil, warnings, nil
    49  	}
    50  
    51  	// We ignore protocols here because our goal is to find out which versions
    52  	// are available _at all_. Which ones are compatible with the current
    53  	// Terraform becomes relevant only once we've selected one, at which point
    54  	// we'll return an error if the selected one is incompatible.
    55  	//
    56  	// We intentionally produce an error on incompatibility, rather than
    57  	// silently ignoring an incompatible version, in order to give the user
    58  	// explicit feedback about why their selection wasn't valid and allow them
    59  	// to decide whether to fix that by changing the selection or by some other
    60  	// action such as upgrading Terraform, using a different OS to run
    61  	// Terraform, etc. Changes that affect compatibility are considered breaking
    62  	// changes from a provider API standpoint, so provider teams should change
    63  	// compatibility only in new major versions.
    64  	ret := make(VersionList, 0, len(versionsResponse))
    65  	for str := range versionsResponse {
    66  		v, err := ParseVersion(str)
    67  		if err != nil {
    68  			return nil, nil, ErrQueryFailed{
    69  				Provider: provider,
    70  				Wrapped:  fmt.Errorf("registry response includes invalid version string %q: %s", str, err),
    71  			}
    72  		}
    73  		ret = append(ret, v)
    74  	}
    75  	ret.Sort() // lowest precedence first, preserving order when equal precedence
    76  	return ret, warnings, nil
    77  }
    78  
    79  // PackageMeta returns metadata about the location and capabilities of
    80  // a distribution package for a particular provider at a particular version
    81  // targeting a particular platform.
    82  //
    83  // Callers of PackageMeta should first call AvailableVersions and pass
    84  // one of the resulting versions to this function. This function cannot
    85  // distinguish between a version that is not available and an unsupported
    86  // target platform, so if it encounters either case it will return an error
    87  // suggesting that the target platform isn't supported under the assumption
    88  // that the caller already checked that the version is available at all.
    89  //
    90  // To find a package suitable for the platform where the provider installation
    91  // process is running, set the "target" argument to
    92  // getproviders.CurrentPlatform.
    93  //
    94  // If the request fails, the returned error might be an value of
    95  // ErrHostNoProviders, ErrHostUnreachable, ErrUnauthenticated,
    96  // ErrPlatformNotSupported, or ErrQueryFailed. Callers must be defensive and
    97  // expect errors of other types too, to allow for future expansion.
    98  func (s *RegistrySource) PackageMeta(ctx context.Context, provider addrs.Provider, version Version, target Platform) (PackageMeta, error) {
    99  	client, err := s.registryClient(provider.Hostname)
   100  	if err != nil {
   101  		return PackageMeta{}, err
   102  	}
   103  
   104  	return client.PackageMeta(ctx, provider, version, target)
   105  }
   106  
   107  func (s *RegistrySource) registryClient(hostname svchost.Hostname) (*registryClient, error) {
   108  	host, err := s.services.Discover(hostname)
   109  	if err != nil {
   110  		return nil, ErrHostUnreachable{
   111  			Hostname: hostname,
   112  			Wrapped:  err,
   113  		}
   114  	}
   115  
   116  	url, err := host.ServiceURL("providers.v1")
   117  	switch err := err.(type) {
   118  	case nil:
   119  		// okay! We'll fall through and return below.
   120  	case *disco.ErrServiceNotProvided:
   121  		return nil, ErrHostNoProviders{
   122  			Hostname: hostname,
   123  		}
   124  	case *disco.ErrVersionNotSupported:
   125  		return nil, ErrHostNoProviders{
   126  			Hostname:        hostname,
   127  			HasOtherVersion: true,
   128  		}
   129  	default:
   130  		return nil, ErrHostUnreachable{
   131  			Hostname: hostname,
   132  			Wrapped:  err,
   133  		}
   134  	}
   135  
   136  	// Check if we have credentials configured for this hostname.
   137  	creds, err := s.services.CredentialsForHost(hostname)
   138  	if err != nil {
   139  		// This indicates that a credentials helper failed, which means we
   140  		// can't do anything better than just pass through the helper's
   141  		// own error message.
   142  		return nil, fmt.Errorf("failed to retrieve credentials for %s: %s", hostname, err)
   143  	}
   144  
   145  	return newRegistryClient(url, creds), nil
   146  }
   147  
   148  func (s *RegistrySource) ForDisplay(provider addrs.Provider) string {
   149  	return fmt.Sprintf("registry %s", provider.Hostname.ForDisplay())
   150  }