mirror of
https://github.com/helm/helm.git
synced 2026-05-28 04:35:48 -04:00
feat: Added multi-platform plugin hook support
Signed-off-by: Steve Hipwell <steve.hipwell@gmail.com>
This commit is contained in:
parent
7853d49071
commit
bb9f0f7816
6 changed files with 439 additions and 185 deletions
|
|
@ -47,19 +47,27 @@ func newPluginCmd(out io.Writer) *cobra.Command {
|
|||
|
||||
// runHook will execute a plugin hook.
|
||||
func runHook(p *plugin.Plugin, event string) error {
|
||||
hook := p.Metadata.Hooks[event]
|
||||
if hook == "" {
|
||||
plugin.SetupPluginEnv(settings, p.Metadata.Name, p.Dir)
|
||||
|
||||
cmds := p.Metadata.PlatformHooks[event]
|
||||
expandArgs := true
|
||||
if len(cmds) == 0 && len(p.Metadata.Hooks) > 0 {
|
||||
cmd := p.Metadata.Hooks[event]
|
||||
if len(cmd) > 0 {
|
||||
cmds = []plugin.PlatformCommand{{Command: "sh", Args: []string{"-c", cmd}}}
|
||||
expandArgs = false
|
||||
}
|
||||
}
|
||||
|
||||
main, argv, err := plugin.PrepareCommands(cmds, expandArgs, []string{})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
prog := exec.Command("sh", "-c", hook)
|
||||
// TODO make this work on windows
|
||||
// I think its ... ¯\_(ツ)_/¯
|
||||
// prog := exec.Command("cmd", "/C", p.Metadata.Hooks.Install())
|
||||
prog := exec.Command(main, argv...)
|
||||
|
||||
debug("running %s hook: %s", event, prog)
|
||||
|
||||
plugin.SetupPluginEnv(settings, p.Metadata.Name, p.Dir)
|
||||
prog.Stdout, prog.Stderr = os.Stdout, os.Stderr
|
||||
if err := prog.Run(); err != nil {
|
||||
if eerr, ok := err.(*exec.ExitError); ok {
|
||||
|
|
|
|||
|
|
@ -25,5 +25,8 @@ const (
|
|||
Update = "update"
|
||||
)
|
||||
|
||||
// PlatformHooks is a map of events to a command for a particular operating system and architecture.
|
||||
type PlatformHooks map[string][]PlatformCommand
|
||||
|
||||
// Hooks is a map of events to commands.
|
||||
type Hooks map[string]string
|
||||
|
|
|
|||
|
|
@ -44,9 +44,10 @@ type Downloaders struct {
|
|||
|
||||
// PlatformCommand represents a command for a particular operating system and architecture
|
||||
type PlatformCommand struct {
|
||||
OperatingSystem string `json:"os"`
|
||||
Architecture string `json:"arch"`
|
||||
Command string `json:"command"`
|
||||
OperatingSystem string `json:"os"`
|
||||
Architecture string `json:"arch"`
|
||||
Command string `json:"command"`
|
||||
Args []string `json:"args"`
|
||||
}
|
||||
|
||||
// Metadata describes a plugin.
|
||||
|
|
@ -65,7 +66,25 @@ type Metadata struct {
|
|||
// Description is a long description shown in places like `helm help`
|
||||
Description string `json:"description"`
|
||||
|
||||
// Command is the command, as a single string.
|
||||
// PlatformCommand is the plugin command, with a platform selector and support for args.
|
||||
//
|
||||
// The command and args will be passed through environment expansion, so env vars can
|
||||
// be present in this command. Unless IgnoreFlags is set, this will
|
||||
// also merge the flags passed from Helm.
|
||||
//
|
||||
// Note that the command is not executed in a shell. To do so, we suggest
|
||||
// pointing the command to a shell script.
|
||||
//
|
||||
// The following rules will apply to processing platform commands:
|
||||
// - If PlatformCommand is present, it will be used
|
||||
// - If both OS and Arch match the current platform, search will stop and the command will be executed
|
||||
// - If OS matches and Arch is empty, the command will be executed
|
||||
// - If no OS/Arch match is found, the default command will be executed
|
||||
// - If no matches are found in platformCommand, Helm will exit with an error
|
||||
PlatformCommand []PlatformCommand `json:"platformCommand"`
|
||||
|
||||
// Command is the plugin command, as a single string.
|
||||
// Providing a command will result in an error if PlatformCommand is also set.
|
||||
//
|
||||
// The command will be passed through environment expansion, so env vars can
|
||||
// be present in this command. Unless IgnoreFlags is set, this will
|
||||
|
|
@ -74,14 +93,8 @@ type Metadata struct {
|
|||
// Note that command is not executed in a shell. To do so, we suggest
|
||||
// pointing the command to a shell script.
|
||||
//
|
||||
// The following rules will apply to processing commands:
|
||||
// - If platformCommand is present, it will be searched first
|
||||
// - If both OS and Arch match the current platform, search will stop and the command will be executed
|
||||
// - If OS matches and there is no more specific match, the command will be executed
|
||||
// - If no OS/Arch match is found, the default command will be executed
|
||||
// - If no command is present and no matches are found in platformCommand, Helm will exit with an error
|
||||
PlatformCommand []PlatformCommand `json:"platformCommand"`
|
||||
Command string `json:"command"`
|
||||
// DEPRECATED: Use PlatformCommand instead. Remove in Helm 4.
|
||||
Command string `json:"command"`
|
||||
|
||||
// IgnoreFlags ignores any flags passed in from Helm
|
||||
//
|
||||
|
|
@ -90,7 +103,31 @@ type Metadata struct {
|
|||
// the `--debug` flag will be discarded.
|
||||
IgnoreFlags bool `json:"ignoreFlags"`
|
||||
|
||||
// Hooks are commands that will run on events.
|
||||
// PlatformHooks are commands that will run on plugin events, with a platform selector and support for args.
|
||||
//
|
||||
// The command and args will be passed through environment expansion, so env vars can
|
||||
// be present in the command.
|
||||
//
|
||||
// Note that the command is not executed in a shell. To do so, we suggest
|
||||
// pointing the command to a shell script.
|
||||
//
|
||||
// The following rules will apply to processing platform hooks:
|
||||
// - If PlatformHooks is present, it will be used
|
||||
// - If both OS and Arch match the current platform, search will stop and the command will be executed
|
||||
// - If OS matches and Arch is empty, the command will be executed
|
||||
// - If no OS/Arch match is found, the default command will be executed
|
||||
// - If no matches are found in platformHooks, Helm will skip the event
|
||||
PlatformHooks PlatformHooks `json:"platformHooks"`
|
||||
|
||||
// Hooks are commands that will run on plugin events, as a single string.
|
||||
// Providing a hooks will result in an error if PlatformHooks is also set.
|
||||
//
|
||||
// The command will be passed through environment expansion, so env vars can
|
||||
// be present in this command.
|
||||
//
|
||||
// Note that the command is executed in the sh shell.
|
||||
//
|
||||
// DEPRECATED: Use PlatformHooks instead. Remove in Helm 4.
|
||||
Hooks Hooks
|
||||
|
||||
// Downloaders field is used if the plugin supply downloader mechanism
|
||||
|
|
@ -112,62 +149,106 @@ type Plugin struct {
|
|||
Dir string
|
||||
}
|
||||
|
||||
// The following rules will apply to processing the Plugin.PlatformCommand.Command:
|
||||
// - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution
|
||||
// - If OS matches and there is no more specific match, the command will be prepared for execution
|
||||
// - If no OS/Arch match is found, return nil
|
||||
func getPlatformCommand(cmds []PlatformCommand) []string {
|
||||
var command []string
|
||||
// Returns command and args strings based on the following rules in priority order:
|
||||
// - From the PlatformCommand where OS and Arch match the current platform
|
||||
// - From the PlatformCommand where OS matches the current platform and Arch is empty/unspecified
|
||||
// - From the PlatformCommand where OS is empty/unspecified and Arch matches the current platform
|
||||
// - From the PlatformCommand where OS and Arch are both empty/unspecified
|
||||
// - Return nil, nil
|
||||
func getPlatformCommand(cmds []PlatformCommand) ([]string, []string) {
|
||||
var command, args []string
|
||||
found := false
|
||||
foundOs := false
|
||||
|
||||
eq := strings.EqualFold
|
||||
for _, c := range cmds {
|
||||
if eq(c.OperatingSystem, runtime.GOOS) {
|
||||
command = strings.Split(c.Command, " ")
|
||||
}
|
||||
if eq(c.OperatingSystem, runtime.GOOS) && eq(c.Architecture, runtime.GOARCH) {
|
||||
return strings.Split(c.Command, " ")
|
||||
// Return early for an exact match
|
||||
return strings.Split(c.Command, " "), c.Args
|
||||
}
|
||||
|
||||
if (len(c.OperatingSystem) > 0 && !eq(c.OperatingSystem, runtime.GOOS)) || len(c.Architecture) > 0 {
|
||||
// Skip if OS is not empty and doesn't match or if arch is set as a set arch requires an OS match
|
||||
continue
|
||||
}
|
||||
|
||||
if !foundOs && len(c.OperatingSystem) > 0 && eq(c.OperatingSystem, runtime.GOOS) {
|
||||
// First OS match with empty arch, can only be overridden by a direct match
|
||||
command = strings.Split(c.Command, " ")
|
||||
args = c.Args
|
||||
found = true
|
||||
foundOs = true
|
||||
} else if !found {
|
||||
// First empty match, can be overridden by a direct match or an OS match
|
||||
command = strings.Split(c.Command, " ")
|
||||
args = c.Args
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return command
|
||||
|
||||
return command, args
|
||||
}
|
||||
|
||||
// PrepareCommand takes a Plugin.PlatformCommand.Command, a Plugin.Command and will applying the following processing:
|
||||
// - If platformCommand is present, it will be searched first
|
||||
// - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution
|
||||
// - If OS matches and there is no more specific match, the command will be prepared for execution
|
||||
// - If no OS/Arch match is found, the default command will be prepared for execution
|
||||
// - If no command is present and no matches are found in platformCommand, will exit with an error
|
||||
// PrepareCommands takes a []Plugin.PlatformCommand
|
||||
// and prepares the command and arguments for execution.
|
||||
//
|
||||
// It merges extraArgs into any arguments supplied in the plugin. It
|
||||
// returns the name of the command and an args array.
|
||||
// returns the main command and an args array.
|
||||
//
|
||||
// The result is suitable to pass to exec.Command.
|
||||
func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) {
|
||||
var parts []string
|
||||
platCmdLen := len(p.Metadata.PlatformCommand)
|
||||
if platCmdLen > 0 {
|
||||
parts = getPlatformCommand(p.Metadata.PlatformCommand)
|
||||
}
|
||||
if platCmdLen == 0 || parts == nil {
|
||||
parts = strings.Split(p.Metadata.Command, " ")
|
||||
}
|
||||
if len(parts) == 0 || parts[0] == "" {
|
||||
func PrepareCommands(cmds []PlatformCommand, expandArgs bool, extraArgs []string) (string, []string, error) {
|
||||
cmdParts, args := getPlatformCommand(cmds)
|
||||
if len(cmdParts) == 0 || cmdParts[0] == "" {
|
||||
return "", nil, fmt.Errorf("no plugin command is applicable")
|
||||
}
|
||||
|
||||
main := os.ExpandEnv(parts[0])
|
||||
main := os.ExpandEnv(cmdParts[0])
|
||||
baseArgs := []string{}
|
||||
if len(parts) > 1 {
|
||||
for _, cmdpart := range parts[1:] {
|
||||
cmdexp := os.ExpandEnv(cmdpart)
|
||||
baseArgs = append(baseArgs, cmdexp)
|
||||
if len(cmdParts) > 1 {
|
||||
for _, cmdPart := range cmdParts[1:] {
|
||||
if expandArgs {
|
||||
baseArgs = append(baseArgs, os.ExpandEnv(cmdPart))
|
||||
} else {
|
||||
baseArgs = append(baseArgs, cmdPart)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !p.Metadata.IgnoreFlags {
|
||||
|
||||
for _, arg := range args {
|
||||
if expandArgs {
|
||||
baseArgs = append(baseArgs, os.ExpandEnv(arg))
|
||||
} else {
|
||||
baseArgs = append(baseArgs, arg)
|
||||
}
|
||||
}
|
||||
|
||||
if len(extraArgs) > 0 {
|
||||
baseArgs = append(baseArgs, extraArgs...)
|
||||
}
|
||||
|
||||
return main, baseArgs, nil
|
||||
}
|
||||
|
||||
// PrepareCommand gets the correct command and arguments for a plugin.
|
||||
//
|
||||
// It merges extraArgs into any arguments supplied in the plugin. It returns the name of the command and an args array.
|
||||
//
|
||||
// The result is suitable to pass to exec.Command.
|
||||
func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) {
|
||||
var extraArgsIn []string
|
||||
|
||||
if !p.Metadata.IgnoreFlags {
|
||||
extraArgsIn = extraArgs
|
||||
}
|
||||
|
||||
cmds := p.Metadata.PlatformCommand
|
||||
if len(cmds) == 0 && len(p.Metadata.Command) > 0 {
|
||||
cmds = []PlatformCommand{{Command: p.Metadata.Command}}
|
||||
}
|
||||
|
||||
return PrepareCommands(cmds, true, extraArgsIn)
|
||||
}
|
||||
|
||||
// validPluginName is a regular expression that validates plugin names.
|
||||
//
|
||||
// Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, _ and -.
|
||||
|
|
@ -184,6 +265,14 @@ func validatePluginData(plug *Plugin, filepath string) error {
|
|||
}
|
||||
plug.Metadata.Usage = sanitizeString(plug.Metadata.Usage)
|
||||
|
||||
if len(plug.Metadata.PlatformCommand) > 0 && len(plug.Metadata.Command) > 0 {
|
||||
return fmt.Errorf("both platformCommand and command are set in %q", filepath)
|
||||
}
|
||||
|
||||
if len(plug.Metadata.PlatformHooks) > 0 && len(plug.Metadata.Hooks) > 0 {
|
||||
return fmt.Errorf("both platformHooks and hooks are set in %q", filepath)
|
||||
}
|
||||
|
||||
// We could also validate SemVer, executable, and other fields should we so choose.
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,163 +26,256 @@ import (
|
|||
"helm.sh/helm/v3/pkg/cli"
|
||||
)
|
||||
|
||||
func checkCommand(p *Plugin, extraArgs []string, osStrCmp string, t *testing.T) {
|
||||
func TestPrepareCommand(t *testing.T) {
|
||||
cmdMain := "sh"
|
||||
cmdArgs := []string{"-c", "echo \"test\""}
|
||||
|
||||
p := &Plugin{
|
||||
Dir: "/tmp", // Unused
|
||||
Metadata: &Metadata{
|
||||
Name: "test",
|
||||
Command: "echo \"error\"",
|
||||
PlatformCommand: []PlatformCommand{
|
||||
{OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: runtime.GOARCH, Command: cmdMain, Args: cmdArgs},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cmd, args, err := p.PrepareCommand([]string{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmd != cmdMain {
|
||||
t.Fatalf("Expected %q, got %q", cmdMain, cmd)
|
||||
}
|
||||
if !reflect.DeepEqual(args, cmdArgs) {
|
||||
t.Fatalf("Expected %v, got %v", cmdArgs, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareCommandExtraArgs(t *testing.T) {
|
||||
cmdMain := "sh"
|
||||
cmdArgs := []string{"-c", "echo \"test\""}
|
||||
extraArgs := []string{"--debug", "--foo", "bar"}
|
||||
|
||||
p := &Plugin{
|
||||
Dir: "/tmp", // Unused
|
||||
Metadata: &Metadata{
|
||||
Name: "test",
|
||||
Command: "echo \"error\"",
|
||||
PlatformCommand: []PlatformCommand{
|
||||
{OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: runtime.GOARCH, Command: cmdMain, Args: cmdArgs},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expectedArgs := append(cmdArgs, extraArgs...)
|
||||
|
||||
cmd, args, err := p.PrepareCommand(extraArgs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmd != "echo" {
|
||||
t.Fatalf("Expected echo, got %q", cmd)
|
||||
if cmd != cmdMain {
|
||||
t.Fatalf("Expected %q, got %q", cmdMain, cmd)
|
||||
}
|
||||
if !reflect.DeepEqual(args, expectedArgs) {
|
||||
t.Fatalf("Expected %v, got %v", expectedArgs, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareCommandExtraArgsIgnored(t *testing.T) {
|
||||
cmdMain := "sh"
|
||||
cmdArgs := []string{"-c", "echo \"test\""}
|
||||
extraArgs := []string{"--debug", "--foo", "bar"}
|
||||
|
||||
p := &Plugin{
|
||||
Dir: "/tmp", // Unused
|
||||
Metadata: &Metadata{
|
||||
Name: "test",
|
||||
Command: "echo \"error\"",
|
||||
PlatformCommand: []PlatformCommand{
|
||||
{OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: runtime.GOARCH, Command: cmdMain, Args: cmdArgs},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
},
|
||||
IgnoreFlags: true,
|
||||
},
|
||||
}
|
||||
|
||||
if l := len(args); l != 5 {
|
||||
t.Fatalf("expected 5 args, got %d", l)
|
||||
}
|
||||
|
||||
expect := []string{"-n", osStrCmp, "--debug", "--foo", "bar"}
|
||||
for i := 0; i < len(args); i++ {
|
||||
if expect[i] != args[i] {
|
||||
t.Errorf("Expected arg=%q, got %q", expect[i], args[i])
|
||||
}
|
||||
}
|
||||
|
||||
// Test with IgnoreFlags. This should omit --debug, --foo, bar
|
||||
p.Metadata.IgnoreFlags = true
|
||||
cmd, args, err = p.PrepareCommand(extraArgs)
|
||||
cmd, args, err := p.PrepareCommand(extraArgs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmd != "echo" {
|
||||
t.Fatalf("Expected echo, got %q", cmd)
|
||||
if cmd != cmdMain {
|
||||
t.Fatalf("Expected %q, got %q", cmdMain, cmd)
|
||||
}
|
||||
if l := len(args); l != 2 {
|
||||
t.Fatalf("expected 2 args, got %d", l)
|
||||
}
|
||||
expect = []string{"-n", osStrCmp}
|
||||
for i := 0; i < len(args); i++ {
|
||||
if expect[i] != args[i] {
|
||||
t.Errorf("Expected arg=%q, got %q", expect[i], args[i])
|
||||
}
|
||||
if !reflect.DeepEqual(args, cmdArgs) {
|
||||
t.Fatalf("Expected %v, got %v", cmdArgs, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareCommand(t *testing.T) {
|
||||
p := &Plugin{
|
||||
Dir: "/tmp", // Unused
|
||||
Metadata: &Metadata{
|
||||
Name: "test",
|
||||
Command: "echo -n foo",
|
||||
},
|
||||
}
|
||||
argv := []string{"--debug", "--foo", "bar"}
|
||||
func TestPrepareCommands(t *testing.T) {
|
||||
cmdMain := "sh"
|
||||
cmdArgs := []string{"-c", "echo \"test\""}
|
||||
|
||||
checkCommand(p, argv, "foo", t)
|
||||
cmds := []PlatformCommand{
|
||||
{OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: runtime.GOARCH, Command: cmdMain, Args: cmdArgs},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
}
|
||||
|
||||
cmd, args, err := PrepareCommands(cmds, true, []string{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmd != cmdMain {
|
||||
t.Fatalf("Expected %q, got %q", cmdMain, cmd)
|
||||
}
|
||||
if !reflect.DeepEqual(args, cmdArgs) {
|
||||
t.Fatalf("Expected %v, got %v", cmdArgs, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlatformPrepareCommand(t *testing.T) {
|
||||
p := &Plugin{
|
||||
Dir: "/tmp", // Unused
|
||||
Metadata: &Metadata{
|
||||
Name: "test",
|
||||
Command: "echo -n os-arch",
|
||||
PlatformCommand: []PlatformCommand{
|
||||
{OperatingSystem: "linux", Architecture: "386", Command: "echo -n linux-386"},
|
||||
{OperatingSystem: "linux", Architecture: "amd64", Command: "echo -n linux-amd64"},
|
||||
{OperatingSystem: "linux", Architecture: "arm64", Command: "echo -n linux-arm64"},
|
||||
{OperatingSystem: "linux", Architecture: "ppc64le", Command: "echo -n linux-ppc64le"},
|
||||
{OperatingSystem: "linux", Architecture: "s390x", Command: "echo -n linux-s390x"},
|
||||
{OperatingSystem: "linux", Architecture: "riscv64", Command: "echo -n linux-riscv64"},
|
||||
{OperatingSystem: "linux", Architecture: "loong64", Command: "echo -n linux-loong64"},
|
||||
{OperatingSystem: "windows", Architecture: "amd64", Command: "echo -n win-64"},
|
||||
},
|
||||
},
|
||||
}
|
||||
var osStrCmp string
|
||||
os := runtime.GOOS
|
||||
arch := runtime.GOARCH
|
||||
if os == "linux" && arch == "386" {
|
||||
osStrCmp = "linux-386"
|
||||
} else if os == "linux" && arch == "amd64" {
|
||||
osStrCmp = "linux-amd64"
|
||||
} else if os == "linux" && arch == "arm64" {
|
||||
osStrCmp = "linux-arm64"
|
||||
} else if os == "linux" && arch == "ppc64le" {
|
||||
osStrCmp = "linux-ppc64le"
|
||||
} else if os == "linux" && arch == "s390x" {
|
||||
osStrCmp = "linux-s390x"
|
||||
} else if os == "linux" && arch == "riscv64" {
|
||||
osStrCmp = "linux-riscv64"
|
||||
} else if os == "linux" && arch == "loong64" {
|
||||
osStrCmp = "linux-loong64"
|
||||
} else if os == "windows" && arch == "amd64" {
|
||||
osStrCmp = "win-64"
|
||||
} else {
|
||||
osStrCmp = "os-arch"
|
||||
func TestPrepareCommandsExtraArgs(t *testing.T) {
|
||||
cmdMain := "sh"
|
||||
cmdArgs := []string{"-c", "echo \"test\""}
|
||||
extraArgs := []string{"--debug", "--foo", "bar"}
|
||||
|
||||
cmds := []PlatformCommand{
|
||||
{OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: runtime.GOARCH, Command: "sh", Args: []string{"-c", "echo \"test\""}},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
}
|
||||
|
||||
argv := []string{"--debug", "--foo", "bar"}
|
||||
checkCommand(p, argv, osStrCmp, t)
|
||||
expectedArgs := append(cmdArgs, extraArgs...)
|
||||
|
||||
cmd, args, err := PrepareCommands(cmds, true, extraArgs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmd != cmdMain {
|
||||
t.Fatalf("Expected %q, got %q", cmdMain, cmd)
|
||||
}
|
||||
if !reflect.DeepEqual(args, expectedArgs) {
|
||||
t.Fatalf("Expected %v, got %v", expectedArgs, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPartialPlatformPrepareCommand(t *testing.T) {
|
||||
p := &Plugin{
|
||||
Dir: "/tmp", // Unused
|
||||
Metadata: &Metadata{
|
||||
Name: "test",
|
||||
Command: "echo -n os-arch",
|
||||
PlatformCommand: []PlatformCommand{
|
||||
{OperatingSystem: "linux", Architecture: "386", Command: "echo -n linux-386"},
|
||||
{OperatingSystem: "windows", Architecture: "amd64", Command: "echo -n win-64"},
|
||||
},
|
||||
},
|
||||
}
|
||||
var osStrCmp string
|
||||
os := runtime.GOOS
|
||||
arch := runtime.GOARCH
|
||||
if os == "linux" {
|
||||
osStrCmp = "linux-386"
|
||||
} else if os == "windows" && arch == "amd64" {
|
||||
osStrCmp = "win-64"
|
||||
} else {
|
||||
osStrCmp = "os-arch"
|
||||
func TestPrepareCommandsNoArch(t *testing.T) {
|
||||
cmdMain := "sh"
|
||||
cmdArgs := []string{"-c", "echo \"test\""}
|
||||
|
||||
cmds := []PlatformCommand{
|
||||
{OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: "", Command: "sh", Args: []string{"-c", "echo \"test\""}},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
}
|
||||
|
||||
argv := []string{"--debug", "--foo", "bar"}
|
||||
checkCommand(p, argv, osStrCmp, t)
|
||||
cmd, args, err := PrepareCommands(cmds, true, []string{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmd != cmdMain {
|
||||
t.Fatalf("Expected %q, got %q", cmdMain, cmd)
|
||||
}
|
||||
if !reflect.DeepEqual(args, cmdArgs) {
|
||||
t.Fatalf("Expected %v, got %v", cmdArgs, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoPrepareCommand(t *testing.T) {
|
||||
p := &Plugin{
|
||||
Dir: "/tmp", // Unused
|
||||
Metadata: &Metadata{
|
||||
Name: "test",
|
||||
},
|
||||
}
|
||||
argv := []string{"--debug", "--foo", "bar"}
|
||||
func TestPrepareCommandsNoOsNoArch(t *testing.T) {
|
||||
cmdMain := "sh"
|
||||
cmdArgs := []string{"-c", "echo \"test\""}
|
||||
|
||||
_, _, err := p.PrepareCommand(argv)
|
||||
if err == nil {
|
||||
cmds := []PlatformCommand{
|
||||
{OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
{OperatingSystem: "", Architecture: "", Command: "sh", Args: []string{"-c", "echo \"test\""}},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
|
||||
}
|
||||
|
||||
cmd, args, err := PrepareCommands(cmds, true, []string{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmd != cmdMain {
|
||||
t.Fatalf("Expected %q, got %q", cmdMain, cmd)
|
||||
}
|
||||
if !reflect.DeepEqual(args, cmdArgs) {
|
||||
t.Fatalf("Expected %v, got %v", cmdArgs, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareCommandsNoMatch(t *testing.T) {
|
||||
cmds := []PlatformCommand{
|
||||
{OperatingSystem: "no-os", Architecture: "no-arch", Command: "sh", Args: []string{"-c", "echo \"test\""}},
|
||||
{OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "sh", Args: []string{"-c", "echo \"test\""}},
|
||||
{OperatingSystem: "no-os", Architecture: runtime.GOARCH, Command: "sh", Args: []string{"-c", "echo \"test\""}},
|
||||
}
|
||||
|
||||
if _, _, err := PrepareCommands(cmds, true, []string{}); err == nil {
|
||||
t.Fatalf("Expected error to be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoMatchPrepareCommand(t *testing.T) {
|
||||
p := &Plugin{
|
||||
Dir: "/tmp", // Unused
|
||||
Metadata: &Metadata{
|
||||
Name: "test",
|
||||
PlatformCommand: []PlatformCommand{
|
||||
{OperatingSystem: "no-os", Architecture: "amd64", Command: "echo -n linux-386"},
|
||||
},
|
||||
},
|
||||
}
|
||||
argv := []string{"--debug", "--foo", "bar"}
|
||||
func TestPrepareCommandsNoCommands(t *testing.T) {
|
||||
cmds := []PlatformCommand{}
|
||||
|
||||
if _, _, err := p.PrepareCommand(argv); err == nil {
|
||||
if _, _, err := PrepareCommands(cmds, true, []string{}); err == nil {
|
||||
t.Fatalf("Expected error to be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareCommandsExpand(t *testing.T) {
|
||||
t.Setenv("TEST", "test")
|
||||
cmdMain := "sh"
|
||||
cmdArgs := []string{"-c", "echo \"${TEST}\""}
|
||||
cmds := []PlatformCommand{
|
||||
{OperatingSystem: "", Architecture: "", Command: cmdMain, Args: cmdArgs},
|
||||
}
|
||||
|
||||
expectedArgs := []string{"-c", "echo \"test\""}
|
||||
|
||||
cmd, args, err := PrepareCommands(cmds, true, []string{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmd != cmdMain {
|
||||
t.Fatalf("Expected %q, got %q", cmdMain, cmd)
|
||||
}
|
||||
if !reflect.DeepEqual(args, expectedArgs) {
|
||||
t.Fatalf("Expected %v, got %v", expectedArgs, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareCommandsNoExpand(t *testing.T) {
|
||||
t.Setenv("TEST", "test")
|
||||
cmdMain := "sh"
|
||||
cmdArgs := []string{"-c", "echo \"${TEST}\""}
|
||||
cmds := []PlatformCommand{
|
||||
{OperatingSystem: "", Architecture: "", Command: cmdMain, Args: cmdArgs},
|
||||
}
|
||||
|
||||
cmd, args, err := PrepareCommands(cmds, false, []string{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmd != cmdMain {
|
||||
t.Fatalf("Expected %q, got %q", cmdMain, cmd)
|
||||
}
|
||||
if !reflect.DeepEqual(args, cmdArgs) {
|
||||
t.Fatalf("Expected %v, got %v", cmdArgs, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDir(t *testing.T) {
|
||||
dirname := "testdata/plugdir/good/hello"
|
||||
plug, err := LoadDir(dirname)
|
||||
|
|
@ -199,10 +292,16 @@ func TestLoadDir(t *testing.T) {
|
|||
Version: "0.1.0",
|
||||
Usage: "usage",
|
||||
Description: "description",
|
||||
Command: "$HELM_PLUGIN_DIR/hello.sh",
|
||||
PlatformCommand: []PlatformCommand{
|
||||
{OperatingSystem: "linux", Architecture: "", Command: "sh", Args: []string{"-c", "${HELM_PLUGIN_DIR}/hello.sh"}},
|
||||
{OperatingSystem: "windows", Architecture: "", Command: "pwsh", Args: []string{"-c", "${HELM_PLUGIN_DIR}/hello.ps1"}},
|
||||
},
|
||||
IgnoreFlags: true,
|
||||
Hooks: map[string]string{
|
||||
Install: "echo installing...",
|
||||
PlatformHooks: map[string][]PlatformCommand{
|
||||
Install: {
|
||||
{OperatingSystem: "linux", Architecture: "", Command: "sh", Args: []string{"-c", "echo \"installing...\""}},
|
||||
{OperatingSystem: "windows", Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"installing...\""}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -249,7 +348,6 @@ func TestDownloader(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLoadAll(t *testing.T) {
|
||||
|
||||
// Verify that empty dir loads:
|
||||
if plugs, err := LoadAll("testdata"); err != nil {
|
||||
t.Fatalf("error loading dir with no plugins: %s", err)
|
||||
|
|
@ -361,6 +459,30 @@ func TestValidatePluginData(t *testing.T) {
|
|||
Dir: "no-such-dir",
|
||||
}
|
||||
|
||||
// A mock plugin with no commands
|
||||
mockNoCommand := mockPlugin("foo")
|
||||
mockNoCommand.Metadata.PlatformCommand = []PlatformCommand{}
|
||||
mockNoCommand.Metadata.PlatformHooks = map[string][]PlatformCommand{}
|
||||
|
||||
// A mock plugin with legacy commands
|
||||
mockLegacyCommand := mockPlugin("foo")
|
||||
mockLegacyCommand.Metadata.PlatformCommand = []PlatformCommand{}
|
||||
mockLegacyCommand.Metadata.Command = "echo \"mock plugin\""
|
||||
mockLegacyCommand.Metadata.PlatformHooks = map[string][]PlatformCommand{}
|
||||
mockLegacyCommand.Metadata.Hooks = map[string]string{
|
||||
Install: "echo installing...",
|
||||
}
|
||||
|
||||
// A mock plugin with a command also set
|
||||
mockWithCommand := mockPlugin("foo")
|
||||
mockWithCommand.Metadata.Command = "echo \"mock plugin\""
|
||||
|
||||
// A mock plugin with a hooks also set
|
||||
mockWithHooks := mockPlugin("foo")
|
||||
mockWithHooks.Metadata.Hooks = map[string]string{
|
||||
Install: "echo installing...",
|
||||
}
|
||||
|
||||
for i, item := range []struct {
|
||||
pass bool
|
||||
plug *Plugin
|
||||
|
|
@ -372,6 +494,10 @@ func TestValidatePluginData(t *testing.T) {
|
|||
{false, mockPlugin("foo -bar ")}, // Test trailing chars
|
||||
{false, mockPlugin("foo\nbar")}, // Test newline
|
||||
{false, mockMissingMeta}, // Test if the metadata section missing
|
||||
{true, mockNoCommand}, // Test no command metadata works
|
||||
{true, mockLegacyCommand}, // Test legacy command metadata works
|
||||
{false, mockWithCommand}, // Test platformCommand and command both set fails
|
||||
{false, mockWithHooks}, // Test platformHooks and hooks both set fails
|
||||
} {
|
||||
err := validatePluginData(item.plug, fmt.Sprintf("test-%d", i))
|
||||
if item.pass && err != nil {
|
||||
|
|
@ -403,7 +529,16 @@ func mockPlugin(name string) *Plugin {
|
|||
Version: "v0.1.2",
|
||||
Usage: "Mock plugin",
|
||||
Description: "Mock plugin for testing",
|
||||
Command: "echo mock plugin",
|
||||
PlatformCommand: []PlatformCommand{
|
||||
{OperatingSystem: "linux", Architecture: "", Command: "sh", Args: []string{"-c", "echo \"mock plugin\""}},
|
||||
{OperatingSystem: "windows", Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"mock plugin\""}},
|
||||
},
|
||||
PlatformHooks: map[string][]PlatformCommand{
|
||||
Install: {
|
||||
{OperatingSystem: "linux", Architecture: "", Command: "sh", Args: []string{"-c", "echo \"installing...\""}},
|
||||
{OperatingSystem: "windows", Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"installing...\""}},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dir: "no-such-dir",
|
||||
}
|
||||
|
|
|
|||
3
pkg/plugin/testdata/plugdir/good/hello/hello.ps1
vendored
Normal file
3
pkg/plugin/testdata/plugdir/good/hello/hello.ps1
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env pwsh
|
||||
|
||||
Write-Host "Hello, world!"
|
||||
|
|
@ -3,7 +3,23 @@ version: "0.1.0"
|
|||
usage: "usage"
|
||||
description: |-
|
||||
description
|
||||
command: "$HELM_PLUGIN_DIR/hello.sh"
|
||||
platformCommand:
|
||||
- os: linux
|
||||
arch:
|
||||
command: "sh"
|
||||
args: ["-c", "${HELM_PLUGIN_DIR}/hello.sh"]
|
||||
- os: windows
|
||||
arch:
|
||||
command: "pwsh"
|
||||
args: ["-c", "${HELM_PLUGIN_DIR}/hello.ps1"]
|
||||
ignoreFlags: true
|
||||
hooks:
|
||||
install: "echo installing..."
|
||||
platformHooks:
|
||||
install:
|
||||
- os: linux
|
||||
arch: ""
|
||||
command: "sh"
|
||||
args: ["-c", 'echo "installing..."']
|
||||
- os: windows
|
||||
arch: ""
|
||||
command: "pwsh"
|
||||
args: ["-c", 'echo "installing..."']
|
||||
|
|
|
|||
Loading…
Reference in a new issue