| package getproviders |
| |
| import ( |
| "context" |
| "fmt" |
| |
| svchost "github.com/hashicorp/terraform-svchost" |
| disco "github.com/hashicorp/terraform-svchost/disco" |
| |
| "github.com/hashicorp/terraform/internal/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()) |
| } |