diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/plugin/discovery')
9 files changed, 1172 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/error.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/error.go new file mode 100644 index 0000000..df855a7 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/error.go | |||
@@ -0,0 +1,30 @@ | |||
1 | package discovery | ||
2 | |||
3 | // Error is a type used to describe situations that the caller must handle | ||
4 | // since they indicate some form of user error. | ||
5 | // | ||
6 | // The functions and methods that return these specialized errors indicate so | ||
7 | // in their documentation. The Error type should not itself be used directly, | ||
8 | // but rather errors should be compared using the == operator with the | ||
9 | // error constants in this package. | ||
10 | // | ||
11 | // Values of this type are _not_ used when the error being reported is an | ||
12 | // operational error (server unavailable, etc) or indicative of a bug in | ||
13 | // this package or its caller. | ||
14 | type Error string | ||
15 | |||
16 | // ErrorNoSuitableVersion indicates that a suitable version (meeting given | ||
17 | // constraints) is not available. | ||
18 | const ErrorNoSuitableVersion = Error("no suitable version is available") | ||
19 | |||
20 | // ErrorNoVersionCompatible indicates that all of the available versions | ||
21 | // that otherwise met constraints are not compatible with the current | ||
22 | // version of Terraform. | ||
23 | const ErrorNoVersionCompatible = Error("no available version is compatible with this version of Terraform") | ||
24 | |||
25 | // ErrorNoSuchProvider indicates that no provider exists with a name given | ||
26 | const ErrorNoSuchProvider = Error("no provider exists with the given name") | ||
27 | |||
28 | func (err Error) Error() string { | ||
29 | return string(err) | ||
30 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/find.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/find.go new file mode 100644 index 0000000..f5bc4c1 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/find.go | |||
@@ -0,0 +1,168 @@ | |||
1 | package discovery | ||
2 | |||
3 | import ( | ||
4 | "io/ioutil" | ||
5 | "log" | ||
6 | "path/filepath" | ||
7 | "strings" | ||
8 | ) | ||
9 | |||
10 | // FindPlugins looks in the given directories for files whose filenames | ||
11 | // suggest that they are plugins of the given kind (e.g. "provider") and | ||
12 | // returns a PluginMetaSet representing the discovered potential-plugins. | ||
13 | // | ||
14 | // Currently this supports two different naming schemes. The current | ||
15 | // standard naming scheme is a subdirectory called $GOOS-$GOARCH containing | ||
16 | // files named terraform-$KIND-$NAME-V$VERSION. The legacy naming scheme is | ||
17 | // files directly in the given directory whose names are like | ||
18 | // terraform-$KIND-$NAME. | ||
19 | // | ||
20 | // Only one plugin will be returned for each unique plugin (name, version) | ||
21 | // pair, with preference given to files found in earlier directories. | ||
22 | // | ||
23 | // This is a convenience wrapper around FindPluginPaths and ResolvePluginsPaths. | ||
24 | func FindPlugins(kind string, dirs []string) PluginMetaSet { | ||
25 | return ResolvePluginPaths(FindPluginPaths(kind, dirs)) | ||
26 | } | ||
27 | |||
28 | // FindPluginPaths looks in the given directories for files whose filenames | ||
29 | // suggest that they are plugins of the given kind (e.g. "provider"). | ||
30 | // | ||
31 | // The return value is a list of absolute paths that appear to refer to | ||
32 | // plugins in the given directories, based only on what can be inferred | ||
33 | // from the naming scheme. The paths returned are ordered such that files | ||
34 | // in later dirs appear after files in earlier dirs in the given directory | ||
35 | // list. Within the same directory plugins are returned in a consistent but | ||
36 | // undefined order. | ||
37 | func FindPluginPaths(kind string, dirs []string) []string { | ||
38 | // This is just a thin wrapper around findPluginPaths so that we can | ||
39 | // use the latter in tests with a fake machineName so we can use our | ||
40 | // test fixtures. | ||
41 | return findPluginPaths(kind, dirs) | ||
42 | } | ||
43 | |||
44 | func findPluginPaths(kind string, dirs []string) []string { | ||
45 | prefix := "terraform-" + kind + "-" | ||
46 | |||
47 | ret := make([]string, 0, len(dirs)) | ||
48 | |||
49 | for _, dir := range dirs { | ||
50 | items, err := ioutil.ReadDir(dir) | ||
51 | if err != nil { | ||
52 | // Ignore missing dirs, non-dirs, etc | ||
53 | continue | ||
54 | } | ||
55 | |||
56 | log.Printf("[DEBUG] checking for %s in %q", kind, dir) | ||
57 | |||
58 | for _, item := range items { | ||
59 | fullName := item.Name() | ||
60 | |||
61 | if !strings.HasPrefix(fullName, prefix) { | ||
62 | log.Printf("[DEBUG] skipping %q, not a %s", fullName, kind) | ||
63 | continue | ||
64 | } | ||
65 | |||
66 | // New-style paths must have a version segment in filename | ||
67 | if strings.Contains(strings.ToLower(fullName), "_v") { | ||
68 | absPath, err := filepath.Abs(filepath.Join(dir, fullName)) | ||
69 | if err != nil { | ||
70 | log.Printf("[ERROR] plugin filepath error: %s", err) | ||
71 | continue | ||
72 | } | ||
73 | |||
74 | log.Printf("[DEBUG] found %s %q", kind, fullName) | ||
75 | ret = append(ret, filepath.Clean(absPath)) | ||
76 | continue | ||
77 | } | ||
78 | |||
79 | // Legacy style with files directly in the base directory | ||
80 | absPath, err := filepath.Abs(filepath.Join(dir, fullName)) | ||
81 | if err != nil { | ||
82 | log.Printf("[ERROR] plugin filepath error: %s", err) | ||
83 | continue | ||
84 | } | ||
85 | |||
86 | log.Printf("[WARNING] found legacy %s %q", kind, fullName) | ||
87 | |||
88 | ret = append(ret, filepath.Clean(absPath)) | ||
89 | } | ||
90 | } | ||
91 | |||
92 | return ret | ||
93 | } | ||
94 | |||
95 | // ResolvePluginPaths takes a list of paths to plugin executables (as returned | ||
96 | // by e.g. FindPluginPaths) and produces a PluginMetaSet describing the | ||
97 | // referenced plugins. | ||
98 | // | ||
99 | // If the same combination of plugin name and version appears multiple times, | ||
100 | // the earlier reference will be preferred. Several different versions of | ||
101 | // the same plugin name may be returned, in which case the methods of | ||
102 | // PluginMetaSet can be used to filter down. | ||
103 | func ResolvePluginPaths(paths []string) PluginMetaSet { | ||
104 | s := make(PluginMetaSet) | ||
105 | |||
106 | type nameVersion struct { | ||
107 | Name string | ||
108 | Version string | ||
109 | } | ||
110 | found := make(map[nameVersion]struct{}) | ||
111 | |||
112 | for _, path := range paths { | ||
113 | baseName := strings.ToLower(filepath.Base(path)) | ||
114 | if !strings.HasPrefix(baseName, "terraform-") { | ||
115 | // Should never happen with reasonable input | ||
116 | continue | ||
117 | } | ||
118 | |||
119 | baseName = baseName[10:] | ||
120 | firstDash := strings.Index(baseName, "-") | ||
121 | if firstDash == -1 { | ||
122 | // Should never happen with reasonable input | ||
123 | continue | ||
124 | } | ||
125 | |||
126 | baseName = baseName[firstDash+1:] | ||
127 | if baseName == "" { | ||
128 | // Should never happen with reasonable input | ||
129 | continue | ||
130 | } | ||
131 | |||
132 | // Trim the .exe suffix used on Windows before we start wrangling | ||
133 | // the remainder of the path. | ||
134 | if strings.HasSuffix(baseName, ".exe") { | ||
135 | baseName = baseName[:len(baseName)-4] | ||
136 | } | ||
137 | |||
138 | parts := strings.SplitN(baseName, "_v", 2) | ||
139 | name := parts[0] | ||
140 | version := VersionZero | ||
141 | if len(parts) == 2 { | ||
142 | version = parts[1] | ||
143 | } | ||
144 | |||
145 | // Auto-installed plugins contain an extra name portion representing | ||
146 | // the expected plugin version, which we must trim off. | ||
147 | if underX := strings.Index(version, "_x"); underX != -1 { | ||
148 | version = version[:underX] | ||
149 | } | ||
150 | |||
151 | if _, ok := found[nameVersion{name, version}]; ok { | ||
152 | // Skip duplicate versions of the same plugin | ||
153 | // (We do this during this step because after this we will be | ||
154 | // dealing with sets and thus lose our ordering with which to | ||
155 | // decide preference.) | ||
156 | continue | ||
157 | } | ||
158 | |||
159 | s.Add(PluginMeta{ | ||
160 | Name: name, | ||
161 | Version: VersionStr(version), | ||
162 | Path: path, | ||
163 | }) | ||
164 | found[nameVersion{name, version}] = struct{}{} | ||
165 | } | ||
166 | |||
167 | return s | ||
168 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/get.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/get.go new file mode 100644 index 0000000..241b5cb --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/get.go | |||
@@ -0,0 +1,424 @@ | |||
1 | package discovery | ||
2 | |||
3 | import ( | ||
4 | "errors" | ||
5 | "fmt" | ||
6 | "io/ioutil" | ||
7 | "log" | ||
8 | "net/http" | ||
9 | "os" | ||
10 | "runtime" | ||
11 | "strconv" | ||
12 | "strings" | ||
13 | |||
14 | "golang.org/x/net/html" | ||
15 | |||
16 | cleanhttp "github.com/hashicorp/go-cleanhttp" | ||
17 | getter "github.com/hashicorp/go-getter" | ||
18 | multierror "github.com/hashicorp/go-multierror" | ||
19 | ) | ||
20 | |||
21 | // Releases are located by parsing the html listing from releases.hashicorp.com. | ||
22 | // | ||
23 | // The URL for releases follows the pattern: | ||
24 | // https://releases.hashicorp.com/terraform-provider-name/<x.y.z>/terraform-provider-name_<x.y.z>_<os>_<arch>.<ext> | ||
25 | // | ||
26 | // The plugin protocol version will be saved with the release and returned in | ||
27 | // the header X-TERRAFORM_PROTOCOL_VERSION. | ||
28 | |||
29 | const protocolVersionHeader = "x-terraform-protocol-version" | ||
30 | |||
31 | var releaseHost = "https://releases.hashicorp.com" | ||
32 | |||
33 | var httpClient = cleanhttp.DefaultClient() | ||
34 | |||
35 | // An Installer maintains a local cache of plugins by downloading plugins | ||
36 | // from an online repository. | ||
37 | type Installer interface { | ||
38 | Get(name string, req Constraints) (PluginMeta, error) | ||
39 | PurgeUnused(used map[string]PluginMeta) (removed PluginMetaSet, err error) | ||
40 | } | ||
41 | |||
42 | // ProviderInstaller is an Installer implementation that knows how to | ||
43 | // download Terraform providers from the official HashiCorp releases service | ||
44 | // into a local directory. The files downloaded are compliant with the | ||
45 | // naming scheme expected by FindPlugins, so the target directory of a | ||
46 | // provider installer can be used as one of several plugin discovery sources. | ||
47 | type ProviderInstaller struct { | ||
48 | Dir string | ||
49 | |||
50 | PluginProtocolVersion uint | ||
51 | |||
52 | // OS and Arch specify the OS and architecture that should be used when | ||
53 | // installing plugins. These use the same labels as the runtime.GOOS and | ||
54 | // runtime.GOARCH variables respectively, and indeed the values of these | ||
55 | // are used as defaults if either of these is the empty string. | ||
56 | OS string | ||
57 | Arch string | ||
58 | |||
59 | // Skip checksum and signature verification | ||
60 | SkipVerify bool | ||
61 | } | ||
62 | |||
63 | // Get is part of an implementation of type Installer, and attempts to download | ||
64 | // and install a Terraform provider matching the given constraints. | ||
65 | // | ||
66 | // This method may return one of a number of sentinel errors from this | ||
67 | // package to indicate issues that are likely to be resolvable via user action: | ||
68 | // | ||
69 | // ErrorNoSuchProvider: no provider with the given name exists in the repository. | ||
70 | // ErrorNoSuitableVersion: the provider exists but no available version matches constraints. | ||
71 | // ErrorNoVersionCompatible: a plugin was found within the constraints but it is | ||
72 | // incompatible with the current Terraform version. | ||
73 | // | ||
74 | // These errors should be recognized and handled as special cases by the caller | ||
75 | // to present a suitable user-oriented error message. | ||
76 | // | ||
77 | // All other errors indicate an internal problem that is likely _not_ solvable | ||
78 | // through user action, or at least not within Terraform's scope. Error messages | ||
79 | // are produced under the assumption that if presented to the user they will | ||
80 | // be presented alongside context about what is being installed, and thus the | ||
81 | // error messages do not redundantly include such information. | ||
82 | func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, error) { | ||
83 | versions, err := i.listProviderVersions(provider) | ||
84 | // TODO: return multiple errors | ||
85 | if err != nil { | ||
86 | return PluginMeta{}, err | ||
87 | } | ||
88 | |||
89 | if len(versions) == 0 { | ||
90 | return PluginMeta{}, ErrorNoSuitableVersion | ||
91 | } | ||
92 | |||
93 | versions = allowedVersions(versions, req) | ||
94 | if len(versions) == 0 { | ||
95 | return PluginMeta{}, ErrorNoSuitableVersion | ||
96 | } | ||
97 | |||
98 | // sort them newest to oldest | ||
99 | Versions(versions).Sort() | ||
100 | |||
101 | // take the first matching plugin we find | ||
102 | for _, v := range versions { | ||
103 | url := i.providerURL(provider, v.String()) | ||
104 | |||
105 | if !i.SkipVerify { | ||
106 | sha256, err := i.getProviderChecksum(provider, v.String()) | ||
107 | if err != nil { | ||
108 | return PluginMeta{}, err | ||
109 | } | ||
110 | |||
111 | // add the checksum parameter for go-getter to verify the download for us. | ||
112 | if sha256 != "" { | ||
113 | url = url + "?checksum=sha256:" + sha256 | ||
114 | } | ||
115 | } | ||
116 | |||
117 | log.Printf("[DEBUG] fetching provider info for %s version %s", provider, v) | ||
118 | if checkPlugin(url, i.PluginProtocolVersion) { | ||
119 | log.Printf("[DEBUG] getting provider %q version %q at %s", provider, v, url) | ||
120 | err := getter.Get(i.Dir, url) | ||
121 | if err != nil { | ||
122 | return PluginMeta{}, err | ||
123 | } | ||
124 | |||
125 | // Find what we just installed | ||
126 | // (This is weird, because go-getter doesn't directly return | ||
127 | // information about what was extracted, and we just extracted | ||
128 | // the archive directly into a shared dir here.) | ||
129 | log.Printf("[DEBUG] looking for the %s %s plugin we just installed", provider, v) | ||
130 | metas := FindPlugins("provider", []string{i.Dir}) | ||
131 | log.Printf("[DEBUG] all plugins found %#v", metas) | ||
132 | metas, _ = metas.ValidateVersions() | ||
133 | metas = metas.WithName(provider).WithVersion(v) | ||
134 | log.Printf("[DEBUG] filtered plugins %#v", metas) | ||
135 | if metas.Count() == 0 { | ||
136 | // This should never happen. Suggests that the release archive | ||
137 | // contains an executable file whose name doesn't match the | ||
138 | // expected convention. | ||
139 | return PluginMeta{}, fmt.Errorf( | ||
140 | "failed to find installed plugin version %s; this is a bug in Terraform and should be reported", | ||
141 | v, | ||
142 | ) | ||
143 | } | ||
144 | |||
145 | if metas.Count() > 1 { | ||
146 | // This should also never happen, and suggests that a | ||
147 | // particular version was re-released with a different | ||
148 | // executable filename. We consider releases as immutable, so | ||
149 | // this is an error. | ||
150 | return PluginMeta{}, fmt.Errorf( | ||
151 | "multiple plugins installed for version %s; this is a bug in Terraform and should be reported", | ||
152 | v, | ||
153 | ) | ||
154 | } | ||
155 | |||
156 | // By now we know we have exactly one meta, and so "Newest" will | ||
157 | // return that one. | ||
158 | return metas.Newest(), nil | ||
159 | } | ||
160 | |||
161 | log.Printf("[INFO] incompatible ProtocolVersion for %s version %s", provider, v) | ||
162 | } | ||
163 | |||
164 | return PluginMeta{}, ErrorNoVersionCompatible | ||
165 | } | ||
166 | |||
167 | func (i *ProviderInstaller) PurgeUnused(used map[string]PluginMeta) (PluginMetaSet, error) { | ||
168 | purge := make(PluginMetaSet) | ||
169 | |||
170 | present := FindPlugins("provider", []string{i.Dir}) | ||
171 | for meta := range present { | ||
172 | chosen, ok := used[meta.Name] | ||
173 | if !ok { | ||
174 | purge.Add(meta) | ||
175 | } | ||
176 | if chosen.Path != meta.Path { | ||
177 | purge.Add(meta) | ||
178 | } | ||
179 | } | ||
180 | |||
181 | removed := make(PluginMetaSet) | ||
182 | var errs error | ||
183 | for meta := range purge { | ||
184 | path := meta.Path | ||
185 | err := os.Remove(path) | ||
186 | if err != nil { | ||
187 | errs = multierror.Append(errs, fmt.Errorf( | ||
188 | "failed to remove unused provider plugin %s: %s", | ||
189 | path, err, | ||
190 | )) | ||
191 | } else { | ||
192 | removed.Add(meta) | ||
193 | } | ||
194 | } | ||
195 | |||
196 | return removed, errs | ||
197 | } | ||
198 | |||
199 | // Plugins are referred to by the short name, but all URLs and files will use | ||
200 | // the full name prefixed with terraform-<plugin_type>- | ||
201 | func (i *ProviderInstaller) providerName(name string) string { | ||
202 | return "terraform-provider-" + name | ||
203 | } | ||
204 | |||
205 | func (i *ProviderInstaller) providerFileName(name, version string) string { | ||
206 | os := i.OS | ||
207 | arch := i.Arch | ||
208 | if os == "" { | ||
209 | os = runtime.GOOS | ||
210 | } | ||
211 | if arch == "" { | ||
212 | arch = runtime.GOARCH | ||
213 | } | ||
214 | return fmt.Sprintf("%s_%s_%s_%s.zip", i.providerName(name), version, os, arch) | ||
215 | } | ||
216 | |||
217 | // providerVersionsURL returns the path to the released versions directory for the provider: | ||
218 | // https://releases.hashicorp.com/terraform-provider-name/ | ||
219 | func (i *ProviderInstaller) providerVersionsURL(name string) string { | ||
220 | return releaseHost + "/" + i.providerName(name) + "/" | ||
221 | } | ||
222 | |||
223 | // providerURL returns the full path to the provider file, using the current OS | ||
224 | // and ARCH: | ||
225 | // .../terraform-provider-name_<x.y.z>/terraform-provider-name_<x.y.z>_<os>_<arch>.<ext> | ||
226 | func (i *ProviderInstaller) providerURL(name, version string) string { | ||
227 | return fmt.Sprintf("%s%s/%s", i.providerVersionsURL(name), version, i.providerFileName(name, version)) | ||
228 | } | ||
229 | |||
230 | func (i *ProviderInstaller) providerChecksumURL(name, version string) string { | ||
231 | fileName := fmt.Sprintf("%s_%s_SHA256SUMS", i.providerName(name), version) | ||
232 | u := fmt.Sprintf("%s%s/%s", i.providerVersionsURL(name), version, fileName) | ||
233 | return u | ||
234 | } | ||
235 | |||
236 | func (i *ProviderInstaller) getProviderChecksum(name, version string) (string, error) { | ||
237 | checksums, err := getPluginSHA256SUMs(i.providerChecksumURL(name, version)) | ||
238 | if err != nil { | ||
239 | return "", err | ||
240 | } | ||
241 | |||
242 | return checksumForFile(checksums, i.providerFileName(name, version)), nil | ||
243 | } | ||
244 | |||
245 | // Return the plugin version by making a HEAD request to the provided url. | ||
246 | // If the header is not present, we assume the latest version will be | ||
247 | // compatible, and leave the check for discovery or execution. | ||
248 | func checkPlugin(url string, pluginProtocolVersion uint) bool { | ||
249 | resp, err := httpClient.Head(url) | ||
250 | if err != nil { | ||
251 | log.Printf("[ERROR] error fetching plugin headers: %s", err) | ||
252 | return false | ||
253 | } | ||
254 | |||
255 | if resp.StatusCode != http.StatusOK { | ||
256 | log.Println("[ERROR] non-200 status fetching plugin headers:", resp.Status) | ||
257 | return false | ||
258 | } | ||
259 | |||
260 | proto := resp.Header.Get(protocolVersionHeader) | ||
261 | if proto == "" { | ||
262 | // The header isn't present, but we don't make this error fatal since | ||
263 | // the latest version will probably work. | ||
264 | log.Printf("[WARNING] missing %s from: %s", protocolVersionHeader, url) | ||
265 | return true | ||
266 | } | ||
267 | |||
268 | protoVersion, err := strconv.Atoi(proto) | ||
269 | if err != nil { | ||
270 | log.Printf("[ERROR] invalid ProtocolVersion: %s", proto) | ||
271 | return false | ||
272 | } | ||
273 | |||
274 | return protoVersion == int(pluginProtocolVersion) | ||
275 | } | ||
276 | |||
277 | // list the version available for the named plugin | ||
278 | func (i *ProviderInstaller) listProviderVersions(name string) ([]Version, error) { | ||
279 | versions, err := listPluginVersions(i.providerVersionsURL(name)) | ||
280 | if err != nil { | ||
281 | // listPluginVersions returns a verbose error message indicating | ||
282 | // what was being accessed and what failed | ||
283 | return nil, err | ||
284 | } | ||
285 | return versions, nil | ||
286 | } | ||
287 | |||
288 | var errVersionNotFound = errors.New("version not found") | ||
289 | |||
290 | // take the list of available versions for a plugin, and filter out those that | ||
291 | // don't fit the constraints. | ||
292 | func allowedVersions(available []Version, required Constraints) []Version { | ||
293 | var allowed []Version | ||
294 | |||
295 | for _, v := range available { | ||
296 | if required.Allows(v) { | ||
297 | allowed = append(allowed, v) | ||
298 | } | ||
299 | } | ||
300 | |||
301 | return allowed | ||
302 | } | ||
303 | |||
304 | // return a list of the plugin versions at the given URL | ||
305 | func listPluginVersions(url string) ([]Version, error) { | ||
306 | resp, err := httpClient.Get(url) | ||
307 | if err != nil { | ||
308 | // http library produces a verbose error message that includes the | ||
309 | // URL being accessed, etc. | ||
310 | return nil, err | ||
311 | } | ||
312 | defer resp.Body.Close() | ||
313 | |||
314 | if resp.StatusCode != http.StatusOK { | ||
315 | body, _ := ioutil.ReadAll(resp.Body) | ||
316 | log.Printf("[ERROR] failed to fetch plugin versions from %s\n%s\n%s", url, resp.Status, body) | ||
317 | |||
318 | switch resp.StatusCode { | ||
319 | case http.StatusNotFound, http.StatusForbidden: | ||
320 | // These are treated as indicative of the given name not being | ||
321 | // a valid provider name at all. | ||
322 | return nil, ErrorNoSuchProvider | ||
323 | |||
324 | default: | ||
325 | // All other errors are assumed to be operational problems. | ||
326 | return nil, fmt.Errorf("error accessing %s: %s", url, resp.Status) | ||
327 | } | ||
328 | |||
329 | } | ||
330 | |||
331 | body, err := html.Parse(resp.Body) | ||
332 | if err != nil { | ||
333 | log.Fatal(err) | ||
334 | } | ||
335 | |||
336 | names := []string{} | ||
337 | |||
338 | // all we need to do is list links on the directory listing page that look like plugins | ||
339 | var f func(*html.Node) | ||
340 | f = func(n *html.Node) { | ||
341 | if n.Type == html.ElementNode && n.Data == "a" { | ||
342 | c := n.FirstChild | ||
343 | if c != nil && c.Type == html.TextNode && strings.HasPrefix(c.Data, "terraform-") { | ||
344 | names = append(names, c.Data) | ||
345 | return | ||
346 | } | ||
347 | } | ||
348 | for c := n.FirstChild; c != nil; c = c.NextSibling { | ||
349 | f(c) | ||
350 | } | ||
351 | } | ||
352 | f(body) | ||
353 | |||
354 | return versionsFromNames(names), nil | ||
355 | } | ||
356 | |||
357 | // parse the list of directory names into a sorted list of available versions | ||
358 | func versionsFromNames(names []string) []Version { | ||
359 | var versions []Version | ||
360 | for _, name := range names { | ||
361 | parts := strings.SplitN(name, "_", 2) | ||
362 | if len(parts) == 2 && parts[1] != "" { | ||
363 | v, err := VersionStr(parts[1]).Parse() | ||
364 | if err != nil { | ||
365 | // filter invalid versions scraped from the page | ||
366 | log.Printf("[WARN] invalid version found for %q: %s", name, err) | ||
367 | continue | ||
368 | } | ||
369 | |||
370 | versions = append(versions, v) | ||
371 | } | ||
372 | } | ||
373 | |||
374 | return versions | ||
375 | } | ||
376 | |||
377 | func checksumForFile(sums []byte, name string) string { | ||
378 | for _, line := range strings.Split(string(sums), "\n") { | ||
379 | parts := strings.Fields(line) | ||
380 | if len(parts) > 1 && parts[1] == name { | ||
381 | return parts[0] | ||
382 | } | ||
383 | } | ||
384 | return "" | ||
385 | } | ||
386 | |||
387 | // fetch the SHA256SUMS file provided, and verify its signature. | ||
388 | func getPluginSHA256SUMs(sumsURL string) ([]byte, error) { | ||
389 | sigURL := sumsURL + ".sig" | ||
390 | |||
391 | sums, err := getFile(sumsURL) | ||
392 | if err != nil { | ||
393 | return nil, fmt.Errorf("error fetching checksums: %s", err) | ||
394 | } | ||
395 | |||
396 | sig, err := getFile(sigURL) | ||
397 | if err != nil { | ||
398 | return nil, fmt.Errorf("error fetching checksums signature: %s", err) | ||
399 | } | ||
400 | |||
401 | if err := verifySig(sums, sig); err != nil { | ||
402 | return nil, err | ||
403 | } | ||
404 | |||
405 | return sums, nil | ||
406 | } | ||
407 | |||
408 | func getFile(url string) ([]byte, error) { | ||
409 | resp, err := httpClient.Get(url) | ||
410 | if err != nil { | ||
411 | return nil, err | ||
412 | } | ||
413 | defer resp.Body.Close() | ||
414 | |||
415 | if resp.StatusCode != http.StatusOK { | ||
416 | return nil, fmt.Errorf("%s", resp.Status) | ||
417 | } | ||
418 | |||
419 | data, err := ioutil.ReadAll(resp.Body) | ||
420 | if err != nil { | ||
421 | return data, err | ||
422 | } | ||
423 | return data, nil | ||
424 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/meta.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/meta.go new file mode 100644 index 0000000..bdcebcb --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/meta.go | |||
@@ -0,0 +1,41 @@ | |||
1 | package discovery | ||
2 | |||
3 | import ( | ||
4 | "crypto/sha256" | ||
5 | "io" | ||
6 | "os" | ||
7 | ) | ||
8 | |||
9 | // PluginMeta is metadata about a plugin, useful for launching the plugin | ||
10 | // and for understanding which plugins are available. | ||
11 | type PluginMeta struct { | ||
12 | // Name is the name of the plugin, e.g. as inferred from the plugin | ||
13 | // binary's filename, or by explicit configuration. | ||
14 | Name string | ||
15 | |||
16 | // Version is the semver version of the plugin, expressed as a string | ||
17 | // that might not be semver-valid. | ||
18 | Version VersionStr | ||
19 | |||
20 | // Path is the absolute path of the executable that can be launched | ||
21 | // to provide the RPC server for this plugin. | ||
22 | Path string | ||
23 | } | ||
24 | |||
25 | // SHA256 returns a SHA256 hash of the content of the referenced executable | ||
26 | // file, or an error if the file's contents cannot be read. | ||
27 | func (m PluginMeta) SHA256() ([]byte, error) { | ||
28 | f, err := os.Open(m.Path) | ||
29 | if err != nil { | ||
30 | return nil, err | ||
31 | } | ||
32 | defer f.Close() | ||
33 | |||
34 | h := sha256.New() | ||
35 | _, err = io.Copy(h, f) | ||
36 | if err != nil { | ||
37 | return nil, err | ||
38 | } | ||
39 | |||
40 | return h.Sum(nil), nil | ||
41 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/meta_set.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/meta_set.go new file mode 100644 index 0000000..181ea1f --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/meta_set.go | |||
@@ -0,0 +1,195 @@ | |||
1 | package discovery | ||
2 | |||
3 | // A PluginMetaSet is a set of PluginMeta objects meeting a certain criteria. | ||
4 | // | ||
5 | // Methods on this type allow filtering of the set to produce subsets that | ||
6 | // meet more restrictive criteria. | ||
7 | type PluginMetaSet map[PluginMeta]struct{} | ||
8 | |||
9 | // Add inserts the given PluginMeta into the receiving set. This is a no-op | ||
10 | // if the given meta is already present. | ||
11 | func (s PluginMetaSet) Add(p PluginMeta) { | ||
12 | s[p] = struct{}{} | ||
13 | } | ||
14 | |||
15 | // Remove removes the given PluginMeta from the receiving set. This is a no-op | ||
16 | // if the given meta is not already present. | ||
17 | func (s PluginMetaSet) Remove(p PluginMeta) { | ||
18 | delete(s, p) | ||
19 | } | ||
20 | |||
21 | // Has returns true if the given meta is in the receiving set, or false | ||
22 | // otherwise. | ||
23 | func (s PluginMetaSet) Has(p PluginMeta) bool { | ||
24 | _, ok := s[p] | ||
25 | return ok | ||
26 | } | ||
27 | |||
28 | // Count returns the number of metas in the set | ||
29 | func (s PluginMetaSet) Count() int { | ||
30 | return len(s) | ||
31 | } | ||
32 | |||
33 | // ValidateVersions returns two new PluginMetaSets, separating those with | ||
34 | // versions that have syntax-valid semver versions from those that don't. | ||
35 | // | ||
36 | // Eliminating invalid versions from consideration (and possibly warning about | ||
37 | // them) is usually the first step of working with a meta set after discovery | ||
38 | // has completed. | ||
39 | func (s PluginMetaSet) ValidateVersions() (valid, invalid PluginMetaSet) { | ||
40 | valid = make(PluginMetaSet) | ||
41 | invalid = make(PluginMetaSet) | ||
42 | for p := range s { | ||
43 | if _, err := p.Version.Parse(); err == nil { | ||
44 | valid.Add(p) | ||
45 | } else { | ||
46 | invalid.Add(p) | ||
47 | } | ||
48 | } | ||
49 | return | ||
50 | } | ||
51 | |||
52 | // WithName returns the subset of metas that have the given name. | ||
53 | func (s PluginMetaSet) WithName(name string) PluginMetaSet { | ||
54 | ns := make(PluginMetaSet) | ||
55 | for p := range s { | ||
56 | if p.Name == name { | ||
57 | ns.Add(p) | ||
58 | } | ||
59 | } | ||
60 | return ns | ||
61 | } | ||
62 | |||
63 | // WithVersion returns the subset of metas that have the given version. | ||
64 | // | ||
65 | // This should be used only with the "valid" result from ValidateVersions; | ||
66 | // it will ignore any plugin metas that have a invalid version strings. | ||
67 | func (s PluginMetaSet) WithVersion(version Version) PluginMetaSet { | ||
68 | ns := make(PluginMetaSet) | ||
69 | for p := range s { | ||
70 | gotVersion, err := p.Version.Parse() | ||
71 | if err != nil { | ||
72 | continue | ||
73 | } | ||
74 | if gotVersion.Equal(version) { | ||
75 | ns.Add(p) | ||
76 | } | ||
77 | } | ||
78 | return ns | ||
79 | } | ||
80 | |||
81 | // ByName groups the metas in the set by their Names, returning a map. | ||
82 | func (s PluginMetaSet) ByName() map[string]PluginMetaSet { | ||
83 | ret := make(map[string]PluginMetaSet) | ||
84 | for p := range s { | ||
85 | if _, ok := ret[p.Name]; !ok { | ||
86 | ret[p.Name] = make(PluginMetaSet) | ||
87 | } | ||
88 | ret[p.Name].Add(p) | ||
89 | } | ||
90 | return ret | ||
91 | } | ||
92 | |||
93 | // Newest returns the one item from the set that has the newest Version value. | ||
94 | // | ||
95 | // The result is meaningful only if the set is already filtered such that | ||
96 | // all of the metas have the same Name. | ||
97 | // | ||
98 | // If there isn't at least one meta in the set then this function will panic. | ||
99 | // Use Count() to ensure that there is at least one value before calling. | ||
100 | // | ||
101 | // If any of the metas have invalid version strings then this function will | ||
102 | // panic. Use ValidateVersions() first to filter out metas with invalid | ||
103 | // versions. | ||
104 | // | ||
105 | // If two metas have the same Version then one is arbitrarily chosen. This | ||
106 | // situation should be avoided by pre-filtering the set. | ||
107 | func (s PluginMetaSet) Newest() PluginMeta { | ||
108 | if len(s) == 0 { | ||
109 | panic("can't call NewestStable on empty PluginMetaSet") | ||
110 | } | ||
111 | |||
112 | var first = true | ||
113 | var winner PluginMeta | ||
114 | var winnerVersion Version | ||
115 | for p := range s { | ||
116 | version, err := p.Version.Parse() | ||
117 | if err != nil { | ||
118 | panic(err) | ||
119 | } | ||
120 | |||
121 | if first == true || version.NewerThan(winnerVersion) { | ||
122 | winner = p | ||
123 | winnerVersion = version | ||
124 | first = false | ||
125 | } | ||
126 | } | ||
127 | |||
128 | return winner | ||
129 | } | ||
130 | |||
131 | // ConstrainVersions takes a set of requirements and attempts to | ||
132 | // return a map from name to a set of metas that have the matching | ||
133 | // name and an appropriate version. | ||
134 | // | ||
135 | // If any of the given requirements match *no* plugins then its PluginMetaSet | ||
136 | // in the returned map will be empty. | ||
137 | // | ||
138 | // All viable metas are returned, so the caller can apply any desired filtering | ||
139 | // to reduce down to a single option. For example, calling Newest() to obtain | ||
140 | // the highest available version. | ||
141 | // | ||
142 | // If any of the metas in the set have invalid version strings then this | ||
143 | // function will panic. Use ValidateVersions() first to filter out metas with | ||
144 | // invalid versions. | ||
145 | func (s PluginMetaSet) ConstrainVersions(reqd PluginRequirements) map[string]PluginMetaSet { | ||
146 | ret := make(map[string]PluginMetaSet) | ||
147 | for p := range s { | ||
148 | name := p.Name | ||
149 | allowedVersions, ok := reqd[name] | ||
150 | if !ok { | ||
151 | continue | ||
152 | } | ||
153 | if _, ok := ret[p.Name]; !ok { | ||
154 | ret[p.Name] = make(PluginMetaSet) | ||
155 | } | ||
156 | version, err := p.Version.Parse() | ||
157 | if err != nil { | ||
158 | panic(err) | ||
159 | } | ||
160 | if allowedVersions.Allows(version) { | ||
161 | ret[p.Name].Add(p) | ||
162 | } | ||
163 | } | ||
164 | return ret | ||
165 | } | ||
166 | |||
167 | // OverridePaths returns a new set where any existing plugins with the given | ||
168 | // names are removed and replaced with the single path given in the map. | ||
169 | // | ||
170 | // This is here only to continue to support the legacy way of overriding | ||
171 | // plugin binaries in the .terraformrc file. It treats all given plugins | ||
172 | // as pre-versioning (version 0.0.0). This mechanism will eventually be | ||
173 | // phased out, with vendor directories being the intended replacement. | ||
174 | func (s PluginMetaSet) OverridePaths(paths map[string]string) PluginMetaSet { | ||
175 | ret := make(PluginMetaSet) | ||
176 | for p := range s { | ||
177 | if _, ok := paths[p.Name]; ok { | ||
178 | // Skip plugins that we're overridding | ||
179 | continue | ||
180 | } | ||
181 | |||
182 | ret.Add(p) | ||
183 | } | ||
184 | |||
185 | // Now add the metadata for overriding plugins | ||
186 | for name, path := range paths { | ||
187 | ret.Add(PluginMeta{ | ||
188 | Name: name, | ||
189 | Version: VersionZero, | ||
190 | Path: path, | ||
191 | }) | ||
192 | } | ||
193 | |||
194 | return ret | ||
195 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/requirements.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/requirements.go new file mode 100644 index 0000000..75430fd --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/requirements.go | |||
@@ -0,0 +1,105 @@ | |||
1 | package discovery | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | ) | ||
6 | |||
7 | // PluginRequirements describes a set of plugins (assumed to be of a consistent | ||
8 | // kind) that are required to exist and have versions within the given | ||
9 | // corresponding sets. | ||
10 | type PluginRequirements map[string]*PluginConstraints | ||
11 | |||
12 | // PluginConstraints represents an element of PluginRequirements describing | ||
13 | // the constraints for a single plugin. | ||
14 | type PluginConstraints struct { | ||
15 | // Specifies that the plugin's version must be within the given | ||
16 | // constraints. | ||
17 | Versions Constraints | ||
18 | |||
19 | // If non-nil, the hash of the on-disk plugin executable must exactly | ||
20 | // match the SHA256 hash given here. | ||
21 | SHA256 []byte | ||
22 | } | ||
23 | |||
24 | // Allows returns true if the given version is within the receiver's version | ||
25 | // constraints. | ||
26 | func (s *PluginConstraints) Allows(v Version) bool { | ||
27 | return s.Versions.Allows(v) | ||
28 | } | ||
29 | |||
30 | // AcceptsSHA256 returns true if the given executable SHA256 hash is acceptable, | ||
31 | // either because it matches the constraint or because there is no such | ||
32 | // constraint. | ||
33 | func (s *PluginConstraints) AcceptsSHA256(digest []byte) bool { | ||
34 | if s.SHA256 == nil { | ||
35 | return true | ||
36 | } | ||
37 | return bytes.Equal(s.SHA256, digest) | ||
38 | } | ||
39 | |||
40 | // Merge takes the contents of the receiver and the other given requirements | ||
41 | // object and merges them together into a single requirements structure | ||
42 | // that satisfies both sets of requirements. | ||
43 | // | ||
44 | // Note that it doesn't make sense to merge two PluginRequirements with | ||
45 | // differing required plugin SHA256 hashes, since the result will never | ||
46 | // match any plugin. | ||
47 | func (r PluginRequirements) Merge(other PluginRequirements) PluginRequirements { | ||
48 | ret := make(PluginRequirements) | ||
49 | for n, c := range r { | ||
50 | ret[n] = &PluginConstraints{ | ||
51 | Versions: Constraints{}.Append(c.Versions), | ||
52 | SHA256: c.SHA256, | ||
53 | } | ||
54 | } | ||
55 | for n, c := range other { | ||
56 | if existing, exists := ret[n]; exists { | ||
57 | ret[n].Versions = ret[n].Versions.Append(c.Versions) | ||
58 | |||
59 | if existing.SHA256 != nil { | ||
60 | if c.SHA256 != nil && !bytes.Equal(c.SHA256, existing.SHA256) { | ||
61 | // If we've been asked to merge two constraints with | ||
62 | // different SHA256 hashes then we'll produce a dummy value | ||
63 | // that can never match anything. This is a silly edge case | ||
64 | // that no reasonable caller should hit. | ||
65 | ret[n].SHA256 = []byte(invalidProviderHash) | ||
66 | } | ||
67 | } else { | ||
68 | ret[n].SHA256 = c.SHA256 // might still be nil | ||
69 | } | ||
70 | } else { | ||
71 | ret[n] = &PluginConstraints{ | ||
72 | Versions: Constraints{}.Append(c.Versions), | ||
73 | SHA256: c.SHA256, | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | return ret | ||
78 | } | ||
79 | |||
80 | // LockExecutables applies additional constraints to the receiver that | ||
81 | // require plugin executables with specific SHA256 digests. This modifies | ||
82 | // the receiver in-place, since it's intended to be applied after | ||
83 | // version constraints have been resolved. | ||
84 | // | ||
85 | // The given map must include a key for every plugin that is already | ||
86 | // required. If not, any missing keys will cause the corresponding plugin | ||
87 | // to never match, though the direct caller doesn't necessarily need to | ||
88 | // guarantee this as long as the downstream code _applying_ these constraints | ||
89 | // is able to deal with the non-match in some way. | ||
90 | func (r PluginRequirements) LockExecutables(sha256s map[string][]byte) { | ||
91 | for name, cons := range r { | ||
92 | digest := sha256s[name] | ||
93 | |||
94 | if digest == nil { | ||
95 | // Prevent any match, which will then presumably cause the | ||
96 | // downstream consumer of this requirements to report an error. | ||
97 | cons.SHA256 = []byte(invalidProviderHash) | ||
98 | continue | ||
99 | } | ||
100 | |||
101 | cons.SHA256 = digest | ||
102 | } | ||
103 | } | ||
104 | |||
105 | const invalidProviderHash = "<invalid>" | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/signature.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/signature.go new file mode 100644 index 0000000..b6686a5 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/signature.go | |||
@@ -0,0 +1,53 @@ | |||
1 | package discovery | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "log" | ||
6 | "strings" | ||
7 | |||
8 | "golang.org/x/crypto/openpgp" | ||
9 | ) | ||
10 | |||
11 | // Verify the data using the provided openpgp detached signature and the | ||
12 | // embedded hashicorp public key. | ||
13 | func verifySig(data, sig []byte) error { | ||
14 | el, err := openpgp.ReadArmoredKeyRing(strings.NewReader(hashiPublicKey)) | ||
15 | if err != nil { | ||
16 | log.Fatal(err) | ||
17 | } | ||
18 | |||
19 | _, err = openpgp.CheckDetachedSignature(el, bytes.NewReader(data), bytes.NewReader(sig)) | ||
20 | return err | ||
21 | } | ||
22 | |||
23 | // this is the public key that signs the checksums file for releases. | ||
24 | const hashiPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- | ||
25 | Version: GnuPG v1 | ||
26 | |||
27 | mQENBFMORM0BCADBRyKO1MhCirazOSVwcfTr1xUxjPvfxD3hjUwHtjsOy/bT6p9f | ||
28 | W2mRPfwnq2JB5As+paL3UGDsSRDnK9KAxQb0NNF4+eVhr/EJ18s3wwXXDMjpIifq | ||
29 | fIm2WyH3G+aRLTLPIpscUNKDyxFOUbsmgXAmJ46Re1fn8uKxKRHbfa39aeuEYWFA | ||
30 | 3drdL1WoUngvED7f+RnKBK2G6ZEpO+LDovQk19xGjiMTtPJrjMjZJ3QXqPvx5wca | ||
31 | KSZLr4lMTuoTI/ZXyZy5bD4tShiZz6KcyX27cD70q2iRcEZ0poLKHyEIDAi3TM5k | ||
32 | SwbbWBFd5RNPOR0qzrb/0p9ksKK48IIfH2FvABEBAAG0K0hhc2hpQ29ycCBTZWN1 | ||
33 | cml0eSA8c2VjdXJpdHlAaGFzaGljb3JwLmNvbT6JATgEEwECACIFAlMORM0CGwMG | ||
34 | CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEFGFLYc0j/xMyWIIAIPhcVqiQ59n | ||
35 | Jc07gjUX0SWBJAxEG1lKxfzS4Xp+57h2xxTpdotGQ1fZwsihaIqow337YHQI3q0i | ||
36 | SqV534Ms+j/tU7X8sq11xFJIeEVG8PASRCwmryUwghFKPlHETQ8jJ+Y8+1asRydi | ||
37 | psP3B/5Mjhqv/uOK+Vy3zAyIpyDOMtIpOVfjSpCplVRdtSTFWBu9Em7j5I2HMn1w | ||
38 | sJZnJgXKpybpibGiiTtmnFLOwibmprSu04rsnP4ncdC2XRD4wIjoyA+4PKgX3sCO | ||
39 | klEzKryWYBmLkJOMDdo52LttP3279s7XrkLEE7ia0fXa2c12EQ0f0DQ1tGUvyVEW | ||
40 | WmJVccm5bq25AQ0EUw5EzQEIANaPUY04/g7AmYkOMjaCZ6iTp9hB5Rsj/4ee/ln9 | ||
41 | wArzRO9+3eejLWh53FoN1rO+su7tiXJA5YAzVy6tuolrqjM8DBztPxdLBbEi4V+j | ||
42 | 2tK0dATdBQBHEh3OJApO2UBtcjaZBT31zrG9K55D+CrcgIVEHAKY8Cb4kLBkb5wM | ||
43 | skn+DrASKU0BNIV1qRsxfiUdQHZfSqtp004nrql1lbFMLFEuiY8FZrkkQ9qduixo | ||
44 | mTT6f34/oiY+Jam3zCK7RDN/OjuWheIPGj/Qbx9JuNiwgX6yRj7OE1tjUx6d8g9y | ||
45 | 0H1fmLJbb3WZZbuuGFnK6qrE3bGeY8+AWaJAZ37wpWh1p0cAEQEAAYkBHwQYAQIA | ||
46 | CQUCUw5EzQIbDAAKCRBRhS2HNI/8TJntCAClU7TOO/X053eKF1jqNW4A1qpxctVc | ||
47 | z8eTcY8Om5O4f6a/rfxfNFKn9Qyja/OG1xWNobETy7MiMXYjaa8uUx5iFy6kMVaP | ||
48 | 0BXJ59NLZjMARGw6lVTYDTIvzqqqwLxgliSDfSnqUhubGwvykANPO+93BBx89MRG | ||
49 | unNoYGXtPlhNFrAsB1VR8+EyKLv2HQtGCPSFBhrjuzH3gxGibNDDdFQLxxuJWepJ | ||
50 | EK1UbTS4ms0NgZ2Uknqn1WRU1Ki7rE4sTy68iZtWpKQXZEJa0IGnuI2sSINGcXCJ | ||
51 | oEIgXTMyCILo34Fa/C6VCm2WBgz9zZO8/rHIiQm1J5zqz0DrDwKBUM9C | ||
52 | =LYpS | ||
53 | -----END PGP PUBLIC KEY BLOCK-----` | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/version.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/version.go new file mode 100644 index 0000000..8fad58d --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/version.go | |||
@@ -0,0 +1,72 @@ | |||
1 | package discovery | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "sort" | ||
6 | |||
7 | version "github.com/hashicorp/go-version" | ||
8 | ) | ||
9 | |||
10 | const VersionZero = "0.0.0" | ||
11 | |||
12 | // A VersionStr is a string containing a possibly-invalid representation | ||
13 | // of a semver version number. Call Parse on it to obtain a real Version | ||
14 | // object, or discover that it is invalid. | ||
15 | type VersionStr string | ||
16 | |||
17 | // Parse transforms a VersionStr into a Version if it is | ||
18 | // syntactically valid. If it isn't then an error is returned instead. | ||
19 | func (s VersionStr) Parse() (Version, error) { | ||
20 | raw, err := version.NewVersion(string(s)) | ||
21 | if err != nil { | ||
22 | return Version{}, err | ||
23 | } | ||
24 | return Version{raw}, nil | ||
25 | } | ||
26 | |||
27 | // MustParse transforms a VersionStr into a Version if it is | ||
28 | // syntactically valid. If it isn't then it panics. | ||
29 | func (s VersionStr) MustParse() Version { | ||
30 | ret, err := s.Parse() | ||
31 | if err != nil { | ||
32 | panic(err) | ||
33 | } | ||
34 | return ret | ||
35 | } | ||
36 | |||
37 | // Version represents a version number that has been parsed from | ||
38 | // a semver string and known to be valid. | ||
39 | type Version struct { | ||
40 | // We wrap this here just because it avoids a proliferation of | ||
41 | // direct go-version imports all over the place, and keeps the | ||
42 | // version-processing details within this package. | ||
43 | raw *version.Version | ||
44 | } | ||
45 | |||
46 | func (v Version) String() string { | ||
47 | return v.raw.String() | ||
48 | } | ||
49 | |||
50 | func (v Version) NewerThan(other Version) bool { | ||
51 | return v.raw.GreaterThan(other.raw) | ||
52 | } | ||
53 | |||
54 | func (v Version) Equal(other Version) bool { | ||
55 | return v.raw.Equal(other.raw) | ||
56 | } | ||
57 | |||
58 | // MinorUpgradeConstraintStr returns a ConstraintStr that would permit | ||
59 | // minor upgrades relative to the receiving version. | ||
60 | func (v Version) MinorUpgradeConstraintStr() ConstraintStr { | ||
61 | segments := v.raw.Segments() | ||
62 | return ConstraintStr(fmt.Sprintf("~> %d.%d", segments[0], segments[1])) | ||
63 | } | ||
64 | |||
65 | type Versions []Version | ||
66 | |||
67 | // Sort sorts version from newest to oldest. | ||
68 | func (v Versions) Sort() { | ||
69 | sort.Slice(v, func(i, j int) bool { | ||
70 | return v[i].NewerThan(v[j]) | ||
71 | }) | ||
72 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/version_set.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/version_set.go new file mode 100644 index 0000000..0aefd75 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/version_set.go | |||
@@ -0,0 +1,84 @@ | |||
1 | package discovery | ||
2 | |||
3 | import ( | ||
4 | "sort" | ||
5 | |||
6 | version "github.com/hashicorp/go-version" | ||
7 | ) | ||
8 | |||
9 | // A ConstraintStr is a string containing a possibly-invalid representation | ||
10 | // of a version constraint provided in configuration. Call Parse on it to | ||
11 | // obtain a real Constraint object, or discover that it is invalid. | ||
12 | type ConstraintStr string | ||
13 | |||
14 | // Parse transforms a ConstraintStr into a Constraints if it is | ||
15 | // syntactically valid. If it isn't then an error is returned instead. | ||
16 | func (s ConstraintStr) Parse() (Constraints, error) { | ||
17 | raw, err := version.NewConstraint(string(s)) | ||
18 | if err != nil { | ||
19 | return Constraints{}, err | ||
20 | } | ||
21 | return Constraints{raw}, nil | ||
22 | } | ||
23 | |||
24 | // MustParse is like Parse but it panics if the constraint string is invalid. | ||
25 | func (s ConstraintStr) MustParse() Constraints { | ||
26 | ret, err := s.Parse() | ||
27 | if err != nil { | ||
28 | panic(err) | ||
29 | } | ||
30 | return ret | ||
31 | } | ||
32 | |||
33 | // Constraints represents a set of versions which any given Version is either | ||
34 | // a member of or not. | ||
35 | type Constraints struct { | ||
36 | raw version.Constraints | ||
37 | } | ||
38 | |||
39 | // AllVersions is a Constraints containing all versions | ||
40 | var AllVersions Constraints | ||
41 | |||
42 | func init() { | ||
43 | AllVersions = Constraints{ | ||
44 | raw: make(version.Constraints, 0), | ||
45 | } | ||
46 | } | ||
47 | |||
48 | // Allows returns true if the given version permitted by the receiving | ||
49 | // constraints set. | ||
50 | func (s Constraints) Allows(v Version) bool { | ||
51 | return s.raw.Check(v.raw) | ||
52 | } | ||
53 | |||
54 | // Append combines the receiving set with the given other set to produce | ||
55 | // a set that is the intersection of both sets, which is to say that resulting | ||
56 | // constraints contain only the versions that are members of both. | ||
57 | func (s Constraints) Append(other Constraints) Constraints { | ||
58 | raw := make(version.Constraints, 0, len(s.raw)+len(other.raw)) | ||
59 | |||
60 | // Since "raw" is a list of constraints that remove versions from the set, | ||
61 | // "Intersection" is implemented by concatenating together those lists, | ||
62 | // thus leaving behind only the versions not removed by either list. | ||
63 | raw = append(raw, s.raw...) | ||
64 | raw = append(raw, other.raw...) | ||
65 | |||
66 | // while the set is unordered, we sort these lexically for consistent output | ||
67 | sort.Slice(raw, func(i, j int) bool { | ||
68 | return raw[i].String() < raw[j].String() | ||
69 | }) | ||
70 | |||
71 | return Constraints{raw} | ||
72 | } | ||
73 | |||
74 | // String returns a string representation of the set members as a set | ||
75 | // of range constraints. | ||
76 | func (s Constraints) String() string { | ||
77 | return s.raw.String() | ||
78 | } | ||
79 | |||
80 | // Unconstrained returns true if and only if the receiver is an empty | ||
81 | // constraint set. | ||
82 | func (s Constraints) Unconstrained() bool { | ||
83 | return len(s.raw) == 0 | ||
84 | } | ||