plugin/discovery: allow customizing the OS/arch for auto-install
Previously we forced only installing for the current GOOS and GOARCH. Now we allow this to be optionally overridden, which allows building tools that can, for example, populate a directory with plugins to run on a Linux server while working on a Mac.
This commit is contained in:
parent
9ee2fbaa2b
commit
610fcb605e
|
@ -32,35 +32,6 @@ var releaseHost = "https://releases.hashicorp.com"
|
|||
|
||||
var httpClient = cleanhttp.DefaultClient()
|
||||
|
||||
// Plugins are referred to by the short name, but all URLs and files will use
|
||||
// the full name prefixed with terraform-<plugin_type>-
|
||||
func providerName(name string) string {
|
||||
return "terraform-provider-" + name
|
||||
}
|
||||
|
||||
func providerFileName(name, version string) string {
|
||||
return fmt.Sprintf("%s_%s_%s_%s.zip", providerName(name), version, runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
// providerVersionsURL returns the path to the released versions directory for the provider:
|
||||
// https://releases.hashicorp.com/terraform-provider-name/
|
||||
func providerVersionsURL(name string) string {
|
||||
return releaseHost + "/" + providerName(name) + "/"
|
||||
}
|
||||
|
||||
// providerURL returns the full path to the provider file, using the current OS
|
||||
// and ARCH:
|
||||
// .../terraform-provider-name_<x.y.z>/terraform-provider-name_<x.y.z>_<os>_<arch>.<ext>
|
||||
func providerURL(name, version string) string {
|
||||
return fmt.Sprintf("%s%s/%s", providerVersionsURL(name), version, providerFileName(name, version))
|
||||
}
|
||||
|
||||
func providerChecksumURL(name, version string) string {
|
||||
fileName := fmt.Sprintf("%s_%s_SHA256SUMS", providerName(name), version)
|
||||
u := fmt.Sprintf("%s%s/%s", providerVersionsURL(name), version, fileName)
|
||||
return u
|
||||
}
|
||||
|
||||
// An Installer maintains a local cache of plugins by downloading plugins
|
||||
// from an online repository.
|
||||
type Installer interface {
|
||||
|
@ -78,6 +49,13 @@ type ProviderInstaller struct {
|
|||
|
||||
PluginProtocolVersion uint
|
||||
|
||||
// OS and Arch specify the OS and architecture that should be used when
|
||||
// installing plugins. These use the same labels as the runtime.GOOS and
|
||||
// runtime.GOARCH variables respectively, and indeed the values of these
|
||||
// are used as defaults if either of these is the empty string.
|
||||
OS string
|
||||
Arch string
|
||||
|
||||
// Skip checksum and signature verification
|
||||
SkipVerify bool
|
||||
}
|
||||
|
@ -102,7 +80,7 @@ type ProviderInstaller struct {
|
|||
// be presented alongside context about what is being installed, and thus the
|
||||
// error messages do not redundantly include such information.
|
||||
func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, error) {
|
||||
versions, err := listProviderVersions(provider)
|
||||
versions, err := i.listProviderVersions(provider)
|
||||
// TODO: return multiple errors
|
||||
if err != nil {
|
||||
return PluginMeta{}, err
|
||||
|
@ -122,10 +100,10 @@ func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, e
|
|||
|
||||
// take the first matching plugin we find
|
||||
for _, v := range versions {
|
||||
url := providerURL(provider, v.String())
|
||||
url := i.providerURL(provider, v.String())
|
||||
|
||||
if !i.SkipVerify {
|
||||
sha256, err := getProviderChecksum(provider, v.String())
|
||||
sha256, err := i.getProviderChecksum(provider, v.String())
|
||||
if err != nil {
|
||||
return PluginMeta{}, err
|
||||
}
|
||||
|
@ -218,6 +196,52 @@ func (i *ProviderInstaller) PurgeUnused(used map[string]PluginMeta) (PluginMetaS
|
|||
return removed, errs
|
||||
}
|
||||
|
||||
// Plugins are referred to by the short name, but all URLs and files will use
|
||||
// the full name prefixed with terraform-<plugin_type>-
|
||||
func (i *ProviderInstaller) providerName(name string) string {
|
||||
return "terraform-provider-" + name
|
||||
}
|
||||
|
||||
func (i *ProviderInstaller) providerFileName(name, version string) string {
|
||||
os := i.OS
|
||||
arch := i.Arch
|
||||
if os == "" {
|
||||
os = runtime.GOOS
|
||||
}
|
||||
if arch == "" {
|
||||
arch = runtime.GOARCH
|
||||
}
|
||||
return fmt.Sprintf("%s_%s_%s_%s.zip", i.providerName(name), version, os, arch)
|
||||
}
|
||||
|
||||
// providerVersionsURL returns the path to the released versions directory for the provider:
|
||||
// https://releases.hashicorp.com/terraform-provider-name/
|
||||
func (i *ProviderInstaller) providerVersionsURL(name string) string {
|
||||
return releaseHost + "/" + i.providerName(name) + "/"
|
||||
}
|
||||
|
||||
// providerURL returns the full path to the provider file, using the current OS
|
||||
// and ARCH:
|
||||
// .../terraform-provider-name_<x.y.z>/terraform-provider-name_<x.y.z>_<os>_<arch>.<ext>
|
||||
func (i *ProviderInstaller) providerURL(name, version string) string {
|
||||
return fmt.Sprintf("%s%s/%s", i.providerVersionsURL(name), version, i.providerFileName(name, version))
|
||||
}
|
||||
|
||||
func (i *ProviderInstaller) providerChecksumURL(name, version string) string {
|
||||
fileName := fmt.Sprintf("%s_%s_SHA256SUMS", i.providerName(name), version)
|
||||
u := fmt.Sprintf("%s%s/%s", i.providerVersionsURL(name), version, fileName)
|
||||
return u
|
||||
}
|
||||
|
||||
func (i *ProviderInstaller) getProviderChecksum(name, version string) (string, error) {
|
||||
checksums, err := getPluginSHA256SUMs(i.providerChecksumURL(name, version))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return checksumForFile(checksums, i.providerFileName(name, version)), nil
|
||||
}
|
||||
|
||||
// Return the plugin version by making a HEAD request to the provided url
|
||||
func checkPlugin(url string, pluginProtocolVersion uint) bool {
|
||||
resp, err := httpClient.Head(url)
|
||||
|
@ -246,6 +270,17 @@ func checkPlugin(url string, pluginProtocolVersion uint) bool {
|
|||
return protoVersion == int(pluginProtocolVersion)
|
||||
}
|
||||
|
||||
// list the version available for the named plugin
|
||||
func (i *ProviderInstaller) listProviderVersions(name string) ([]Version, error) {
|
||||
versions, err := listPluginVersions(i.providerVersionsURL(name))
|
||||
if err != nil {
|
||||
// listPluginVersions returns a verbose error message indicating
|
||||
// what was being accessed and what failed
|
||||
return nil, err
|
||||
}
|
||||
return versions, nil
|
||||
}
|
||||
|
||||
var errVersionNotFound = errors.New("version not found")
|
||||
|
||||
// take the list of available versions for a plugin, and filter out those that
|
||||
|
@ -262,17 +297,6 @@ func allowedVersions(available []Version, required Constraints) []Version {
|
|||
return allowed
|
||||
}
|
||||
|
||||
// list the version available for the named plugin
|
||||
func listProviderVersions(name string) ([]Version, error) {
|
||||
versions, err := listPluginVersions(providerVersionsURL(name))
|
||||
if err != nil {
|
||||
// listPluginVersions returns a verbose error message indicating
|
||||
// what was being accessed and what failed
|
||||
return nil, err
|
||||
}
|
||||
return versions, nil
|
||||
}
|
||||
|
||||
// return a list of the plugin versions at the given URL
|
||||
func listPluginVersions(url string) ([]Version, error) {
|
||||
resp, err := httpClient.Get(url)
|
||||
|
@ -346,15 +370,6 @@ func versionsFromNames(names []string) []Version {
|
|||
return versions
|
||||
}
|
||||
|
||||
func getProviderChecksum(name, version string) (string, error) {
|
||||
checksums, err := getPluginSHA256SUMs(providerChecksumURL(name, version))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return checksumForFile(checksums, providerFileName(name, version)), nil
|
||||
}
|
||||
|
||||
func checksumForFile(sums []byte, name string) string {
|
||||
for _, line := range strings.Split(string(sums), "\n") {
|
||||
parts := strings.Fields(line)
|
||||
|
|
|
@ -100,7 +100,8 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
|
||||
func TestVersionListing(t *testing.T) {
|
||||
versions, err := listProviderVersions("test")
|
||||
i := &ProviderInstaller{}
|
||||
versions, err := i.listProviderVersions("test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -125,11 +126,12 @@ func TestVersionListing(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCheckProtocolVersions(t *testing.T) {
|
||||
if checkPlugin(providerURL("test", VersionStr("1.2.3").MustParse().String()), 4) {
|
||||
i := &ProviderInstaller{}
|
||||
if checkPlugin(i.providerURL("test", VersionStr("1.2.3").MustParse().String()), 4) {
|
||||
t.Fatal("protocol version 4 is not compatible")
|
||||
}
|
||||
|
||||
if !checkPlugin(providerURL("test", VersionStr("1.2.3").MustParse().String()), 3) {
|
||||
if !checkPlugin(i.providerURL("test", VersionStr("1.2.3").MustParse().String()), 3) {
|
||||
t.Fatal("protocol version 3 should be compatible")
|
||||
}
|
||||
}
|
||||
|
@ -265,8 +267,10 @@ func TestProviderInstallerPurgeUnused(t *testing.T) {
|
|||
|
||||
// Test fetching a provider's checksum file while verifying its signature.
|
||||
func TestProviderChecksum(t *testing.T) {
|
||||
i := &ProviderInstaller{}
|
||||
|
||||
// we only need the checksum, as getter is doing the actual file comparison.
|
||||
sha256sum, err := getProviderChecksum("template", "0.1.0")
|
||||
sha256sum, err := i.getProviderChecksum("template", "0.1.0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -277,7 +281,7 @@ func TestProviderChecksum(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := checksumForFile(sumData, providerFileName("template", "0.1.0"))
|
||||
expected := checksumForFile(sumData, i.providerFileName("template", "0.1.0"))
|
||||
|
||||
if sha256sum != expected {
|
||||
t.Fatalf("expected: %s\ngot %s\n", sha256sum, expected)
|
||||
|
@ -286,8 +290,10 @@ func TestProviderChecksum(t *testing.T) {
|
|||
|
||||
// Test fetching a provider's checksum file witha bad signature
|
||||
func TestProviderChecksumBadSignature(t *testing.T) {
|
||||
i := &ProviderInstaller{}
|
||||
|
||||
// we only need the checksum, as getter is doing the actual file comparison.
|
||||
sha256sum, err := getProviderChecksum("badsig", "0.1.0")
|
||||
sha256sum, err := i.getProviderChecksum("badsig", "0.1.0")
|
||||
if err == nil {
|
||||
t.Fatal("expcted error")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue