Merge branch 'main' into feature/vault-integration

Signed-off-by: Vineet0197 <47638245+Vineet0197@users.noreply.github.com>
pull/13492/head
Vineet0197 10 months ago committed by GitHub
commit f89960ab8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -6,12 +6,15 @@
# Organizations Using Helm # Organizations Using Helm
- [IBM](https://www.ibm.com) - [IBM](https://www.ibm.com)
- [InfoCert](https://www.infocert.it/)
- [Microsoft](https://microsoft.com) - [Microsoft](https://microsoft.com)
- [Omnistrate](https://omnistrate.com)
- [Octopus Deploy](https://octopus.com/) - [Octopus Deploy](https://octopus.com/)
- [New Relic](https://www.newrelic.com) - [New Relic](https://www.newrelic.com)
- [Qovery](https://www.qovery.com/) - [Qovery](https://www.qovery.com/)
- [Samsung SDS](https://www.samsungsds.com/) - [Samsung SDS](https://www.samsungsds.com/)
- [Softonic](https://hello.softonic.com/) - [Softonic](https://hello.softonic.com/)
- [SyncTune](https://mb-consulting.dev)
- [Syself](https://syself.com) - [Syself](https://syself.com)
- [Ville de Montreal](https://montreal.ca) - [Ville de Montreal](https://montreal.ca)
- [Intercept](https://Intercept.cloud) - [Intercept](https://Intercept.cloud)

@ -47,19 +47,27 @@ func newPluginCmd(out io.Writer) *cobra.Command {
// runHook will execute a plugin hook. // runHook will execute a plugin hook.
func runHook(p *plugin.Plugin, event string) error { func runHook(p *plugin.Plugin, event string) error {
hook := p.Metadata.Hooks[event] plugin.SetupPluginEnv(settings, p.Metadata.Name, p.Dir)
if hook == "" {
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 return nil
} }
prog := exec.Command("sh", "-c", hook) prog := exec.Command(main, argv...)
// TODO make this work on windows
// I think its ... ¯\_(ツ)_/¯
// prog := exec.Command("cmd", "/C", p.Metadata.Hooks.Install())
debug("running %s hook: %s", event, prog) debug("running %s hook: %s", event, prog)
plugin.SetupPluginEnv(settings, p.Metadata.Name, p.Dir)
prog.Stdout, prog.Stderr = os.Stdout, os.Stderr prog.Stdout, prog.Stderr = os.Stdout, os.Stderr
if err := prog.Run(); err != nil { if err := prog.Run(); err != nil {
if eerr, ok := err.(*exec.ExitError); ok { if eerr, ok := err.(*exec.ExitError); ok {

@ -39,14 +39,14 @@ require (
golang.org/x/term v0.26.0 golang.org/x/term v0.26.0
golang.org/x/text v0.20.0 golang.org/x/text v0.20.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.31.2 k8s.io/api v0.31.3
k8s.io/apiextensions-apiserver v0.31.2 k8s.io/apiextensions-apiserver v0.31.3
k8s.io/apimachinery v0.31.2 k8s.io/apimachinery v0.31.3
k8s.io/apiserver v0.31.2 k8s.io/apiserver v0.31.3
k8s.io/cli-runtime v0.31.2 k8s.io/cli-runtime v0.31.3
k8s.io/client-go v0.31.2 k8s.io/client-go v0.31.3
k8s.io/klog/v2 v2.130.1 k8s.io/klog/v2 v2.130.1
k8s.io/kubectl v0.31.2 k8s.io/kubectl v0.31.3
oras.land/oras-go v1.2.5 oras.land/oras-go v1.2.5
sigs.k8s.io/yaml v1.4.0 sigs.k8s.io/yaml v1.4.0
) )
@ -192,7 +192,8 @@ require (
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/component-base v0.31.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/component-base v0.31.3 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect

@ -642,26 +642,26 @@ gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8=
k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE=
k8s.io/apiextensions-apiserver v0.31.2 h1:W8EwUb8+WXBLu56ser5IudT2cOho0gAKeTOnywBLxd0= k8s.io/apiextensions-apiserver v0.31.3 h1:+GFGj2qFiU7rGCsA5o+p/rul1OQIq6oYpQw4+u+nciE=
k8s.io/apiextensions-apiserver v0.31.2/go.mod h1:i+Geh+nGCJEGiCGR3MlBDkS7koHIIKWVfWeRFiOsUcM= k8s.io/apiextensions-apiserver v0.31.3/go.mod h1:2DSpFhUZZJmn/cr/RweH1cEVVbzFw9YBu4T+U3mf1e4=
k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4=
k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
k8s.io/apiserver v0.31.2 h1:VUzOEUGRCDi6kX1OyQ801m4A7AUPglpsmGvdsekmcI4= k8s.io/apiserver v0.31.3 h1:+1oHTtCB+OheqFEz375D0IlzHZ5VeQKX1KGXnx+TTuY=
k8s.io/apiserver v0.31.2/go.mod h1:o3nKZR7lPlJqkU5I3Ove+Zx3JuoFjQobGX1Gctw6XuE= k8s.io/apiserver v0.31.3/go.mod h1:PrxVbebxrxQPFhJk4powDISIROkNMKHibTg9lTRQ0Qg=
k8s.io/cli-runtime v0.31.2 h1:7FQt4C4Xnqx8V1GJqymInK0FFsoC+fAZtbLqgXYVOLQ= k8s.io/cli-runtime v0.31.3 h1:fEQD9Xokir78y7pVK/fCJN090/iYNrLHpFbGU4ul9TI=
k8s.io/cli-runtime v0.31.2/go.mod h1:XROyicf+G7rQ6FQJMbeDV9jqxzkWXTYD6Uxd15noe0Q= k8s.io/cli-runtime v0.31.3/go.mod h1:Q2jkyTpl+f6AtodQvgDI8io3jrfr+Z0LyQBPJJ2Btq8=
k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= k8s.io/client-go v0.31.3 h1:CAlZuM+PH2cm+86LOBemaJI/lQ5linJ6UFxKX/SoG+4=
k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs= k8s.io/client-go v0.31.3/go.mod h1:2CgjPUTpv3fE5dNygAr2NcM8nhHzXvxB8KL5gYc3kJs=
k8s.io/component-base v0.31.2 h1:Z1J1LIaC0AV+nzcPRFqfK09af6bZ4D1nAOpWsy9owlA= k8s.io/component-base v0.31.3 h1:DMCXXVx546Rfvhj+3cOm2EUxhS+EyztH423j+8sOwhQ=
k8s.io/component-base v0.31.2/go.mod h1:9PeyyFN/drHjtJZMCTkSpQJS3U9OXORnHQqMLDz0sUQ= k8s.io/component-base v0.31.3/go.mod h1:xME6BHfUOafRgT0rGVBGl7TuSg8Z9/deT7qq6w7qjIU=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/kubectl v0.31.2 h1:gTxbvRkMBwvTSAlobiTVqsH6S8Aa1aGyBcu5xYLsn8M= k8s.io/kubectl v0.31.3 h1:3r111pCjPsvnR98oLLxDMwAeM6OPGmPty6gSKaLTQes=
k8s.io/kubectl v0.31.2/go.mod h1:EyASYVU6PY+032RrTh5ahtSOMgoDRIux9V1JLKtG5xM= k8s.io/kubectl v0.31.3/go.mod h1:lhMECDCbJN8He12qcKqs2QfmVo9Pue30geovBVpH5fs=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo=

@ -25,5 +25,8 @@ const (
Update = "update" 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. // Hooks is a map of events to commands.
type Hooks map[string]string type Hooks map[string]string

@ -44,9 +44,10 @@ type Downloaders struct {
// PlatformCommand represents a command for a particular operating system and architecture // PlatformCommand represents a command for a particular operating system and architecture
type PlatformCommand struct { type PlatformCommand struct {
OperatingSystem string `json:"os"` OperatingSystem string `json:"os"`
Architecture string `json:"arch"` Architecture string `json:"arch"`
Command string `json:"command"` Command string `json:"command"`
Args []string `json:"args"`
} }
// Metadata describes a plugin. // Metadata describes a plugin.
@ -65,23 +66,35 @@ type Metadata struct {
// Description is a long description shown in places like `helm help` // Description is a long description shown in places like `helm help`
Description string `json:"description"` 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 // be present in this command. Unless IgnoreFlags is set, this will
// also merge the flags passed from Helm. // 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. // pointing the command to a shell script.
// //
// The following rules will apply to processing commands: // The following rules will apply to processing platform commands:
// - If platformCommand is present, it will be searched first // - 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 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 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 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 // - If no matches are found in platformCommand, Helm will exit with an error
PlatformCommand []PlatformCommand `json:"platformCommand"` PlatformCommand []PlatformCommand `json:"platformCommand"`
Command string `json:"command"`
// 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
// 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.
//
// DEPRECATED: Use PlatformCommand instead. Remove in Helm 4.
Command string `json:"command"`
// IgnoreFlags ignores any flags passed in from Helm // IgnoreFlags ignores any flags passed in from Helm
// //
@ -90,7 +103,31 @@ type Metadata struct {
// the `--debug` flag will be discarded. // the `--debug` flag will be discarded.
IgnoreFlags bool `json:"ignoreFlags"` 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 Hooks Hooks
// Downloaders field is used if the plugin supply downloader mechanism // Downloaders field is used if the plugin supply downloader mechanism
@ -112,62 +149,106 @@ type Plugin struct {
Dir string Dir string
} }
// The following rules will apply to processing the Plugin.PlatformCommand.Command: // Returns command and args strings based on the following rules in priority order:
// - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution // - From the PlatformCommand where OS and Arch match the current platform
// - If OS matches and there is no more specific match, the command will be prepared for execution // - From the PlatformCommand where OS matches the current platform and Arch is empty/unspecified
// - If no OS/Arch match is found, return nil // - From the PlatformCommand where OS is empty/unspecified and Arch matches the current platform
func getPlatformCommand(cmds []PlatformCommand) []string { // - From the PlatformCommand where OS and Arch are both empty/unspecified
var command []string // - Return nil, nil
func getPlatformCommand(cmds []PlatformCommand) ([]string, []string) {
var command, args []string
found := false
foundOs := false
eq := strings.EqualFold eq := strings.EqualFold
for _, c := range cmds { 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) { 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: // PrepareCommands takes a []Plugin.PlatformCommand
// - If platformCommand is present, it will be searched first // and prepares the command and arguments for execution.
// - 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 // 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. // The result is suitable to pass to exec.Command.
func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) { func PrepareCommands(cmds []PlatformCommand, expandArgs bool, extraArgs []string) (string, []string, error) {
var parts []string cmdParts, args := getPlatformCommand(cmds)
platCmdLen := len(p.Metadata.PlatformCommand) if len(cmdParts) == 0 || cmdParts[0] == "" {
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] == "" {
return "", nil, fmt.Errorf("no plugin command is applicable") return "", nil, fmt.Errorf("no plugin command is applicable")
} }
main := os.ExpandEnv(parts[0]) main := os.ExpandEnv(cmdParts[0])
baseArgs := []string{} baseArgs := []string{}
if len(parts) > 1 { if len(cmdParts) > 1 {
for _, cmdpart := range parts[1:] { for _, cmdPart := range cmdParts[1:] {
cmdexp := os.ExpandEnv(cmdpart) if expandArgs {
baseArgs = append(baseArgs, cmdexp) 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...) baseArgs = append(baseArgs, extraArgs...)
} }
return main, baseArgs, nil 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. // validPluginName is a regular expression that validates plugin names.
// //
// Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, _ and -. // 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) 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. // We could also validate SemVer, executable, and other fields should we so choose.
return nil return nil
} }

@ -26,163 +26,256 @@ import (
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli"
) )
func checkCommand(p *Plugin, extraArgs []string, osStrCmp string, t *testing.T) { func TestPrepareCommand(t *testing.T) {
cmd, args, err := p.PrepareCommand(extraArgs) cmdMain := "sh"
if err != nil { cmdArgs := []string{"-c", "echo \"test\""}
t.Fatal(err)
}
if cmd != "echo" {
t.Fatalf("Expected echo, got %q", cmd)
}
if l := len(args); l != 5 {
t.Fatalf("expected 5 args, got %d", l)
}
expect := []string{"-n", osStrCmp, "--debug", "--foo", "bar"} p := &Plugin{
for i := 0; i < len(args); i++ { Dir: "/tmp", // Unused
if expect[i] != args[i] { Metadata: &Metadata{
t.Errorf("Expected arg=%q, got %q", expect[i], args[i]) 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},
},
},
} }
// Test with IgnoreFlags. This should omit --debug, --foo, bar cmd, args, err := p.PrepareCommand([]string{})
p.Metadata.IgnoreFlags = true
cmd, args, err = p.PrepareCommand(extraArgs)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if cmd != "echo" { if cmd != cmdMain {
t.Fatalf("Expected echo, got %q", cmd) t.Fatalf("Expected %q, got %q", cmdMain, cmd)
} }
if l := len(args); l != 2 { if !reflect.DeepEqual(args, cmdArgs) {
t.Fatalf("expected 2 args, got %d", l) t.Fatalf("Expected %v, got %v", cmdArgs, args)
}
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])
}
} }
} }
func TestPrepareCommand(t *testing.T) { func TestPrepareCommandExtraArgs(t *testing.T) {
cmdMain := "sh"
cmdArgs := []string{"-c", "echo \"test\""}
extraArgs := []string{"--debug", "--foo", "bar"}
p := &Plugin{ p := &Plugin{
Dir: "/tmp", // Unused Dir: "/tmp", // Unused
Metadata: &Metadata{ Metadata: &Metadata{
Name: "test", 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: 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\""}},
},
}, },
} }
argv := []string{"--debug", "--foo", "bar"}
checkCommand(p, argv, "foo", t) expectedArgs := append(cmdArgs, extraArgs...)
cmd, args, err := p.PrepareCommand(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 TestPlatformPrepareCommand(t *testing.T) { func TestPrepareCommandExtraArgsIgnored(t *testing.T) {
cmdMain := "sh"
cmdArgs := []string{"-c", "echo \"test\""}
extraArgs := []string{"--debug", "--foo", "bar"}
p := &Plugin{ p := &Plugin{
Dir: "/tmp", // Unused Dir: "/tmp", // Unused
Metadata: &Metadata{ Metadata: &Metadata{
Name: "test", Name: "test",
Command: "echo -n os-arch", Command: "echo \"error\"",
PlatformCommand: []PlatformCommand{ PlatformCommand: []PlatformCommand{
{OperatingSystem: "linux", Architecture: "386", Command: "echo -n linux-386"}, {OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
{OperatingSystem: "linux", Architecture: "amd64", Command: "echo -n linux-amd64"}, {OperatingSystem: runtime.GOOS, Architecture: runtime.GOARCH, Command: cmdMain, Args: cmdArgs},
{OperatingSystem: "linux", Architecture: "arm64", Command: "echo -n linux-arm64"}, {OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
{OperatingSystem: "linux", Architecture: "ppc64le", Command: "echo -n linux-ppc64le"}, {OperatingSystem: runtime.GOOS, Architecture: "", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
{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"},
}, },
IgnoreFlags: true,
}, },
} }
var osStrCmp string
os := runtime.GOOS cmd, args, err := p.PrepareCommand(extraArgs)
arch := runtime.GOARCH if err != nil {
if os == "linux" && arch == "386" { t.Fatal(err)
osStrCmp = "linux-386" }
} else if os == "linux" && arch == "amd64" { if cmd != cmdMain {
osStrCmp = "linux-amd64" t.Fatalf("Expected %q, got %q", cmdMain, cmd)
} else if os == "linux" && arch == "arm64" { }
osStrCmp = "linux-arm64" if !reflect.DeepEqual(args, cmdArgs) {
} else if os == "linux" && arch == "ppc64le" { t.Fatalf("Expected %v, got %v", cmdArgs, args)
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"
}
argv := []string{"--debug", "--foo", "bar"}
checkCommand(p, argv, osStrCmp, t)
} }
func TestPartialPlatformPrepareCommand(t *testing.T) { func TestPrepareCommands(t *testing.T) {
p := &Plugin{ cmdMain := "sh"
Dir: "/tmp", // Unused cmdArgs := []string{"-c", "echo \"test\""}
Metadata: &Metadata{
Name: "test", cmds := []PlatformCommand{
Command: "echo -n os-arch", {OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
PlatformCommand: []PlatformCommand{ {OperatingSystem: runtime.GOOS, Architecture: runtime.GOARCH, Command: cmdMain, Args: cmdArgs},
{OperatingSystem: "linux", Architecture: "386", Command: "echo -n linux-386"}, {OperatingSystem: runtime.GOOS, Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
{OperatingSystem: "windows", Architecture: "amd64", Command: "echo -n win-64"}, {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)
} }
var osStrCmp string if !reflect.DeepEqual(args, cmdArgs) {
os := runtime.GOOS t.Fatalf("Expected %v, got %v", cmdArgs, args)
arch := runtime.GOARCH }
if os == "linux" { }
osStrCmp = "linux-386"
} else if os == "windows" && arch == "amd64" { func TestPrepareCommandsExtraArgs(t *testing.T) {
osStrCmp = "win-64" cmdMain := "sh"
} else { cmdArgs := []string{"-c", "echo \"test\""}
osStrCmp = "os-arch" 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"} expectedArgs := append(cmdArgs, extraArgs...)
checkCommand(p, argv, osStrCmp, t)
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 TestNoPrepareCommand(t *testing.T) { func TestPrepareCommandsNoArch(t *testing.T) {
p := &Plugin{ cmdMain := "sh"
Dir: "/tmp", // Unused cmdArgs := []string{"-c", "echo \"test\""}
Metadata: &Metadata{
Name: "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"}
_, _, err := p.PrepareCommand(argv) cmd, args, err := PrepareCommands(cmds, true, []string{})
if err == nil { if err != nil {
t.Fatalf("Expected error to be returned") 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 TestNoMatchPrepareCommand(t *testing.T) { func TestPrepareCommandsNoOsNoArch(t *testing.T) {
p := &Plugin{ cmdMain := "sh"
Dir: "/tmp", // Unused cmdArgs := []string{"-c", "echo \"test\""}
Metadata: &Metadata{
Name: "test", cmds := []PlatformCommand{
PlatformCommand: []PlatformCommand{ {OperatingSystem: "no-os", Architecture: "no-arch", Command: "pwsh", Args: []string{"-c", "echo \"error\""}},
{OperatingSystem: "no-os", Architecture: "amd64", Command: "echo -n linux-386"}, {OperatingSystem: "", 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"}
if _, _, err := p.PrepareCommand(argv); err == nil { 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") t.Fatalf("Expected error to be returned")
} }
} }
func TestPrepareCommandsNoCommands(t *testing.T) {
cmds := []PlatformCommand{}
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) { func TestLoadDir(t *testing.T) {
dirname := "testdata/plugdir/good/hello" dirname := "testdata/plugdir/good/hello"
plug, err := LoadDir(dirname) plug, err := LoadDir(dirname)
@ -199,10 +292,16 @@ func TestLoadDir(t *testing.T) {
Version: "0.1.0", Version: "0.1.0",
Usage: "usage", Usage: "usage",
Description: "description", 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, IgnoreFlags: true,
Hooks: map[string]string{ PlatformHooks: map[string][]PlatformCommand{
Install: "echo installing...", 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) { func TestLoadAll(t *testing.T) {
// Verify that empty dir loads: // Verify that empty dir loads:
if plugs, err := LoadAll("testdata"); err != nil { if plugs, err := LoadAll("testdata"); err != nil {
t.Fatalf("error loading dir with no plugins: %s", err) t.Fatalf("error loading dir with no plugins: %s", err)
@ -361,6 +459,30 @@ func TestValidatePluginData(t *testing.T) {
Dir: "no-such-dir", 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 { for i, item := range []struct {
pass bool pass bool
plug *Plugin plug *Plugin
@ -372,6 +494,10 @@ func TestValidatePluginData(t *testing.T) {
{false, mockPlugin("foo -bar ")}, // Test trailing chars {false, mockPlugin("foo -bar ")}, // Test trailing chars
{false, mockPlugin("foo\nbar")}, // Test newline {false, mockPlugin("foo\nbar")}, // Test newline
{false, mockMissingMeta}, // Test if the metadata section missing {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)) err := validatePluginData(item.plug, fmt.Sprintf("test-%d", i))
if item.pass && err != nil { if item.pass && err != nil {
@ -403,7 +529,16 @@ func mockPlugin(name string) *Plugin {
Version: "v0.1.2", Version: "v0.1.2",
Usage: "Mock plugin", Usage: "Mock plugin",
Description: "Mock plugin for testing", 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", Dir: "no-such-dir",
} }

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

@ -3,7 +3,23 @@ version: "0.1.0"
usage: "usage" usage: "usage"
description: |- description: |-
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 ignoreFlags: true
hooks: platformHooks:
install: "echo installing..." install:
- os: linux
arch: ""
command: "sh"
args: ["-c", 'echo "installing..."']
- os: windows
arch: ""
command: "pwsh"
args: ["-c", 'echo "installing..."']

Loading…
Cancel
Save