mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-11 17:12:51 +00:00
config run: support for RunFns.Functions and RunFns.Input
- Support specifying RunFns.Functions using the `-i` flag to specify an image - Parse the function config from key-value arguments specified after ` -- ` - Support reading from stdin / writing to stdout if no arguments are provided - Table driven tests for parsing flags and args into RunFns structure
This commit is contained in:
@@ -97,6 +97,9 @@ func NewConfigCommand(name string) *cobra.Command {
|
|||||||
root.AddCommand(commands.Merge3Command(name))
|
root.AddCommand(commands.Merge3Command(name))
|
||||||
root.AddCommand(commands.CountCommand(name))
|
root.AddCommand(commands.CountCommand(name))
|
||||||
root.AddCommand(commands.RunFnCommand(name))
|
root.AddCommand(commands.RunFnCommand(name))
|
||||||
|
root.AddCommand(commands.XArgsCommand())
|
||||||
|
root.AddCommand(commands.WrapCommand())
|
||||||
|
|
||||||
root.AddCommand(commands.SetCommand(name))
|
root.AddCommand(commands.SetCommand(name))
|
||||||
root.AddCommand(commands.ListSettersCommand(name))
|
root.AddCommand(commands.ListSettersCommand(name))
|
||||||
root.AddCommand(commands.CreateSetterCommand(name))
|
root.AddCommand(commands.CreateSetterCommand(name))
|
||||||
|
|||||||
@@ -4,22 +4,27 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
|
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||||
"sigs.k8s.io/kustomize/kyaml/runfn"
|
"sigs.k8s.io/kustomize/kyaml/runfn"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetCatRunner returns a RunFnRunner.
|
// GetCatRunner returns a RunFnRunner.
|
||||||
func GetRunFnRunner(name string) *RunFnRunner {
|
func GetRunFnRunner(name string) *RunFnRunner {
|
||||||
r := &RunFnRunner{}
|
r := &RunFnRunner{}
|
||||||
c := &cobra.Command{
|
c := &cobra.Command{
|
||||||
Use: "run DIR",
|
Use: "run [DIR]",
|
||||||
Aliases: []string{"run-fns"},
|
|
||||||
Short: commands.RunFnsShort,
|
Short: commands.RunFnsShort,
|
||||||
Long: commands.RunFnsLong,
|
Long: commands.RunFnsLong,
|
||||||
Example: commands.RunFnsExamples,
|
Example: commands.RunFnsExamples,
|
||||||
RunE: r.runE,
|
RunE: r.runE,
|
||||||
Args: cobra.ExactArgs(1),
|
PreRunE: r.preRunE,
|
||||||
}
|
}
|
||||||
fixDocs(name, c)
|
fixDocs(name, c)
|
||||||
c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true,
|
c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true,
|
||||||
@@ -29,11 +34,12 @@ func GetRunFnRunner(name string) *RunFnRunner {
|
|||||||
&r.DryRun, "dry-run", false, "print results to stdout")
|
&r.DryRun, "dry-run", false, "print results to stdout")
|
||||||
r.Command.Flags().BoolVar(
|
r.Command.Flags().BoolVar(
|
||||||
&r.GlobalScope, "global-scope", false, "set global scope for functions.")
|
&r.GlobalScope, "global-scope", false, "set global scope for functions.")
|
||||||
r.Command.Flags().StringSliceVar(
|
r.Command.Flags().StringSliceVarP(
|
||||||
&r.FnPaths, "fn-path", []string{},
|
&r.FnPaths, "fn-path", "p", []string{},
|
||||||
"directories containing functions without configuration")
|
"read functions from these directories instead of the configuration directory.")
|
||||||
r.Command.AddCommand(XArgsCommand())
|
r.Command.Flags().StringVarP(
|
||||||
r.Command.AddCommand(WrapCommand())
|
&r.Image, "image", "i", "",
|
||||||
|
"run this image as a function instead of discovering them.")
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,12 +54,142 @@ type RunFnRunner struct {
|
|||||||
DryRun bool
|
DryRun bool
|
||||||
GlobalScope bool
|
GlobalScope bool
|
||||||
FnPaths []string
|
FnPaths []string
|
||||||
|
Image string
|
||||||
|
RunFns runfn.RunFns
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RunFnRunner) runE(c *cobra.Command, args []string) error {
|
func (r *RunFnRunner) runE(c *cobra.Command, args []string) error {
|
||||||
rec := runfn.RunFns{Path: args[0], FunctionPaths: r.FnPaths, GlobalScope: r.GlobalScope}
|
return handleError(c, r.RunFns.Execute())
|
||||||
if r.DryRun {
|
}
|
||||||
rec.Output = c.OutOrStdout()
|
|
||||||
}
|
// getFunctions parses the commandline flags and arguments into explicit
|
||||||
return handleError(c, rec.Execute())
|
// Functions to run.
|
||||||
|
func (r *RunFnRunner) getFunctions(c *cobra.Command, args, dataItems []string) (
|
||||||
|
[]*yaml.RNode, error) {
|
||||||
|
// if image isn't specified, then Functions is empty
|
||||||
|
if r.Image == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the function spec to set as an annotation
|
||||||
|
fn, err := yaml.Parse(`container: {}`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// TODO: add support network, volumes, etc based on flag values
|
||||||
|
err = fn.PipeE(
|
||||||
|
yaml.Lookup("container"),
|
||||||
|
yaml.SetField("image", yaml.NewScalarRNode(r.Image)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the function config
|
||||||
|
rc, err := yaml.Parse(`
|
||||||
|
metadata:
|
||||||
|
name: function-input
|
||||||
|
data: {}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the function annotation on the function config so it
|
||||||
|
// is parsed by RunFns
|
||||||
|
value, err := fn.String()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = rc.PipeE(
|
||||||
|
yaml.LookupCreate(yaml.MappingNode, "metadata", "annotations"),
|
||||||
|
yaml.SetField("config.kubernetes.io/function", yaml.NewScalarRNode(value)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// default the function config kind to ConfigMap, this may be overridden
|
||||||
|
var kind = "ConfigMap"
|
||||||
|
var version = "v1"
|
||||||
|
|
||||||
|
// populate the function config with data. this is a convention for functions
|
||||||
|
// to be more commandline friendly
|
||||||
|
if len(dataItems) > 0 {
|
||||||
|
dataField, err := rc.Pipe(yaml.Lookup("data"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i, s := range dataItems {
|
||||||
|
kv := strings.SplitN(s, "=", 2)
|
||||||
|
if i == 0 && len(kv) == 1 {
|
||||||
|
// first argument may be the kind
|
||||||
|
kind = s
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(kv) != 2 {
|
||||||
|
return nil, fmt.Errorf("args must have keys and values separated by =")
|
||||||
|
}
|
||||||
|
err := dataField.PipeE(yaml.SetField(kv[0], yaml.NewScalarRNode(kv[1])))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = rc.PipeE(yaml.SetField("kind", yaml.NewScalarRNode(kind)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = rc.PipeE(yaml.SetField("apiVersion", yaml.NewScalarRNode(version)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return []*yaml.RNode{rc}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RunFnRunner) preRunE(c *cobra.Command, args []string) error {
|
||||||
|
if c.ArgsLenAtDash() >= 0 && r.Image == "" {
|
||||||
|
return errors.Errorf("must specify --image")
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataItems []string
|
||||||
|
if c.ArgsLenAtDash() >= 0 {
|
||||||
|
dataItems = args[c.ArgsLenAtDash():]
|
||||||
|
args = args[:c.ArgsLenAtDash()]
|
||||||
|
}
|
||||||
|
if len(args) > 1 {
|
||||||
|
return errors.Errorf("0 or 1 arguments supported, function arguments go after '--'")
|
||||||
|
}
|
||||||
|
|
||||||
|
fns, err := r.getFunctions(c, args, dataItems)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the output to stdout if in dry-run mode or no arguments are specified
|
||||||
|
var output io.Writer
|
||||||
|
var input io.Reader
|
||||||
|
if len(args) == 0 {
|
||||||
|
output = c.OutOrStdout()
|
||||||
|
input = c.InOrStdin()
|
||||||
|
} else if r.DryRun {
|
||||||
|
output = c.OutOrStdout()
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the path if specified as an argument
|
||||||
|
var path string
|
||||||
|
if len(args) == 1 {
|
||||||
|
// argument is the directory
|
||||||
|
path = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
r.RunFns = runfn.RunFns{
|
||||||
|
FunctionPaths: r.FnPaths,
|
||||||
|
GlobalScope: r.GlobalScope,
|
||||||
|
Functions: fns,
|
||||||
|
Output: output,
|
||||||
|
Input: input,
|
||||||
|
Path: path,
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't consider args for the function
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
232
cmd/config/internal/commands/run_test.go
Normal file
232
cmd/config/internal/commands/run_test.go
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestRunFnCommand_preRunE verifies that preRunE correctly parses the commandline
|
||||||
|
// flags and arguments into the RunFns structure to be executed.
|
||||||
|
func TestRunFnCommand_preRunE(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
expected string
|
||||||
|
err string
|
||||||
|
path string
|
||||||
|
input io.Reader
|
||||||
|
output io.Writer
|
||||||
|
functionPaths []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "config map",
|
||||||
|
args: []string{"run", "dir", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"},
|
||||||
|
path: "dir",
|
||||||
|
expected: `
|
||||||
|
metadata:
|
||||||
|
name: function-input
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/function: |
|
||||||
|
container: {image: 'foo:bar'}
|
||||||
|
data: {a: b, c: d, e: f}
|
||||||
|
kind: ConfigMap
|
||||||
|
apiVersion: v1
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config map stdin / stdout",
|
||||||
|
args: []string{"run", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"},
|
||||||
|
input: os.Stdin,
|
||||||
|
output: os.Stdout,
|
||||||
|
expected: `
|
||||||
|
metadata:
|
||||||
|
name: function-input
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/function: |
|
||||||
|
container: {image: 'foo:bar'}
|
||||||
|
data: {a: b, c: d, e: f}
|
||||||
|
kind: ConfigMap
|
||||||
|
apiVersion: v1
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config map dry-run",
|
||||||
|
args: []string{"run", "dir", "--image", "foo:bar", "--dry-run", "--", "a=b", "c=d", "e=f"},
|
||||||
|
output: os.Stdout,
|
||||||
|
path: "dir",
|
||||||
|
expected: `
|
||||||
|
metadata:
|
||||||
|
name: function-input
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/function: |
|
||||||
|
container: {image: 'foo:bar'}
|
||||||
|
data: {a: b, c: d, e: f}
|
||||||
|
kind: ConfigMap
|
||||||
|
apiVersion: v1
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config map no args",
|
||||||
|
args: []string{"run", "dir", "--image", "foo:bar"},
|
||||||
|
path: "dir",
|
||||||
|
expected: `
|
||||||
|
metadata:
|
||||||
|
name: function-input
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/function: |
|
||||||
|
container: {image: 'foo:bar'}
|
||||||
|
data: {}
|
||||||
|
kind: ConfigMap
|
||||||
|
apiVersion: v1
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom kind",
|
||||||
|
args: []string{"run", "dir", "-i", "foo:bar", "--", "Foo", "g=h"},
|
||||||
|
path: "dir",
|
||||||
|
expected: `
|
||||||
|
metadata:
|
||||||
|
name: function-input
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/function: |
|
||||||
|
container: {image: 'foo:bar'}
|
||||||
|
data: {g: h}
|
||||||
|
kind: Foo
|
||||||
|
apiVersion: v1
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom kind '=' in data",
|
||||||
|
args: []string{"run", "dir", "-i", "foo:bar", "--", "Foo", "g=h", "i=j=k"},
|
||||||
|
path: "dir",
|
||||||
|
expected: `
|
||||||
|
metadata:
|
||||||
|
name: function-input
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/function: |
|
||||||
|
container: {image: 'foo:bar'}
|
||||||
|
data: {g: h, i: j=k}
|
||||||
|
kind: Foo
|
||||||
|
apiVersion: v1
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function paths",
|
||||||
|
args: []string{"run", "dir", "-p", "path1", "--fn-path", "path2"},
|
||||||
|
path: "dir",
|
||||||
|
functionPaths: []string{"path1", "path2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom kind with function paths",
|
||||||
|
args: []string{
|
||||||
|
"run", "dir", "-p", "path", "-i", "foo:bar", "--", "Foo", "g=h", "i=j=k"},
|
||||||
|
path: "dir",
|
||||||
|
functionPaths: []string{"path"},
|
||||||
|
expected: `
|
||||||
|
metadata:
|
||||||
|
name: function-input
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/function: |
|
||||||
|
container: {image: 'foo:bar'}
|
||||||
|
data: {g: h, i: j=k}
|
||||||
|
kind: Foo
|
||||||
|
apiVersion: v1
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config map multi args",
|
||||||
|
args: []string{"run", "dir", "dir2", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"},
|
||||||
|
err: "0 or 1 arguments supported",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config map not image",
|
||||||
|
args: []string{"run", "dir", "--", "a=b", "c=d", "e=f"},
|
||||||
|
err: "must specify --image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config map bad data",
|
||||||
|
args: []string{"run", "dir", "--image", "foo:bar", "--", "a=b", "c", "e=f"},
|
||||||
|
err: "must have keys and values separated by",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range tests {
|
||||||
|
tt := tests[i]
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
r := GetRunFnRunner("kustomize")
|
||||||
|
// Don't run the actual command
|
||||||
|
r.Command.Run = nil
|
||||||
|
r.Command.RunE = func(cmd *cobra.Command, args []string) error { return nil }
|
||||||
|
r.Command.SilenceErrors = true
|
||||||
|
r.Command.SilenceUsage = true
|
||||||
|
|
||||||
|
// hack due to https://github.com/spf13/cobra/issues/42
|
||||||
|
root := &cobra.Command{Use: "root"}
|
||||||
|
root.AddCommand(r.Command)
|
||||||
|
root.SetArgs(tt.args)
|
||||||
|
|
||||||
|
// error case
|
||||||
|
err := r.Command.Execute()
|
||||||
|
if tt.err != "" {
|
||||||
|
if !assert.Error(t, err) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !assert.Contains(t, err.Error(), tt.err) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
// don't check anything else in error case
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// non-error case
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if Input was set
|
||||||
|
if !assert.Equal(t, tt.input, r.RunFns.Input) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if Output was set
|
||||||
|
if !assert.Equal(t, tt.output, r.RunFns.Output) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if Path was set
|
||||||
|
if !assert.Equal(t, tt.path, r.RunFns.Path) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if FunctionPaths were set
|
||||||
|
if tt.functionPaths == nil {
|
||||||
|
// make Equal work against flag default
|
||||||
|
tt.functionPaths = []string{}
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, tt.functionPaths, r.RunFns.FunctionPaths) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if Functions were set
|
||||||
|
if tt.expected != "" {
|
||||||
|
if !assert.Len(t, r.RunFns.Functions, 1) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
actual := strings.TrimSpace(r.RunFns.Functions[0].MustString())
|
||||||
|
if !assert.Equal(t, strings.TrimSpace(tt.expected), actual) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user