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 }