diff --git a/command/plugins_install.go b/command/plugins_install.go index f6d7b11086e..8b108528dde 100644 --- a/command/plugins_install.go +++ b/command/plugins_install.go @@ -4,6 +4,7 @@ package command import ( + "bytes" "context" "crypto/sha256" "encoding/json" @@ -12,6 +13,7 @@ import ( "io" "os" "os/exec" + "path/filepath" "runtime" "strings" @@ -48,8 +50,8 @@ Usage: packer plugins install [OPTIONS...] [] Options: - path : install the plugin from a locally-sourced plugin binary. This installs the plugin where a normal invocation would, but will - not try to download it from a web server, but instead directly - install the binary for Packer to be able to load it later on. + not try to download it from a remote location, and instead + install the binary in the Packer plugins path. This option cannot be specified with a version constraint. - force: forces installation of a plugin, even if it is already there. ` @@ -131,6 +133,10 @@ func (c *PluginsInstallCommand) RunContext(buildCtx context.Context, args *Plugi }, } + if runtime.GOOS == "windows" { + opts.BinaryInstallationOptions.Ext = ".exe" + } + plugin, diags := addrs.ParsePluginSourceString(args.PluginName) if diags.HasErrors() { c.Ui.Error(diags.Error()) @@ -140,7 +146,7 @@ func (c *PluginsInstallCommand) RunContext(buildCtx context.Context, args *Plugi // If we did specify a binary to install the plugin from, we ignore // the Github-based getter in favour of installing it directly. if args.PluginPath != "" { - return c.InstallFromBinary(args) + return c.InstallFromBinary(opts, plugin, args) } // a plugin requirement that matches them all @@ -157,10 +163,6 @@ func (c *PluginsInstallCommand) RunContext(buildCtx context.Context, args *Plugi pluginRequirement.VersionConstraints = constraints } - if runtime.GOOS == "windows" && opts.Ext == "" { - opts.BinaryInstallationOptions.Ext = ".exe" - } - getters := []plugingetter.Getter{ &github.Getter{ // In the past some terraform plugins downloads were blocked from a @@ -199,36 +201,23 @@ func (c *PluginsInstallCommand) RunContext(buildCtx context.Context, args *Plugi return 0 } -func (c *PluginsInstallCommand) InstallFromBinary(args *PluginsInstallArgs) int { - pluginDirs := c.Meta.CoreConfig.Components.PluginConfig.KnownPluginFolders - - if len(pluginDirs) == 0 { - c.Ui.Say(`Error: cannot find a place to install the plugin to - -In order to install the plugin for later use, Packer needs to know where to -install them. - -This can be specified through the PACKER_CONFIG_DIR environment variable, -but should be automatically inferred by Packer. +func (c *PluginsInstallCommand) InstallFromBinary(opts plugingetter.ListInstallationsOptions, plgin *addrs.Plugin, args *PluginsInstallArgs) int { + // As with the other commands, we get the last plugin directory as it + // has precedence over the others, and is where we'll install the + // plugins to. + pluginDir := opts.FromFolders[len(opts.FromFolders)-1] -If you see this message, this is likely a Packer bug, please consider opening -an issue on our Github repo to signal it.`) - } + var err error - pluginSlugParts := strings.Split(args.PluginName, "/") - if len(pluginSlugParts) != 3 { + args.PluginPath, err = filepath.Abs(args.PluginPath) + if err != nil { return writeDiags(c.Ui, nil, hcl.Diagnostics{&hcl.Diagnostic{ Severity: hcl.DiagError, - Summary: "Invalid plugin name specifier", - Detail: fmt.Sprintf("The plugin name specified provided (%q) does not conform to the mandated format of //.", args.PluginName), + Summary: "Failed to transform path", + Detail: fmt.Sprintf("Failed to transform the given path to an absolute one: %s", err), }}) } - // As with the other commands, we get the last plugin directory as it - // has precedence over the others, and is where we'll install the - // plugins to. - pluginDir := pluginDirs[len(pluginDirs)-1] - s, err := os.Stat(args.PluginPath) if err != nil { return writeDiags(c.Ui, nil, hcl.Diagnostics{&hcl.Diagnostic{ @@ -254,6 +243,7 @@ an issue on our Github repo to signal it.`) Detail: fmt.Sprintf("Packer failed to run %s describe: %s", args.PluginPath, err), }}) } + var desc plugin.SetDescription if err := json.Unmarshal(describeCmd, &desc); err != nil { return writeDiags(c.Ui, nil, hcl.Diagnostics{&hcl.Diagnostic{ @@ -263,7 +253,15 @@ an issue on our Github repo to signal it.`) }}) } - if strings.Contains(desc.Version, "-") { + semver, err := version.NewSemver(desc.Version) + if err != nil { + return writeDiags(c.Ui, nil, hcl.Diagnostics{&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid version", + Detail: fmt.Sprintf("Plugin's reported version (%q) is not semver-compatible: %s", desc.Version, err), + }}) + } + if semver.Prerelease() != "" { return writeDiags(c.Ui, nil, hcl.Diagnostics{&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid version", @@ -279,15 +277,9 @@ an issue on our Github repo to signal it.`) Detail: fmt.Sprintf("Failed to open plugin binary from %q: %s", args.PluginPath, err), }}) } - defer pluginBinary.Close() - // We'll install the SHA256SUM file alongside the plugin, based on the - // contents of the plugin being passed. - // - // This will make our loaders happy as they require a valid checksum - // for loading plugins installed this way. - shasum := sha256.New() - _, err = io.Copy(shasum, pluginBinary) + pluginContents := &bytes.Buffer{} + _, err = io.Copy(pluginContents, pluginBinary) if err != nil { return writeDiags(c.Ui, nil, hcl.Diagnostics{&hcl.Diagnostic{ Severity: hcl.DiagError, @@ -295,10 +287,14 @@ an issue on our Github repo to signal it.`) Detail: fmt.Sprintf("Failed to read plugin binary from %q: %s", args.PluginPath, err), }}) } + _ = pluginBinary.Close() // At this point, we know the provided binary behaves correctly with // describe, so it's very likely to be a plugin, let's install it. - installDir := fmt.Sprintf("%s/%s", pluginDir, args.PluginName) + installDir := filepath.Join( + pluginDir, + filepath.Join(plgin.Parts()...), + ) err = os.MkdirAll(installDir, 0755) if err != nil { return writeDiags(c.Ui, nil, hcl.Diagnostics{&hcl.Diagnostic{ @@ -308,15 +304,17 @@ an issue on our Github repo to signal it.`) }}) } - binaryPath := fmt.Sprintf( - "%s/packer-plugin-%s_v%s_%s_%s_%s", - installDir, - pluginSlugParts[2], + outputPrefix := fmt.Sprintf( + "packer-plugin-%s_v%s_%s", + plgin.Type, desc.Version, desc.APIVersion, - runtime.GOOS, - runtime.GOARCH, ) + binaryPath := filepath.Join( + installDir, + outputPrefix+opts.BinaryInstallationOptions.FilenameSuffix(), + ) + outputPlugin, err := os.OpenFile(binaryPath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0755) if err != nil { return writeDiags(c.Ui, nil, hcl.Diagnostics{&hcl.Diagnostic{ @@ -327,16 +325,7 @@ an issue on our Github repo to signal it.`) } defer outputPlugin.Close() - _, err = pluginBinary.Seek(0, 0) - if err != nil { - return writeDiags(c.Ui, nil, hcl.Diagnostics{&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Failed to reset plugin's reader", - Detail: fmt.Sprintf("Failed to seek offset 0 while attempting to reset the buffer for the plugin to install: %s", err), - }}) - } - - _, err = io.Copy(outputPlugin, pluginBinary) + _, err = outputPlugin.Write(pluginContents.Bytes()) if err != nil { return writeDiags(c.Ui, nil, hcl.Diagnostics{&hcl.Diagnostic{ Severity: hcl.DiagError, @@ -345,6 +334,11 @@ an issue on our Github repo to signal it.`) }}) } + // We'll install the SHA256SUM file alongside the plugin, based on the + // contents of the plugin being passed. + shasum := sha256.New() + _, _ = shasum.Write(pluginContents.Bytes()) + shasumPath := fmt.Sprintf("%s_SHA256SUM", binaryPath) shaFile, err := os.OpenFile(shasumPath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0644) if err != nil {