Skip to content

Commit

Permalink
command: + -plugin-override opt to build/validate
Browse files Browse the repository at this point in the history
The plugin-override flag aims to forcefully load a plugin by passing in
its path, ignoring completely the plugins discovered before, or those in
the required_plugins block.
  • Loading branch information
lbajolet-hashicorp committed Oct 6, 2023
1 parent a1722ab commit 25b6e21
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 5 deletions.
9 changes: 8 additions & 1 deletion command/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,13 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, cla *BuildArgs) int
return ret
}

diags := packerStarter.DetectPluginBinaries()
diags := packerStarter.DetectPluginBinaries(cla.getIgnoredPluginAccessors())
ret = writeDiags(c.Ui, nil, diags)
if ret != 0 {
return ret
}

diags = c.Meta.ProcessOverrides(&cla.MetaArgs)
ret = writeDiags(c.Ui, nil, diags)
if ret != 0 {
return ret
Expand Down Expand Up @@ -411,6 +417,7 @@ Options:
-machine-readable Produce machine-readable output.
-on-error=[cleanup|abort|ask|run-cleanup-provisioner] If the build fails do: clean up (default), abort, ask, or run-cleanup-provisioner.
-parallel-builds=1 Number of builds to run in parallel. 1 disables parallelization. 0 means no limit (Default: 0)
-plugin-override Force loading a plugin from a binary. Format must be accessor=path (ex: -plugin-override "amazon=./path/to/plugin")
-timestamp-ui Enable prefixing of each ui output with an RFC3339 timestamp.
-var 'key=value' Variable for templates, can be used multiple times.
-var-file=path JSON or HCL2 file containing user variables, can be used multiple times.
Expand Down
27 changes: 27 additions & 0 deletions command/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func (ma *MetaArgs) AddFlagSets(fs *flag.FlagSet) {
fs.Var((*sliceflag.StringFlag)(&ma.Only), "only", "")
fs.Var((*sliceflag.StringFlag)(&ma.Except), "except", "")
fs.Var((*kvflag.Flag)(&ma.Vars), "var", "")
fs.Var((*kvflag.Flag)(&ma.PluginPathOverrides), "plugin-override", "use that to force loading a plugin from a binary. Format must be accessor=path")
fs.Var((*kvflag.StringSlice)(&ma.VarFiles), "var-file", "")
fs.Var(&ma.ConfigType, "config-type", "set to 'hcl2' to run in hcl2 mode when no file is passed.")
}
Expand All @@ -75,6 +76,32 @@ type MetaArgs struct {
// WarnOnUndeclared does not have a common default, as the default varies per sub-command usage.
// Refer to individual command FlagSets for usage.
WarnOnUndeclaredVar bool

// PluginPathOverrides is the list of plugins to use instead of the ones
// discovered manually.
//
// Each plugin specified here will have precedence over the plugins
// that are loaded normally by Packer.
PluginPathOverrides map[string]string
}

// getIgnoredPluginAccessors returns a list of accessors to not try to install
//
// This is mostly useful for commands like validate or build, which load the
// required plugins, and to not attempt to load/verify them if they are
// overridden by the command-line args
func (ma MetaArgs) getIgnoredPluginAccessors() []string {
overrides := len(ma.PluginPathOverrides)
if overrides == 0 {
return nil
}

ret := make([]string, 0, overrides)
for acc := range ma.PluginPathOverrides {
ret = append(ret, acc)
}

return ret
}

func (ba *BuildArgs) AddFlagSets(flags *flag.FlagSet) {
Expand Down
51 changes: 51 additions & 0 deletions command/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
"flag"
"fmt"
"io"
"log"
"os"
"regexp"
"strings"

"github.com/hashicorp/hcl/v2"
Expand Down Expand Up @@ -40,6 +42,55 @@ type Meta struct {
Version string
}

// markPluginAsNonBundled marks each plugin with the accessor as prefix as not bundled
//
// This is required as otherwise we may detect an overridden plugin as bundled,
// since the marking happens before we try to override them.
func markPluginAsNonBundled(pluginAccessor string) {
compoAcc := regexp.MustCompile(fmt.Sprintf(
"^packer-(builder|post-processor|provisioner|datasource)-%s",
pluginAccessor))
var toMarkAsNonBundled []string
for pluginComponent := range bundledStatus {
if compoAcc.MatchString(pluginComponent) {
toMarkAsNonBundled = append(toMarkAsNonBundled, pluginComponent)
}
}

for _, cmp := range toMarkAsNonBundled {
bundledStatus[cmp] = false
}
}

// ProcessOverrides takes a list of plugin overrides loaded from the
// command-line arguments
//
// Each override must point to a valid, existing binary, and will be
// used over any component previously loaded, either automatically, or
// through the `DetectPluginBinaries` function.
func (m *Meta) ProcessOverrides(cla *MetaArgs) hcl.Diagnostics {
log.Printf("Processing overrides")
var diags hcl.Diagnostics

for accessor, path := range cla.PluginPathOverrides {
log.Printf("Loading plugin %q from %q", accessor, path)
err := m.CoreConfig.Components.PluginConfig.DiscoverMultiPlugin(accessor, path)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "failed to locate overridden plugin",
Detail: fmt.Sprintf("The plugin %q could not be loaded from provided path %q", accessor, path),
})

continue
}

markPluginAsNonBundled(accessor)
}

