Skip to content

Commit

Permalink
plugin/discovery: allow customizing the OS/arch for auto-install
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
apparentlymart committed Jul 5, 2017
1 parent 9ee2fba commit 610fcb6
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 58 deletions.
119 changes: 67 additions & 52 deletions plugin/discovery/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
Expand All @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
18 changes: 12 additions & 6 deletions plugin/discovery/get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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")
}
}
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
Expand All @@ -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")
}
Expand Down

0 comments on commit 610fcb6

Please sign in to comment.