From fef2ec6cffeb35d90f520168250ed38db351238c Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Mon, 11 Oct 2021 11:13:29 +0200 Subject: [PATCH] add pkr plugin check cmd from packer core (#85) This is simplified to not pull in core as a dep. The drawback is that we don't actually start plugins to verify that it works with Packer and has a ConfigSpec func. We do start a binary, though. But, I think this is fine since if a plugin implements describe then we can be pretty sure it uses the SDK's function and interfaces. Related to hashicorp/packer#11317 closes hashicorp/packer#11309 --- cmd/packer-sdc/internal/plugincheck/README.md | 5 + cmd/packer-sdc/internal/plugincheck/cmd.go | 117 ++++++++++++++++++ cmd/packer-sdc/main.go | 4 + 3 files changed, 126 insertions(+) create mode 100644 cmd/packer-sdc/internal/plugincheck/README.md create mode 100644 cmd/packer-sdc/internal/plugincheck/cmd.go diff --git a/cmd/packer-sdc/internal/plugincheck/README.md b/cmd/packer-sdc/internal/plugincheck/README.md new file mode 100644 index 000000000..839d2d417 --- /dev/null +++ b/cmd/packer-sdc/internal/plugincheck/README.md @@ -0,0 +1,5 @@ +## `plugin-check` + +`plugin-check` will check wether a plugin binary seems to work with packer. + +Use: `packer-sdc plugin-check packer-plugin-happy-cloud` diff --git a/cmd/packer-sdc/internal/plugincheck/cmd.go b/cmd/packer-sdc/internal/plugincheck/cmd.go new file mode 100644 index 000000000..82089d033 --- /dev/null +++ b/cmd/packer-sdc/internal/plugincheck/cmd.go @@ -0,0 +1,117 @@ +package plugincheck + +import ( + _ "embed" + "encoding/json" + "fmt" + "log" + "os/exec" + "path/filepath" + "strings" + + "github.com/pkg/errors" +) + +var ( + //go:embed README.md + readme string +) + +type Command struct { +} + +func (cmd *Command) Help() string { + return "\n" + readme +} + +func (cmd *Command) Run(args []string) int { + if err := cmd.run(args); err != nil { + log.Printf("%v", err) + return 1 + } + return 0 +} + +func (cmd *Command) run(args []string) error { + + if len(args) != 1 { + cmd.Help() + return errors.New("plugin-check requires a plugin binary name as an argument.\n" + + "ex: 'packer-plugin-happycloud'. Check will be run on the binary.") + } + + pluginName := args[0] + + if isOldPlugin(pluginName) { + fmt.Printf("\n[WARNING] Plugin is named with old prefix `packer-[builder|provisioner|post-processor]-{name})`. " + + "These will be detected but Packer cannot install them automatically. " + + "The plugin must be a multi-component plugin named packer-plugin-{name} to be installable through the `packer init` command.\n" + + "See docs at: https://www.packer.io/docs/plugins.\n") + return nil + } + + if err := checkPluginName(pluginName); err != nil { + return err + } + + path, err := filepath.Abs(pluginName) + if err != nil { + return err + } + + output, err := exec.Command(path, "describe").Output() + if err != nil { + return errors.Wrap(err, "failed to describe plugin") + } + + desc := pluginDescription{} + err = json.Unmarshal(output, &desc) + if err != nil { + return errors.Wrap(err, "failed to json.Unmarshal plugin description") + } + if len(desc.Version) == 0 { + return errors.New("Version needs to be set") + } + if len(desc.SDKVersion) == 0 { + return errors.New("SDKVersion needs to be set") + } + if len(desc.APIVersion) == 0 { + return errors.New("APIVersion needs to be set") + } + + if len(desc.Builders) == 0 && len(desc.PostProcessors) == 0 && len(desc.Datasources) == 0 { + return errors.New("this plugin defines no component.") + } + return nil +} + +type pluginDescription struct { + Version string `json:"version"` + SDKVersion string `json:"sdk_version"` + APIVersion string `json:"api_version"` + Builders []string `json:"builders"` + PostProcessors []string `json:"post_processors"` + Datasources []string `json:"datasources"` +} + +func isOldPlugin(pluginName string) bool { + return strings.HasPrefix(pluginName, "packer-builder-") || + strings.HasPrefix(pluginName, "packer-provisioner-") || + strings.HasPrefix(pluginName, "packer-post-processor-") +} + +// checkPluginName checks for the possible valid names for a plugin, +// packer-plugin-* or packer-[builder|provisioner|post-processor]-*. If the name +// is prefixed with `packer-[builder|provisioner|post-processor]-`, packer won't +// be able to install it, therefore a WARNING will be shown. +func checkPluginName(name string) error { + if strings.HasPrefix(name, "packer-plugin-") { + return nil + } + + return fmt.Errorf("plugin name is not valid") +} + +func (cmd *Command) Synopsis() string { + return "Tell wether a plugin release looks valid for Packer." +} diff --git a/cmd/packer-sdc/main.go b/cmd/packer-sdc/main.go index 76346cd33..266538470 100644 --- a/cmd/packer-sdc/main.go +++ b/cmd/packer-sdc/main.go @@ -6,6 +6,7 @@ import ( "os" mapstructure_to_hcl2 "github.com/hashicorp/packer-plugin-sdk/cmd/packer-sdc/internal/mapstructure-to-hcl2" + "github.com/hashicorp/packer-plugin-sdk/cmd/packer-sdc/internal/plugincheck" "github.com/hashicorp/packer-plugin-sdk/cmd/packer-sdc/internal/renderdocs" struct_markdown "github.com/hashicorp/packer-plugin-sdk/cmd/packer-sdc/internal/struct-markdown" "github.com/hashicorp/packer-plugin-sdk/version" @@ -37,6 +38,9 @@ func main() { "renderdocs": func() (cli.Command, error) { return &renderdocs.Command{}, nil }, + "plugin-check": func() (cli.Command, error) { + return &plugincheck.Command{}, nil + }, } exitStatus, err := c.Run()