Skip to content

Commit

Permalink
feat: add exec-plugin argument and environment support (#5316)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
xvzf authored Feb 16, 2025
1 parent 3be1af6 commit cc7a71c
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 5 deletions.
4 changes: 4 additions & 0 deletions kyaml/fn/runtime/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ type Filter struct {
// Args are the arguments to the executable
Args []string `yaml:"args,omitempty"`

// Env is exposed to the environment
Env []string `yaml:"env,omitempty"`

// WorkingDir is the working directory that the executable
// should run in
WorkingDir string
Expand All @@ -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 {
cmd := exec.Command(c.Path, c.Args...) //nolint:gosec
cmd.Env = append(os.Environ(), c.Env...)
cmd.Stdin = reader
cmd.Stdout = writer
cmd.Stderr = os.Stderr
Expand Down
7 changes: 4 additions & 3 deletions kyaml/fn/runtime/exec/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
func TestFunctionFilter_Filter(t *testing.T) {
wd, err := os.Getwd()
require.NoError(t, err)
var tests = []struct {
tests := []struct {
name string
input []string
functionConfig string
Expand Down Expand Up @@ -57,8 +57,9 @@ metadata:
},
expectedError: "",
instance: exec.Filter{
Path: "sed",
Args: []string{"s/Deployment/StatefulSet/g"},
Path: "sh",
Env: []string{"TARGET=StatefulSet"},
Args: []string{"-c", `sed "s/Deployment/$TARGET/g"`},
WorkingDir: wd,
},
},
Expand Down
6 changes: 6 additions & 0 deletions kyaml/fn/runtime/runtimeutil/functiontypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ type FunctionSpec struct {

type ExecSpec struct {
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
Expand Down
28 changes: 26 additions & 2 deletions kyaml/runfn/runfn.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,8 @@ func (r RunFns) getFunctionsFromFunctions() ([]kio.Filter, error) {
return r.getFunctionFilters(true, r.Functions...)
}

// mergeContainerEnv will merge the envs specified by command line (imperative) and config
// file (declarative). If they have same key, the imperative value will be respected.
// mergeContainerEnv is container-specific and 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) mergeContainerEnv(envs []string) []string {
imperative := runtimeutil.NewContainerEnvFromStringSlice(r.Env)
declarative := runtimeutil.NewContainerEnvFromStringSlice(envs)
Expand All @@ -297,6 +297,28 @@ func (r RunFns) mergeContainerEnv(envs []string) []string {
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) (
[]kio.Filter, error) {
var fltrs []kio.Filter
Expand Down Expand Up @@ -494,6 +516,8 @@ func (r *RunFns) ffp(spec runtimeutil.FunctionSpec, api *yaml.RNode, currentUser
if r.EnableExec && spec.Exec.Path != "" {
ef := &exec.Filter{
Path: spec.Exec.Path,
Args: spec.Exec.Args,
Env: r.mergeExecEnv(spec.Exec.Env),
WorkingDir: r.WorkingDir,
}

Expand Down
52 changes: 52 additions & 0 deletions kyaml/runfn/runfn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
}

0 comments on commit cc7a71c

Please sign in to comment.