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 }