mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-14 10:30:59 +00:00
feat: add exec-plugin argument and environment support (#5316)
* feat: add exec-plugin argument and environment support Previously, the documentation lead to think that this is working, but it's not been implemented. This PR is fixing this Signed-off-by: Matthias Riegler <matthias.riegler@ankorstore.com> * chore: disable linting for env var split Signed-off-by: Matthias Riegler <matthias.riegler@ankorstore.com> --------- Signed-off-by: Matthias Riegler <matthias.riegler@ankorstore.com>
This commit is contained in:
@@ -21,6 +21,9 @@ type Filter struct {
|
|||||||
// Args are the arguments to the executable
|
// Args are the arguments to the executable
|
||||||
Args []string `yaml:"args,omitempty"`
|
Args []string `yaml:"args,omitempty"`
|
||||||
|
|
||||||
|
// Env is exposed to the environment
|
||||||
|
Env []string `yaml:"env,omitempty"`
|
||||||
|
|
||||||
// WorkingDir is the working directory that the executable
|
// WorkingDir is the working directory that the executable
|
||||||
// should run in
|
// should run in
|
||||||
WorkingDir string
|
WorkingDir string
|
||||||
@@ -35,6 +38,7 @@ func (c *Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
|||||||
|
|
||||||
func (c *Filter) Run(reader io.Reader, writer io.Writer) error {
|
func (c *Filter) Run(reader io.Reader, writer io.Writer) error {
|
||||||
cmd := exec.Command(c.Path, c.Args...) //nolint:gosec
|
cmd := exec.Command(c.Path, c.Args...) //nolint:gosec
|
||||||
|
cmd.Env = append(os.Environ(), c.Env...)
|
||||||
cmd.Stdin = reader
|
cmd.Stdin = reader
|
||||||
cmd.Stdout = writer
|
cmd.Stdout = writer
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
func TestFunctionFilter_Filter(t *testing.T) {
|
func TestFunctionFilter_Filter(t *testing.T) {
|
||||||
wd, err := os.Getwd()
|
wd, err := os.Getwd()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
var tests = []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input []string
|
input []string
|
||||||
functionConfig string
|
functionConfig string
|
||||||
@@ -57,8 +57,9 @@ metadata:
|
|||||||
},
|
},
|
||||||
expectedError: "",
|
expectedError: "",
|
||||||
instance: exec.Filter{
|
instance: exec.Filter{
|
||||||
Path: "sed",
|
Path: "sh",
|
||||||
Args: []string{"s/Deployment/StatefulSet/g"},
|
Env: []string{"TARGET=StatefulSet"},
|
||||||
|
Args: []string{"-c", `sed "s/Deployment/$TARGET/g"`},
|
||||||
WorkingDir: wd,
|
WorkingDir: wd,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -138,6 +138,12 @@ type FunctionSpec struct {
|
|||||||
|
|
||||||
type ExecSpec struct {
|
type ExecSpec struct {
|
||||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||||
|
|
||||||
|
// Args is a slice of args that will be passed as arguments to script
|
||||||
|
Args []string `json:"args,omitempty" yaml:"args,omitempty"`
|
||||||
|
|
||||||
|
// Env is a slice of env string that will be exposed to container
|
||||||
|
Env []string `json:"envs,omitempty" yaml:"envs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerSpec defines a spec for running a function as a container
|
// ContainerSpec defines a spec for running a function as a container
|
||||||
|
|||||||
@@ -281,8 +281,8 @@ func (r RunFns) getFunctionsFromFunctions() ([]kio.Filter, error) {
|
|||||||
return r.getFunctionFilters(true, r.Functions...)
|
return r.getFunctionFilters(true, r.Functions...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergeContainerEnv will merge the envs specified by command line (imperative) and config
|
// mergeContainerEnv is container-specific and will merge the envs specified by command line (imperative)
|
||||||
// file (declarative). If they have same key, the imperative value will be respected.
|
// and config file (declarative). If they have same key, the imperative value will be respected.
|
||||||
func (r RunFns) mergeContainerEnv(envs []string) []string {
|
func (r RunFns) mergeContainerEnv(envs []string) []string {
|
||||||
imperative := runtimeutil.NewContainerEnvFromStringSlice(r.Env)
|
imperative := runtimeutil.NewContainerEnvFromStringSlice(r.Env)
|
||||||
declarative := runtimeutil.NewContainerEnvFromStringSlice(envs)
|
declarative := runtimeutil.NewContainerEnvFromStringSlice(envs)
|
||||||
@@ -297,6 +297,28 @@ func (r RunFns) mergeContainerEnv(envs []string) []string {
|
|||||||
return declarative.Raw()
|
return declarative.Raw()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mergeExecEnv will merge the envs specified by command line (imperative) and config
|
||||||
|
// file (declarative). If they have same key, the imperative value will be respected.
|
||||||
|
func (r RunFns) mergeExecEnv(envs []string) []string {
|
||||||
|
envMap := map[string]string{}
|
||||||
|
|
||||||
|
for _, env := range append(envs, r.Env...) {
|
||||||
|
res := strings.Split(env, "=")
|
||||||
|
//nolint:gomnd
|
||||||
|
if len(res) == 2 {
|
||||||
|
envMap[res[0]] = res[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mergedEnv := []string{}
|
||||||
|
for key, value := range envMap {
|
||||||
|
mergedEnv = append(mergedEnv, fmt.Sprintf("%s=%s", key, value))
|
||||||
|
}
|
||||||
|
// Sort the envs to make the output deterministic
|
||||||
|
sort.Strings(mergedEnv)
|
||||||
|
return mergedEnv
|
||||||
|
}
|
||||||
|
|
||||||
func (r RunFns) getFunctionFilters(global bool, fns ...*yaml.RNode) (
|
func (r RunFns) getFunctionFilters(global bool, fns ...*yaml.RNode) (
|
||||||
[]kio.Filter, error) {
|
[]kio.Filter, error) {
|
||||||
var fltrs []kio.Filter
|
var fltrs []kio.Filter
|
||||||
@@ -494,6 +516,8 @@ func (r *RunFns) ffp(spec runtimeutil.FunctionSpec, api *yaml.RNode, currentUser
|
|||||||
if r.EnableExec && spec.Exec.Path != "" {
|
if r.EnableExec && spec.Exec.Path != "" {
|
||||||
ef := &exec.Filter{
|
ef := &exec.Filter{
|
||||||
Path: spec.Exec.Path,
|
Path: spec.Exec.Path,
|
||||||
|
Args: spec.Exec.Args,
|
||||||
|
Env: r.mergeExecEnv(spec.Exec.Env),
|
||||||
WorkingDir: r.WorkingDir,
|
WorkingDir: r.WorkingDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1282,3 +1282,55 @@ func TestRunFns_mergeContainerEnv(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunFns_mergeExecEnv(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
instance RunFns
|
||||||
|
inputEnvs []string
|
||||||
|
expect []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "all empty",
|
||||||
|
instance: RunFns{},
|
||||||
|
expect: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty command line envs",
|
||||||
|
instance: RunFns{},
|
||||||
|
inputEnvs: []string{"foo=bar"},
|
||||||
|
expect: []string{"foo=bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty declarative envs",
|
||||||
|
instance: RunFns{
|
||||||
|
Env: []string{"foo=bar"},
|
||||||
|
},
|
||||||
|
expect: []string{"foo=bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same key",
|
||||||
|
instance: RunFns{
|
||||||
|
Env: []string{"foo=bar"},
|
||||||
|
},
|
||||||
|
inputEnvs: []string{"foo=bar1"},
|
||||||
|
expect: []string{"foo=bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same exported key",
|
||||||
|
instance: RunFns{
|
||||||
|
Env: []string{"foo=bar"},
|
||||||
|
},
|
||||||
|
inputEnvs: []string{"foo1=bar1"},
|
||||||
|
expect: []string{"foo1=bar1", "foo=bar"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range testcases {
|
||||||
|
tc := testcases[i]
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
envs := tc.instance.mergeExecEnv(tc.inputEnvs)
|
||||||
|
assert.Equal(t, tc.expect, envs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user