2023-03-02 15:37:05 -05:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
2023-08-10 18:53:29 -04:00
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
2023-03-02 15:37:05 -05:00
|
|
|
|
2021-02-02 12:05:04 -05:00
|
|
|
package command
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"crypto/sha256"
|
|
|
|
|
"fmt"
|
|
|
|
|
"log"
|
|
|
|
|
"runtime"
|
|
|
|
|
"strings"
|
|
|
|
|
|
2025-07-28 23:20:23 -04:00
|
|
|
"github.com/hashicorp/packer/packer/plugin-getter/release"
|
|
|
|
|
|
2023-10-25 11:56:30 -04:00
|
|
|
gversion "github.com/hashicorp/go-version"
|
2021-02-02 12:05:04 -05:00
|
|
|
pluginsdk "github.com/hashicorp/packer-plugin-sdk/plugin"
|
2021-02-04 05:11:42 -05:00
|
|
|
"github.com/hashicorp/packer/packer"
|
2021-02-02 12:05:04 -05:00
|
|
|
plugingetter "github.com/hashicorp/packer/packer/plugin-getter"
|
|
|
|
|
"github.com/hashicorp/packer/packer/plugin-getter/github"
|
|
|
|
|
"github.com/hashicorp/packer/version"
|
|
|
|
|
"github.com/posener/complete"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type InitCommand struct {
|
|
|
|
|
Meta
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *InitCommand) Run(args []string) int {
|
|
|
|
|
ctx, cleanup := handleTermInterrupt(c.Ui)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
cfg, ret := c.ParseArgs(args)
|
|
|
|
|
if ret != 0 {
|
|
|
|
|
return ret
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return c.RunContext(ctx, cfg)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *InitCommand) ParseArgs(args []string) (*InitArgs, int) {
|
|
|
|
|
var cfg InitArgs
|
2023-10-06 10:48:31 -04:00
|
|
|
flags := c.Meta.FlagSet("init")
|
2021-02-02 12:05:04 -05:00
|
|
|
flags.Usage = func() { c.Ui.Say(c.Help()) }
|
|
|
|
|
cfg.AddFlagSets(flags)
|
|
|
|
|
if err := flags.Parse(args); err != nil {
|
|
|
|
|
return &cfg, 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
args = flags.Args()
|
|
|
|
|
if len(args) != 1 {
|
|
|
|
|
flags.Usage()
|
|
|
|
|
return &cfg, 1
|
|
|
|
|
}
|
|
|
|
|
cfg.Path = args[0]
|
|
|
|
|
return &cfg, 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *InitCommand) RunContext(buildCtx context.Context, cla *InitArgs) int {
|
|
|
|
|
packerStarter, ret := c.GetConfig(&cla.MetaArgs)
|
|
|
|
|
if ret != 0 {
|
|
|
|
|
return ret
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get plugins requirements
|
|
|
|
|
reqs, diags := packerStarter.PluginRequirements()
|
|
|
|
|
ret = writeDiags(c.Ui, nil, diags)
|
|
|
|
|
if ret != 0 {
|
|
|
|
|
return ret
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-27 10:47:34 -04:00
|
|
|
if len(reqs) == 0 {
|
|
|
|
|
c.Ui.Message(`
|
|
|
|
|
No plugins requirement found, make sure you reference a Packer config
|
|
|
|
|
containing a packer.required_plugins block. See
|
|
|
|
|
https://www.packer.io/docs/templates/hcl_templates/blocks/packer
|
|
|
|
|
for more info.`)
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-02 12:05:04 -05:00
|
|
|
opts := plugingetter.ListInstallationsOptions{
|
2024-01-15 14:41:25 -05:00
|
|
|
PluginDirectory: c.Meta.CoreConfig.Components.PluginConfig.PluginDirectory,
|
2021-02-02 12:05:04 -05:00
|
|
|
BinaryInstallationOptions: plugingetter.BinaryInstallationOptions{
|
|
|
|
|
OS: runtime.GOOS,
|
|
|
|
|
ARCH: runtime.GOARCH,
|
|
|
|
|
APIVersionMajor: pluginsdk.APIVersionMajor,
|
|
|
|
|
APIVersionMinor: pluginsdk.APIVersionMinor,
|
|
|
|
|
Checksummers: []plugingetter.Checksummer{
|
|
|
|
|
{Type: "sha256", Hash: sha256.New()},
|
|
|
|
|
},
|
command: list releases only for remote installs
When installing a plugin from a remote source, we list the installed
plugins that match the constraints specified, and if the constraint is
already satisfied, we don't do anything.
However, since remote installation is only relevant for releases of a
plugin, we should only look at the installed releases of a plugin, and
not consider pre-releases for that step.
This wasn't the case before this commit, as if a prerelease version of a
commit (ex: 10.8.1-dev), and we try to invoke `packer init` with a
constraint on this version specifically, Packer would locate that
pre-release and assume it was already installed, so would silently
succeed the command and do nothing.
This isn't the expected behaviour as we should install the final release
of that plugin, regardless of any prerelease installation of the plugin.
So this commit fixes that by only listing releases, so we don't report
the plugin being already installed if a prerelease is what's installed.
2024-05-02 09:52:51 -04:00
|
|
|
ReleasesOnly: true,
|
2021-02-02 12:05:04 -05:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if runtime.GOOS == "windows" && opts.Ext == "" {
|
|
|
|
|
opts.BinaryInstallationOptions.Ext = ".exe"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Printf("[TRACE] init: %#v", opts)
|
|
|
|
|
|
2025-07-28 23:20:23 -04:00
|
|
|
// the ordering of the getters is important here, place the getter on top which you want to try first
|
2021-02-02 12:05:04 -05:00
|
|
|
getters := []plugingetter.Getter{
|
2025-07-28 23:20:23 -04:00
|
|
|
&release.Getter{
|
|
|
|
|
Name: "releases.hashicorp.com",
|
|
|
|
|
},
|
2021-02-02 12:05:04 -05:00
|
|
|
&github.Getter{
|
|
|
|
|
// In the past some terraform plugins downloads were blocked from a
|
|
|
|
|
// specific aws region by s3. Changing the user agent unblocked the
|
|
|
|
|
// downloads so having one user agent per version will help mitigate
|
|
|
|
|
// that a little more. Especially in the case someone forks this
|
|
|
|
|
// code to make it more aggressive or something.
|
|
|
|
|
// TODO: allow to set this from the config file or an environment
|
|
|
|
|
// variable.
|
|
|
|
|
UserAgent: "packer-getter-github-" + version.String(),
|
2025-07-28 23:20:23 -04:00
|
|
|
Name: "github.com",
|
2021-02-02 12:05:04 -05:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-04 05:11:42 -05:00
|
|
|
ui := &packer.ColoredUi{
|
|
|
|
|
Color: packer.UiColorCyan,
|
|
|
|
|
Ui: c.Ui,
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-02 12:05:04 -05:00
|
|
|
for _, pluginRequirement := range reqs {
|
|
|
|
|
// Get installed plugins that match requirement
|
|
|
|
|
|
|
|
|
|
installs, err := pluginRequirement.ListInstallations(opts)
|
|
|
|
|
if err != nil {
|
|
|
|
|
c.Ui.Error(err.Error())
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-25 11:56:30 -04:00
|
|
|
if len(installs) > 0 {
|
|
|
|
|
if !cla.Force && !cla.Upgrade {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2021-02-02 12:05:04 -05:00
|
|
|
|
2023-10-25 11:56:30 -04:00
|
|
|
if cla.Force && !cla.Upgrade {
|
2024-04-29 16:09:57 -04:00
|
|
|
// Only place another constaint to the latest release
|
|
|
|
|
// binary, if any, otherwise this is essentially the same
|
|
|
|
|
// as an upgrade
|
|
|
|
|
var installVersion string
|
|
|
|
|
for _, install := range installs {
|
|
|
|
|
ver, _ := gversion.NewVersion(install.Version)
|
|
|
|
|
if ver.Prerelease() == "" {
|
|
|
|
|
installVersion = install.Version
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if installVersion != "" {
|
|
|
|
|
pluginRequirement.VersionConstraints, _ = gversion.NewConstraint(fmt.Sprintf("=%s", installVersion))
|
|
|
|
|
}
|
2023-10-25 11:56:30 -04:00
|
|
|
}
|
2021-02-02 12:05:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newInstall, err := pluginRequirement.InstallLatest(plugingetter.InstallOptions{
|
2024-01-15 14:41:25 -05:00
|
|
|
PluginDirectory: opts.PluginDirectory,
|
2021-02-02 12:05:04 -05:00
|
|
|
BinaryInstallationOptions: opts.BinaryInstallationOptions,
|
|
|
|
|
Getters: getters,
|
2023-10-25 11:56:30 -04:00
|
|
|
Force: cla.Force,
|
2021-02-02 12:05:04 -05:00
|
|
|
})
|
|
|
|
|
if err != nil {
|
2023-08-09 14:42:38 -04:00
|
|
|
c.Ui.Error(fmt.Sprintf("Failed getting the %q plugin:", pluginRequirement.Identifier))
|
|
|
|
|
c.Ui.Error(err.Error())
|
|
|
|
|
ret = 1
|
2021-02-02 12:05:04 -05:00
|
|
|
}
|
|
|
|
|
if newInstall != nil {
|
2021-02-15 09:32:42 -05:00
|
|
|
msg := fmt.Sprintf("Installed plugin %s %s in %q", pluginRequirement.Identifier, newInstall.Version, newInstall.BinaryPath)
|
2021-02-04 05:11:42 -05:00
|
|
|
ui.Say(msg)
|
2021-02-02 12:05:04 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ret
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (*InitCommand) Help() string {
|
|
|
|
|
helpText := `
|
2023-07-24 11:41:04 -04:00
|
|
|
Usage: packer init [options] TEMPLATE
|
2021-02-02 12:05:04 -05:00
|
|
|
|
|
|
|
|
Install all the missing plugins required in a Packer config. Note that Packer
|
|
|
|
|
does not have a state.
|
|
|
|
|
|
|
|
|
|
This is the first command that should be executed when working with a new
|
|
|
|
|
or existing template.
|
|
|
|
|
|
|
|
|
|
This command is always safe to run multiple times. Though subsequent runs may
|
|
|
|
|
give errors, this command will never delete anything.
|
|
|
|
|
|
|
|
|
|
Options:
|
|
|
|
|
-upgrade On top of installing missing plugins, update
|
|
|
|
|
installed plugins to the latest available
|
|
|
|
|
version, if there is a new higher one. Note that
|
|
|
|
|
this still takes into consideration the version
|
|
|
|
|
constraint of the config.
|
2023-12-04 09:50:02 -05:00
|
|
|
-force Forces reinstallation of plugins, even if already
|
2023-10-25 11:56:30 -04:00
|
|
|
installed.
|
2021-02-02 12:05:04 -05:00
|
|
|
`
|
|
|
|
|
|
|
|
|
|
return strings.TrimSpace(helpText)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (*InitCommand) Synopsis() string {
|
|
|
|
|
return "Install missing plugins or upgrade plugins"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (*InitCommand) AutocompleteArgs() complete.Predictor {
|
|
|
|
|
return complete.PredictNothing
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (*InitCommand) AutocompleteFlags() complete.Flags {
|
|
|
|
|
return complete.Flags{
|
|
|
|
|
"-upgrade": complete.PredictNothing,
|
|
|
|
|
}
|
|
|
|
|
}
|