return diags
}

// Core returns the core for the given template given the configured
// CoreConfig and user variables on this Meta.
func (m *Meta) Core(tpl *template.Template, cla *MetaArgs) (*packer.Core, error) {
Expand Down
9 changes: 8 additions & 1 deletion command/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@ func (c *ValidateCommand) RunContext(ctx context.Context, cla *ValidateArgs) int
return 0
}

diags := packerStarter.DetectPluginBinaries()
diags := packerStarter.DetectPluginBinaries(cla.getIgnoredPluginAccessors())
ret = writeDiags(c.Ui, nil, diags)
if ret != 0 {
return ret
}

diags = c.Meta.ProcessOverrides(&cla.MetaArgs)
ret = writeDiags(c.Ui, nil, diags)
if ret != 0 {
return ret
Expand Down Expand Up @@ -120,6 +126,7 @@ Options:
-var-file=path JSON or HCL2 file containing user variables, can be used multiple times.
-no-warn-undeclared-var Disable warnings for user variable files containing undeclared variables.
-evaluate-datasources Evaluate data sources during validation (HCL2 only, may incur costs); Defaults to false.
-plugin-override Force loading a plugin from a binary. Format must be accessor=path (ex: -plugin-override "amazon=./path/to/plugin")
`

return strings.TrimSpace(helpText)
Expand Down
18 changes: 17 additions & 1 deletion hcl2template/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,17 @@ func (cfg *PackerConfig) PluginRequirements() (plugingetter.Requirements, hcl.Di
return reqs, diags
}

func (cfg *PackerConfig) DetectPluginBinaries() hcl.Diagnostics {
func isIgnored(pluginAccessor string, ignoredAccessors []string) bool {
for _, acc := range ignoredAccessors {
if acc == pluginAccessor {
return true
}
}

return false
}

func (cfg *PackerConfig) DetectPluginBinaries(ignoreAccessors []string) hcl.Diagnostics {
opts := plugingetter.ListInstallationsOptions{
FromFolders: cfg.parser.PluginConfig.KnownPluginFolders,
BinaryInstallationOptions: plugingetter.BinaryInstallationOptions{
Expand All @@ -80,6 +90,12 @@ func (cfg *PackerConfig) DetectPluginBinaries() hcl.Diagnostics {
uninstalledPlugins := map[string]string{}

for _, pluginRequirement := range pluginReqs {
// If the plugin with the accessor is loaded directly, we won't
// try to load it from here.
if isIgnored(pluginRequirement.Accessor, ignoreAccessors) {
continue
}

sortedInstalls, err := pluginRequirement.ListInstallations(opts)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Expand Down
2 changes: 1 addition & 1 deletion packer/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func NewCore(c *CoreConfig) *Core {

// DetectPluginBinaries is used to load required plugins from the template,
// since it is unsupported in JSON, this is essentially a no-op.
func (c *Core) DetectPluginBinaries() hcl.Diagnostics {
func (c *Core) DetectPluginBinaries([]string) hcl.Diagnostics {
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion packer/run_interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type InitializeOptions struct {
type PluginBinaryDetector interface {
// DetectPluginBinaries is used only for HCL2 templates, and loads required
// plugins if specified.
DetectPluginBinaries() hcl.Diagnostics
DetectPluginBinaries(ignore []string) hcl.Diagnostics
}

// The Handler handles all Packer things. This interface reflects the Packer
Expand Down

0 comments on commit 25b6e21

Please sign in to comment.