github.com/hashicorp/packer@v1.14.3/command/init.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package command
     5  
     6  import (
     7  	"context"
     8  	"crypto/sha256"
     9  	"fmt"
    10  	"log"
    11  	"runtime"
    12  	"strings"
    13  
    14  	"github.com/hashicorp/packer/packer/plugin-getter/release"
    15  
    16  	gversion "github.com/hashicorp/go-version"
    17  	pluginsdk "github.com/hashicorp/packer-plugin-sdk/plugin"
    18  	"github.com/hashicorp/packer/packer"
    19  	plugingetter "github.com/hashicorp/packer/packer/plugin-getter"
    20  	"github.com/hashicorp/packer/packer/plugin-getter/github"
    21  	"github.com/hashicorp/packer/version"
    22  	"github.com/posener/complete"
    23  )
    24  
    25  type InitCommand struct {
    26  	Meta
    27  }
    28  
    29  func (c *InitCommand) Run(args []string) int {
    30  	ctx, cleanup := handleTermInterrupt(c.Ui)
    31  	defer cleanup()
    32  
    33  	cfg, ret := c.ParseArgs(args)
    34  	if ret != 0 {
    35  		return ret
    36  	}
    37  
    38  	return c.RunContext(ctx, cfg)
    39  }
    40  
    41  func (c *InitCommand) ParseArgs(args []string) (*InitArgs, int) {
    42  	var cfg InitArgs
    43  	flags := c.Meta.FlagSet("init")
    44  	flags.Usage = func() { c.Ui.Say(c.Help()) }
    45  	cfg.AddFlagSets(flags)
    46  	if err := flags.Parse(args); err != nil {
    47  		return &cfg, 1
    48  	}
    49  
    50  	args = flags.Args()
    51  	if len(args) != 1 {
    52  		flags.Usage()
    53  		return &cfg, 1
    54  	}
    55  	cfg.Path = args[0]
    56  	return &cfg, 0
    57  }
    58  
    59  func (c *InitCommand) RunContext(buildCtx context.Context, cla *InitArgs) int {
    60  	packerStarter, ret := c.GetConfig(&cla.MetaArgs)
    61  	if ret != 0 {
    62  		return ret
    63  	}
    64  
    65  	// Get plugins requirements
    66  	reqs, diags := packerStarter.PluginRequirements()
    67  	ret = writeDiags(c.Ui, nil, diags)
    68  	if ret != 0 {
    69  		return ret
    70  	}
    71  
    72  	if len(reqs) == 0 {
    73  		c.Ui.Message(`
    74  No plugins requirement found, make sure you reference a Packer config
    75  containing a packer.required_plugins block. See
    76  https://www.packer.io/docs/templates/hcl_templates/blocks/packer
    77  for more info.`)
    78  	}
    79  
    80  	opts := plugingetter.ListInstallationsOptions{
    81  		PluginDirectory: c.Meta.CoreConfig.Components.PluginConfig.PluginDirectory,
    82  		BinaryInstallationOptions: plugingetter.BinaryInstallationOptions{
    83  			OS:              runtime.GOOS,
    84  			ARCH:            runtime.GOARCH,
    85  			APIVersionMajor: pluginsdk.APIVersionMajor,
    86  			APIVersionMinor: pluginsdk.APIVersionMinor,
    87  			Checksummers: []plugingetter.Checksummer{
    88  				{Type: "sha256", Hash: sha256.New()},
    89  			},
    90  			ReleasesOnly: true,
    91  		},
    92  	}
    93  
    94  	if runtime.GOOS == "windows" && opts.Ext == "" {
    95  		opts.BinaryInstallationOptions.Ext = ".exe"
    96  	}
    97  
    98  	log.Printf("[TRACE] init: %#v", opts)
    99  
   100  	// the ordering of the getters is important here, place the getter on top which you want to try first
   101  	getters := []plugingetter.Getter{
   102  		&release.Getter{
   103  			Name: "releases.hashicorp.com",
   104  		},
   105  		&github.Getter{
   106  			// In the past some terraform plugins downloads were blocked from a
   107  			// specific aws region by s3. Changing the user agent unblocked the
   108  			// downloads so having one user agent per version will help mitigate
   109  			// that a little more. Especially in the case someone forks this
   110  			// code to make it more aggressive or something.
   111  			// TODO: allow to set this from the config file or an environment
   112  			// variable.
   113  			UserAgent: "packer-getter-github-" + version.String(),
   114  			Name:      "github.com",
   115  		},
   116  	}
   117  
   118  	ui := &packer.ColoredUi{
   119  		Color: packer.UiColorCyan,
   120  		Ui:    c.Ui,
   121  	}
   122  
   123  	for _, pluginRequirement := range reqs {
   124  		// Get installed plugins that match requirement
   125  
   126  		installs, err := pluginRequirement.ListInstallations(opts)
   127  		if err != nil {
   128  			c.Ui.Error(err.Error())
   129  			return 1
   130  		}
   131  
   132  		if len(installs) > 0 {
   133  			if !cla.Force && !cla.Upgrade {
   134  				continue
   135  			}
   136  
   137  			if cla.Force && !cla.Upgrade {
   138  				// Only place another constaint to the latest release
   139  				// binary, if any, otherwise this is essentially the same
   140  				// as an upgrade
   141  				var installVersion string
   142  				for _, install := range installs {
   143  					ver, _ := gversion.NewVersion(install.Version)
   144  					if ver.Prerelease() == "" {
   145  						installVersion = install.Version
   146  					}
   147  				}
   148  
   149  				if installVersion != "" {
   150  					pluginRequirement.VersionConstraints, _ = gversion.NewConstraint(fmt.Sprintf("=%s", installVersion))
   151  				}
   152  			}
   153  		}
   154  
   155  		newInstall, err := pluginRequirement.InstallLatest(plugingetter.InstallOptions{
   156  			PluginDirectory:           opts.PluginDirectory,
   157  			BinaryInstallationOptions: opts.BinaryInstallationOptions,
   158  			Getters:                   getters,
   159  			Force:                     cla.Force,
   160  		})
   161  		if err != nil {
   162  			c.Ui.Error(fmt.Sprintf("Failed getting the %q plugin:", pluginRequirement.Identifier))
   163  			c.Ui.Error(err.Error())
   164  			ret = 1
   165  		}
   166  		if newInstall != nil {
   167  			msg := fmt.Sprintf("Installed plugin %s %s in %q", pluginRequirement.Identifier, newInstall.Version, newInstall.BinaryPath)
   168  			ui.Say(msg)
   169  		}
   170  	}
   171  	return ret
   172  }
   173  
   174  func (*InitCommand) Help() string {
   175  	helpText := `
   176  Usage: packer init [options] TEMPLATE
   177  
   178    Install all the missing plugins required in a Packer config. Note that Packer
   179    does not have a state.
   180  
   181    This is the first command that should be executed when working with a new
   182    or existing template.
   183  
   184    This command is always safe to run multiple times. Though subsequent runs may
   185    give errors, this command will never delete anything.
   186  
   187  Options:
   188    -upgrade                     On top of installing missing plugins, update
   189                                 installed plugins to the latest available
   190                                 version, if there is a new higher one. Note that
   191                                 this still takes into consideration the version
   192                                 constraint of the config.
   193    -force                       Forces reinstallation of plugins, even if already
   194                                 installed.
   195  `
   196  
   197  	return strings.TrimSpace(helpText)
   198  }
   199  
   200  func (*InitCommand) Synopsis() string {
   201  	return "Install missing plugins or upgrade plugins"
   202  }
   203  
   204  func (*InitCommand) AutocompleteArgs() complete.Predictor {
   205  	return complete.PredictNothing
   206  }
   207  
   208  func (*InitCommand) AutocompleteFlags() complete.Flags {
   209  	return complete.Flags{
   210  		"-upgrade": complete.PredictNothing,
   211  	}
   212  }