feat: Added multi-platform plugin hook support

Signed-off-by: Steve Hipwell <steve.hipwell@gmail.com>
pull/12962/head
Steve Hipwell 3 months ago
parent 69636e6ac5
commit 5790a0f34b
No known key found for this signature in database

@ -47,19 +47,26 @@ 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 == "" {
var cmd string
var cmdArgs []string
plugin.SetupPluginEnv(settings, p.Metadata.Name, p.Dir)
command := p.Metadata.Hooks[event]
if command != "" {
cmd = "sh"
cmdArgs = []string{"-c", command}
}
main, argv, err := plugin.PrepareCommands(p.Metadata.PlatformHooks[event], cmd, cmdArgs, false, []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,23 +66,32 @@ 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 will be passed through environment expansion, so env vars can
// 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 command is not executed in a shell. To do so, we suggest
// 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 commands:
// - If platformCommand is present, it will be searched first
// The following rules will apply to processing platform 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"`
// Command is the plugin command, as a single string. The command will be ignored if a valid PlatformCommand is found.
//
// The command 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 command is not executed in a shell. To do so, we suggest
// pointing the command to a shell script.
Command string `json:"command"`
// IgnoreFlags ignores any flags passed in from Helm
//
@ -90,7 +100,28 @@ 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 commands:
// - If PlatformHooks 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 skip the event
PlatformHooks PlatformHooks `json:"platformHooks"`
// Hooks are commands that will run on plugin events, as a single string.
//
// 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.
Hooks Hooks
// Downloaders field is used if the plugin supply downloader mechanism
@ -116,21 +147,29 @@ type Plugin struct {
// - 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
func getPlatformCommand(cmds []PlatformCommand) ([]string, []string) {
var command, args []string
eq := strings.EqualFold
for _, c := range cmds {
if len(c.Architecture) > 0 && !eq(c.Architecture, runtime.GOARCH) {
continue
}
if eq(c.OperatingSystem, runtime.GOOS) {
command = strings.Split(c.Command, " ")
args = c.Args
}
if eq(c.OperatingSystem, runtime.GOOS) && eq(c.Architecture, runtime.GOARCH) {
return strings.Split(c.Command, " ")
return strings.Split(c.Command, " "), c.Args
}
}
return command
return command, args
}
// PrepareCommand takes a Plugin.PlatformCommand.Command, a Plugin.Command and will applying the following processing:
// PrepareCommands takes a []Plugin.PlatformCommand, 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
@ -141,33 +180,71 @@ func getPlatformCommand(cmds []PlatformCommand) []string {
// 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 parts []string
platCmdLen := len(p.Metadata.PlatformCommand)
if platCmdLen > 0 {
parts = getPlatformCommand(p.Metadata.PlatformCommand)
func PrepareCommands(cmds []PlatformCommand, legacyCmd string, legacyCmdArgs []string, expandLegacyCmdArgs bool, extraArgs []string) (string, []string, error) {
var cmdParts, args []string
expandArgs := true
cmdsLen := len(cmds)
if cmdsLen > 0 {
cmdParts, args = getPlatformCommand(cmds)
}
if platCmdLen == 0 || parts == nil {
parts = strings.Split(p.Metadata.Command, " ")
if cmdsLen == 0 || cmdParts == nil {
cmdParts = strings.Split(legacyCmd, " ")
args = legacyCmdArgs
expandArgs = expandLegacyCmdArgs
}
if len(parts) == 0 || parts[0] == "" {
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 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
//
// 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
}
return PrepareCommands(p.Metadata.PlatformCommand, p.Metadata.Command, []string{}, 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 -.

@ -21,165 +21,389 @@ import (
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"helm.sh/helm/v3/pkg/cli"
)
func checkCommand(p *Plugin, extraArgs []string, osStrCmp string, t *testing.T) {
cmd, args, err := p.PrepareCommand(extraArgs)
func TestPrepareCommand(t *testing.T) {
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: "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\""}},
},
},
}
expectedIndex := 1
cmd, args, err := p.PrepareCommand([]string{})
if err != nil {
t.Fatal(err)
}
if cmd != "echo" {
t.Fatalf("Expected echo, got %q", cmd)
if cmd != p.Metadata.PlatformCommand[expectedIndex].Command {
t.Fatalf("Expected %q, got %q", p.Metadata.PlatformCommand[expectedIndex].Command, cmd)
}
if l := len(args); l != 5 {
t.Fatalf("expected 5 args, got %d", l)
if !reflect.DeepEqual(args, p.Metadata.PlatformCommand[expectedIndex].Args) {
t.Fatalf("Expected %v, got %v", p.Metadata.PlatformCommand[expectedIndex].Args, args)
}
}
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])
}
func TestPrepareCommandExtraArgs(t *testing.T) {
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: "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\""}},
},
},
}
extraArgs := []string{"--debug", "--foo", "bar"}
expectedIndex := 1
resultArgs := append(p.Metadata.PlatformCommand[expectedIndex].Args, extraArgs...)
// 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 l := len(args); l != 2 {
t.Fatalf("expected 2 args, got %d", l)
if cmd != p.Metadata.PlatformCommand[expectedIndex].Command {
t.Fatalf("Expected %q, got %q", p.Metadata.PlatformCommand[expectedIndex].Command, cmd)
}
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, resultArgs) {
t.Fatalf("Expected %v, got %v", resultArgs, args)
}
}
func TestPrepareCommand(t *testing.T) {
func TestPrepareCommandExtraArgsIgnored(t *testing.T) {
p := &Plugin{
Dir: "/tmp", // Unused
Metadata: &Metadata{
Name: "test",
Command: "echo -n foo",
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: "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\""}},
},
IgnoreFlags: true,
},
}
argv := []string{"--debug", "--foo", "bar"}
extraArgs := []string{"--debug", "--foo", "bar"}
expectedIndex := 1
checkCommand(p, argv, "foo", t)
cmd, args, err := p.PrepareCommand(extraArgs)
if err != nil {
t.Fatal(err)
}
if cmd != p.Metadata.PlatformCommand[expectedIndex].Command {
t.Fatalf("Expected %q, got %q", p.Metadata.PlatformCommand[expectedIndex].Command, cmd)
}
if !reflect.DeepEqual(args, p.Metadata.PlatformCommand[expectedIndex].Args) {
t.Fatalf("Expected %v, got %v", p.Metadata.PlatformCommand[expectedIndex].Args, args)
}
}
func TestPlatformPrepareCommand(t *testing.T) {
func TestPrepareCommandNoArgs(t *testing.T) {
command := "echo"
commandArgs := []string{"-n", "\"test\""}
p := &Plugin{
Dir: "/tmp", // Unused
Metadata: &Metadata{
Name: "test",
Command: "echo -n os-arch",
Command: "echo \"error\"",
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: "windows", Architecture: "amd64", Command: "echo -n win-64"},
{OperatingSystem: "no-os", Architecture: "no-arch", Command: "echo \"error\""},
{OperatingSystem: runtime.GOOS, Architecture: runtime.GOARCH, Command: strings.Join(append([]string{command}, commandArgs...), " ")},
{OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "echo \"error\""},
{OperatingSystem: runtime.GOOS, Architecture: "", Command: "echo \"error\""},
},
},
}
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 == "windows" && arch == "amd64" {
osStrCmp = "win-64"
} else {
osStrCmp = "os-arch"
}
argv := []string{"--debug", "--foo", "bar"}
checkCommand(p, argv, osStrCmp, t)
cmd, args, err := p.PrepareCommand([]string{})
if err != nil {
t.Fatal(err)
}
if cmd != command {
t.Fatalf("Expected %q, got %q", command, cmd)
}
if !reflect.DeepEqual(args, commandArgs) {
t.Fatalf("Expected %v, got %v", commandArgs, args)
}
}
func TestPartialPlatformPrepareCommand(t *testing.T) {
func TestPrepareCommandFallback(t *testing.T) {
command := "echo"
commandArgs := []string{"-n", "foo"}
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"},
},
Command: strings.Join(append([]string{command}, commandArgs...), " "),
},
}
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"
cmd, args, err := p.PrepareCommand([]string{})
if err != nil {
t.Fatal(err)
}
if cmd != command {
t.Fatalf("Expected %q, got %q", command, cmd)
}
if !reflect.DeepEqual(commandArgs, args) {
t.Fatalf("Expected %v, got %v", args, args)
}
}
func TestPrepareCommandFallbackExtraArgs(t *testing.T) {
command := "echo"
commandArgs := []string{"-n", "foo"}
p := &Plugin{
Dir: "/tmp", // Unused
Metadata: &Metadata{
Name: "test",
Command: strings.Join(append([]string{command}, commandArgs...), " "),
},
}
extraArgs := []string{"--debug", "--foo", "bar"}
argv := []string{"--debug", "--foo", "bar"}
checkCommand(p, argv, osStrCmp, t)
resultArgs := append(commandArgs, extraArgs...)
cmd, args, err := p.PrepareCommand(extraArgs)
if err != nil {
t.Fatal(err)
}
if cmd != command {
t.Fatalf("Expected %q, got %q", command, cmd)
}
if !reflect.DeepEqual(args, resultArgs) {
t.Fatalf("Expected %v, got %v", resultArgs, args)
}
}
func TestNoPrepareCommand(t *testing.T) {
func TestPrepareCommandFallbackExtraArgsIgnored(t *testing.T) {
command := "echo"
commandArgs := []string{"-n", "foo"}
p := &Plugin{
Dir: "/tmp", // Unused
Metadata: &Metadata{
Name: "test",
Command: strings.Join(append([]string{command}, commandArgs...), " "),
IgnoreFlags: true,
},
}
extraArgs := []string{"--debug", "--foo", "bar"}
cmd, args, err := p.PrepareCommand(extraArgs)
if err != nil {
t.Fatal(err)
}
if cmd != command {
t.Fatalf("Expected %q, got %q", command, cmd)
}
if !reflect.DeepEqual(args, commandArgs) {
t.Fatalf("Expected %v, got %v", commandArgs, args)
}
}
func TestPrepareCommandNoMatch(t *testing.T) {
p := &Plugin{
Dir: "/tmp", // Unused
Metadata: &Metadata{
Name: "test",
PlatformCommand: []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\""}},
},
},
}
argv := []string{"--debug", "--foo", "bar"}
_, _, err := p.PrepareCommand(argv)
_, _, err := p.PrepareCommand([]string{})
if err == nil {
t.Fatalf("Expected error to be returned")
}
}
func TestNoMatchPrepareCommand(t *testing.T) {
func TestPrepareCommandNoCommand(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"}
if _, _, err := p.PrepareCommand(argv); err == nil {
_, _, err := p.PrepareCommand([]string{})
if err == nil {
t.Fatalf("Expected error to be returned")
}
}
func TestPrepareCommands(t *testing.T) {
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\""}},
}
expectedIndex := 1
cmd, args, err := PrepareCommands(cmds, "pwsh", []string{"-c", "echo \"error\""}, true, []string{})
if err != nil {
t.Fatal(err)
}
if cmd != cmds[expectedIndex].Command {
t.Fatalf("Expected %q, got %q", cmds[expectedIndex].Command, cmd)
}
if !reflect.DeepEqual(args, cmds[expectedIndex].Args) {
t.Fatalf("Expected %v, got %v", cmds[expectedIndex].Args, args)
}
}
func TestPrepareCommandsExtraArgs(t *testing.T) {
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\""}},
}
extraArgs := []string{"--debug", "--foo", "bar"}
expectedIndex := 1
resultArgs := append(cmds[expectedIndex].Args, extraArgs...)
cmd, args, err := PrepareCommands(cmds, "pwsh", []string{"-c", "echo \"error\""}, true, extraArgs)
if err != nil {
t.Fatal(err)
}
if cmd != cmds[expectedIndex].Command {
t.Fatalf("Expected %q, got %q", cmds[expectedIndex].Command, cmd)
}
if !reflect.DeepEqual(args, resultArgs) {
t.Fatalf("Expected %v, got %v", resultArgs, args)
}
}
func TestPrepareCommandsNoArch(t *testing.T) {
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\""}},
}
expectedIndex := 1
cmd, args, err := PrepareCommands(cmds, "pwsh", []string{"-c", "echo \"error\""}, true, []string{})
if err != nil {
t.Fatal(err)
}
if cmd != cmds[expectedIndex].Command {
t.Fatalf("Expected %q, got %q", cmds[expectedIndex].Command, cmd)
}
if !reflect.DeepEqual(args, cmds[expectedIndex].Args) {
t.Fatalf("Expected %v, got %v", cmds[expectedIndex].Args, args)
}
}
func TestPrepareCommandsFallback(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\""}},
}
command := "sh"
commandArgs := []string{"-c", "echo \"test\""}
cmd, args, err := PrepareCommands(cmds, command, commandArgs, true, []string{})
if err != nil {
t.Fatal(err)
}
if cmd != command {
t.Fatalf("Expected %q, got %q", command, cmd)
}
if !reflect.DeepEqual(args, commandArgs) {
t.Fatalf("Expected %v, got %v", commandArgs, 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, "", []string{}, true, []string{}); err == nil {
t.Fatalf("Expected error to be returned")
}
}
func TestPrepareCommandsNoCommands(t *testing.T) {
cmds := []PlatformCommand{}
if _, _, err := PrepareCommands(cmds, "", []string{}, true, []string{}); err == nil {
t.Fatalf("Expected error to be returned")
}
}
func TestPrepareCommandsExpand(t *testing.T) {
cmds := []PlatformCommand{}
command := "echo"
commandArgs := []string{"${TEST}"}
commandArgsExpanded := []string{""}
cmd, args, err := PrepareCommands(cmds, command, commandArgs, true, []string{})
if err != nil {
t.Fatal(err)
}
if cmd != command {
t.Fatalf("Expected %q, got %q", command, cmd)
}
if !reflect.DeepEqual(args, commandArgsExpanded) {
t.Fatalf("Expected %v, got %v", commandArgsExpanded, args)
}
}
func TestPrepareCommandsNoExpand(t *testing.T) {
cmds := []PlatformCommand{}
command := "echo"
commandArgs := []string{"${TEST}"}
cmd, args, err := PrepareCommands(cmds, command, commandArgs, false, []string{})
if err != nil {
t.Fatal(err)
}
if cmd != command {
t.Fatalf("Expected %q, got %q", command, cmd)
}
if !reflect.DeepEqual(args, commandArgs) {
t.Fatalf("Expected %v, got %v", commandArgs, args)
}
}
func TestLoadDir(t *testing.T) {
dirname := "testdata/plugdir/good/hello"
plug, err := LoadDir(dirname)
@ -196,8 +420,18 @@ 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"}},
},
Command: "${HELM_PLUGIN_DIR}/hello.sh",
IgnoreFlags: true,
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...\""}},
},
},
Hooks: map[string]string{
Install: "echo installing...",
},

@ -0,0 +1,3 @@
#!/usr/bin/env pwsh
Write-Host "Hello, world!"

@ -3,7 +3,26 @@ 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"]
command: "${HELM_PLUGIN_DIR}/hello.sh"
ignoreFlags: true
PlatformHooks:
install:
- os: linux
arch:
command: "sh"
args: ["-c", 'echo "installing..."']
- os: windows
arch:
command: "pwsh"
args: ["-c", 'echo "installing..."']
hooks:
install: "echo installing..."

Loading…
Cancel
Save