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 }