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()
|
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
|
// An Installer maintains a local cache of plugins by downloading plugins
|
||||||
// from an online repository.
|
// from an online repository.
|
||||||
type Installer interface {
|
type Installer interface {
|
||||||
|
@ -78,6 +49,13 @@ type ProviderInstaller struct {
|
||||||
|
|
||||||
PluginProtocolVersion uint
|
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
|
// Skip checksum and signature verification
|
||||||
SkipVerify bool
|
SkipVerify bool
|
||||||
}
|
}
|
||||||
|
@ -102,7 +80,7 @@ type ProviderInstaller struct {
|
||||||
// be presented alongside context about what is being installed, and thus the
|
// be presented alongside context about what is being installed, and thus the
|
||||||
// error messages do not redundantly include such information.
|
// error messages do not redundantly include such information.
|
||||||
func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, error) {
|
func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, error) {
|
||||||
versions, err := listProviderVersions(provider)
|
versions, err := i.listProviderVersions(provider)
|
||||||
// TODO: return multiple errors
|
// TODO: return multiple errors
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return PluginMeta{}, err
|
return PluginMeta{}, err
|
||||||
|
@ -122,10 +100,10 @@ func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, e
|
||||||
|
|
||||||
// take the first matching plugin we find
|
// take the first matching plugin we find
|
||||||
for _, v := range versions {
|
for _, v := range versions {
|
||||||
url := providerURL(provider, v.String())
|
url := i.providerURL(provider, v.String())
|
||||||
|
|
||||||
if !i.SkipVerify {
|
if !i.SkipVerify {
|
||||||
sha256, err := getProviderChecksum(provider, v.String())
|
sha256, err := i.getProviderChecksum(provider, v.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return PluginMeta{}, err
|
return PluginMeta{}, err
|
||||||
}
|
}
|
||||||
|
@ -218,6 +196,52 @@ func (i *ProviderInstaller) PurgeUnused(used map[string]PluginMeta) (PluginMetaS
|
||||||
return removed, errs
|
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
|
// Return the plugin version by making a HEAD request to the provided url
|
||||||
func checkPlugin(url string, pluginProtocolVersion uint) bool {
|
func checkPlugin(url string, pluginProtocolVersion uint) bool {
|
||||||
resp, err := httpClient.Head(url)
|
resp, err := httpClient.Head(url)
|
||||||
|
@ -246,6 +270,17 @@ func checkPlugin(url string, pluginProtocolVersion uint) bool {
|
||||||
return protoVersion == int(pluginProtocolVersion)
|
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")
|
var errVersionNotFound = errors.New("version not found")
|
||||||
|
|
||||||
// take the list of available versions for a plugin, and filter out those that
|
// 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
|
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
|
// return a list of the plugin versions at the given URL
|
||||||
func listPluginVersions(url string) ([]Version, error) {
|
func listPluginVersions(url string) ([]Version, error) {
|
||||||
resp, err := httpClient.Get(url)
|
resp, err := httpClient.Get(url)
|
||||||
|
@ -346,15 +370,6 @@ func versionsFromNames(names []string) []Version {
|
||||||
return versions
|
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 {
|
func checksumForFile(sums []byte, name string) string {
|
||||||
for _, line := range strings.Split(string(sums), "\n") {
|
for _, line := range strings.Split(string(sums), "\n") {
|
||||||
parts := strings.Fields(line)
|
parts := strings.Fields(line)
|
||||||
|
|
|
@ -100,7 +100,8 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVersionListing(t *testing.T) {
|
func TestVersionListing(t *testing.T) {
|
||||||
versions, err := listProviderVersions("test")
|
i := &ProviderInstaller{}
|
||||||
|
versions, err := i.listProviderVersions("test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -125,11 +126,12 @@ func TestVersionListing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckProtocolVersions(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")
|
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")
|
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.
|
// Test fetching a provider's checksum file while verifying its signature.
|
||||||
func TestProviderChecksum(t *testing.T) {
|
func TestProviderChecksum(t *testing.T) {
|
||||||
|
i := &ProviderInstaller{}
|
||||||
|
|
||||||
// we only need the checksum, as getter is doing the actual file comparison.
|
// 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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -277,7 +281,7 @@ func TestProviderChecksum(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := checksumForFile(sumData, providerFileName("template", "0.1.0"))
|
expected := checksumForFile(sumData, i.providerFileName("template", "0.1.0"))
|
||||||
|
|
||||||
if sha256sum != expected {
|
if sha256sum != expected {
|
||||||
t.Fatalf("expected: %s\ngot %s\n", 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
|
// Test fetching a provider's checksum file witha bad signature
|
||||||
func TestProviderChecksumBadSignature(t *testing.T) {
|
func TestProviderChecksumBadSignature(t *testing.T) {
|
||||||
|
i := &ProviderInstaller{}
|
||||||
|
|
||||||
// we only need the checksum, as getter is doing the actual file comparison.
|
// 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 {
|
if err == nil {
|
||||||
t.Fatal("expcted error")
|
t.Fatal("expcted error")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue