-
Notifications
You must be signed in to change notification settings - Fork 9.4k
/
registry_source.go
150 lines (133 loc) · 5.28 KB
/
registry_source.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package getproviders
import (
"context"
"fmt"
svchost "github.com/hashicorp/terraform-svchost"
disco "github.com/hashicorp/terraform-svchost/disco"
"github.com/hashicorp/terraform/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())
}