diff --git a/cmd/kyaml/cmd/cat.go b/cmd/kyaml/cmd/cat.go index bfe60690b..b2ce1175b 100644 --- a/cmd/kyaml/cmd/cat.go +++ b/cmd/kyaml/cmd/cat.go @@ -27,7 +27,7 @@ func GetCatRunner() *CatRunner { kyaml cat my-dir/ # wrap Resource config from a directory in an ResourceList -kyaml cat my-dir/ --wrap-kind ResourceList --wrap-version kyaml.kustomize.dev/v1alpha1 --function-config fn.yaml +kyaml cat my-dir/ --wrap-kind ResourceList --wrap-version config.kubernetes.io/v1alpha1 --function-config fn.yaml # unwrap Resource config from a directory in an ResourceList ... | kyaml cat @@ -51,10 +51,10 @@ kyaml cat my-dir/ --wrap-kind ResourceList --wrap-version kyaml.kustomize.dev/v1 "'FoldedStyle', 'FlowStyle'.") c.Flags().BoolVar(&r.StripComments, "strip-comments", false, "remove comments from yaml.") - c.Flags().BoolVar(&r.IncludeReconcilers, "include-reconcilers", false, - "if true, include reconciler Resources in the output.") - c.Flags().BoolVar(&r.ExcludeNonReconcilers, "exclude-non-reconcilers", false, - "if true, exclude non-reconciler Resources in the output.") + c.Flags().BoolVar(&r.IncludeLocal, "include-local", false, + "if true, include local-config in the output.") + c.Flags().BoolVar(&r.ExcludeNonLocal, "exclude-non-local", false, + "if true, exclude non-local-config in the output.") r.Command = c return r } @@ -65,17 +65,17 @@ func CatCommand() *cobra.Command { // CatRunner contains the run function type CatRunner struct { - IncludeSubpackages bool - Format bool - KeepAnnotations bool - WrapKind string - WrapApiVersion string - FunctionConfig string - Styles []string - StripComments bool - IncludeReconcilers bool - ExcludeNonReconcilers bool - Command *cobra.Command + IncludeSubpackages bool + Format bool + KeepAnnotations bool + WrapKind string + WrapApiVersion string + FunctionConfig string + Styles []string + StripComments bool + IncludeLocal bool + ExcludeNonLocal bool + Command *cobra.Command } func (r *CatRunner) runE(c *cobra.Command, args []string) error { @@ -105,9 +105,9 @@ func (r *CatRunner) runE(c *cobra.Command, args []string) error { } var fltr []kio.Filter // don't include reconcilers - fltr = append(fltr, &filters.IsReconcilerFilter{ - ExcludeReconcilers: !r.IncludeReconcilers, - IncludeNonReconcilers: !r.ExcludeNonReconcilers, + fltr = append(fltr, &filters.IsLocalConfig{ + IncludeLocalConfig: r.IncludeLocal, + ExcludeNonLocalConfig: r.ExcludeNonLocal, }) if r.Format { fltr = append(fltr, filters.FormatFilter{}) diff --git a/cmd/kyaml/cmd/cat_test.go b/cmd/kyaml/cmd/cat_test.go index 755e52186..36a9133f6 100644 --- a/cmd/kyaml/cmd/cat_test.go +++ b/cmd/kyaml/cmd/cat_test.go @@ -47,10 +47,15 @@ spec: return } err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(` -apiVersion: gcr.io/example/image:version +apiVersion: v1 kind: Abstraction metadata: name: foo + configFn: + container: + image: gcr.io/example/reconciler:v1 + annotations: + config.kubernetes.io/local-config: "true" spec: replicas: 3 --- @@ -149,10 +154,15 @@ spec: return } err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(` -apiVersion: gcr.io/example/image:version +apiVersion: v1 kind: Abstraction metadata: name: foo + configFn: + container: + image: gcr.io/example/image:version + annotations: + config.kubernetes.io/local-config: "true" spec: replicas: 3 --- @@ -173,7 +183,7 @@ spec: // fmt the files b := &bytes.Buffer{} r := cmd.GetCatRunner() - r.Command.SetArgs([]string{d, "--include-reconcilers"}) + r.Command.SetArgs([]string{d, "--include-local"}) r.Command.SetOut(b) if !assert.NoError(t, r.Command.Execute()) { return @@ -202,13 +212,17 @@ spec: selector: app: nginx --- -apiVersion: gcr.io/example/image:version +apiVersion: v1 kind: Abstraction metadata: name: foo annotations: + config.kubernetes.io/local-config: "true" config.kubernetes.io/package: . config.kubernetes.io/path: f2.yaml + configFn: + container: + image: gcr.io/example/image:version spec: replicas: 3 --- @@ -259,10 +273,15 @@ spec: return } err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(` -apiVersion: gcr.io/example/image:version +apiVersion: v1 kind: Abstraction metadata: name: foo + annotations: + config.kubernetes.io/local-config: "true" + configFn: + container: + image: gcr.io/example/reconciler:v1 spec: replicas: 3 --- @@ -283,19 +302,23 @@ spec: // fmt the files b := &bytes.Buffer{} r := cmd.GetCatRunner() - r.Command.SetArgs([]string{d, "--include-reconcilers", "--exclude-non-reconcilers"}) + r.Command.SetArgs([]string{d, "--include-local", "--exclude-non-local"}) r.Command.SetOut(b) if !assert.NoError(t, r.Command.Execute()) { return } - if !assert.Equal(t, `apiVersion: gcr.io/example/image:version + if !assert.Equal(t, `apiVersion: v1 kind: Abstraction metadata: name: foo annotations: + config.kubernetes.io/local-config: "true" config.kubernetes.io/package: . config.kubernetes.io/path: f2.yaml + configFn: + container: + image: gcr.io/example/reconciler:v1 spec: replicas: 3 `, b.String()) { diff --git a/cmd/kyaml/cmd/cmdwrap.go b/cmd/kyaml/cmd/cmdwrap.go new file mode 100644 index 000000000..389fbeaab --- /dev/null +++ b/cmd/kyaml/cmd/cmdwrap.go @@ -0,0 +1,143 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "bytes" + "io" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/kio/filters" +) + +// GetWrapRunner returns a command runner. +func GetWrapRunner() *WrapRunner { + r := &WrapRunner{} + c := &cobra.Command{ + Use: "wrap CMD...", + Short: "Wrap an executable so it implements the config fn interface", + Long: `Wrap an executable so it implements the config fn interface + +wrap simplifies writing config functions by: + +- invoking an executable command converting an input ResourceList into environment +- merging the output onto the original input as a set of patches +- setting filenames on any Resources missing them + +config function authors may use wrap by using it to invoke a command from a container image + +The following are equivalent: + + kyaml wrap -- CMD + + kyaml xargs -- CMD | kyaml merge | kyaml fmt --set-filenames + +Environment Variables: + + KUST_OVERRIDE_DIR: + + Path to a directory containing patches to apply to after merging. +`, + Example: ` + +`, + RunE: r.runE, + SilenceUsage: true, + FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true}, + Args: cobra.MinimumNArgs(1), + } + r.Command = c + r.XArgs = GetXArgsRunner() + c.Flags().BoolVar(&r.XArgs.EnvOnly, + "env-only", true, "only set env vars, not arguments.") + c.Flags().StringVar(&r.XArgs.WrapKind, + "wrap-kind", "List", "wrap the input xargs give to the command in this type.") + c.Flags().StringVar(&r.XArgs.WrapVersion, + "wrap-version", "v1", "wrap the input xargs give to the command in this type.") + return r +} + +// WrapRunner contains the run function +type WrapRunner struct { + Command *cobra.Command + XArgs *XArgsRunner + getEnv func(key string) string +} + +const ( + KustMergeEnv = "KUST_MERGE" + KustOverrideDirEnv = "KUST_OVERRIDE_DIR" +) + +func WrapCommand() *cobra.Command { + return GetWrapRunner().Command +} + +func (r *WrapRunner) runE(c *cobra.Command, args []string) error { + if r.getEnv == nil { + r.getEnv = os.Getenv + } + xargsIn := &bytes.Buffer{} + if _, err := io.Copy(xargsIn, c.InOrStdin()); err != nil { + return err + } + mergeInput := bytes.NewBuffer(xargsIn.Bytes()) + // Run the command + xargsOut := &bytes.Buffer{} + r.XArgs.Command.SetArgs(args) + r.XArgs.Command.SetIn(xargsIn) + r.XArgs.Command.SetOut(xargsOut) + r.XArgs.Command.SetErr(os.Stderr) + if err := r.XArgs.Command.Execute(); err != nil { + return err + } + + // merge the results + buff := &kio.PackageBuffer{} + + var fltrs []kio.Filter + var inputs []kio.Reader + if r.getEnv(KustMergeEnv) == "" || r.getEnv(KustMergeEnv) == "true" || r.getEnv(KustMergeEnv) == "1" { + inputs = append(inputs, &kio.ByteReader{Reader: mergeInput}) + fltrs = append(fltrs, &filters.MergeFilter{}) + } + inputs = append(inputs, &kio.ByteReader{Reader: xargsOut}) + + if err := (kio.Pipeline{Inputs: inputs, Filters: fltrs, Outputs: []kio.Writer{buff}}). + Execute(); err != nil { + return err + } + + inputs, fltrs = []kio.Reader{buff}, nil + if r.getEnv(KustOverrideDirEnv) != "" { + // merge the overrides on top of the output + fltrs = append(fltrs, filters.MergeFilter{}) + inputs = append(inputs, + kio.LocalPackageReader{ + OmitReaderAnnotations: true, // don't set path annotations, as they would override + PackagePath: r.getEnv(KustOverrideDirEnv)}) + } + fltrs = append(fltrs, + &filters.FileSetter{ + FilenamePattern: filepath.Join("config", filters.DefaultFilenamePattern)}, + &filters.FormatFilter{}) + + err := kio.Pipeline{ + Inputs: inputs, + Filters: fltrs, + Outputs: []kio.Writer{kio.ByteWriter{ + Sort: true, + KeepReaderAnnotations: true, + Writer: c.OutOrStdout(), + WrappingKind: kio.ResourceListKind, + WrappingApiVersion: kio.ResourceListApiVersion}}}.Execute() + if err != nil { + return err + } + + return nil +} diff --git a/cmd/kyaml/cmd/cmdwrap_test.go b/cmd/kyaml/cmd/cmdwrap_test.go new file mode 100644 index 000000000..f6785c5f6 --- /dev/null +++ b/cmd/kyaml/cmd/cmdwrap_test.go @@ -0,0 +1,307 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "bytes" + "path/filepath" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" +) + +const ( + input = `apiVersion: config.kubernetes.io/v1alpha1 +kind: ResourceList +functionConfig: + metadata: + name: test + spec: + replicas: 11 +items: +- apiVersion: apps/v1 + kind: Deployment + metadata: + name: test + labels: + app: nginx + name: test + spec: + replicas: 5 + selector: + matchLabels: + app: nginx + name: test + template: + metadata: + labels: + app: nginx + name: test + spec: + containers: + - name: test + image: nginx:v1.7 + ports: + - containerPort: 8080 + name: http + resources: + limits: + cpu: 500m +- apiVersion: v1 + kind: Service + metadata: + name: test + labels: + app: nginx + name: test + spec: + ports: + # This i the port. + - port: 8080 + targetPort: 8080 + name: http + selector: + app: nginx + name: test +` + + output = `apiVersion: config.kubernetes.io/v1alpha1 +kind: ResourceList +items: +- apiVersion: apps/v1 + kind: Deployment + metadata: + name: test + labels: + name: test + app: nginx + annotations: + config.kubernetes.io/index: 0 + config.kubernetes.io/path: config/test_deployment.yaml + spec: + replicas: 11 + selector: + matchLabels: + name: test + app: nginx + template: + metadata: + labels: + name: test + app: nginx + spec: + containers: + - name: test + image: nginx:v1.7 + ports: + - name: http + containerPort: 8080 + resources: + limits: + cpu: 500m +- apiVersion: v1 + kind: Service + metadata: + name: test + labels: + name: test + app: nginx + annotations: + config.kubernetes.io/index: 0 + config.kubernetes.io/path: config/test_service.yaml + spec: + selector: + name: test + app: nginx + ports: + - name: http + # This i the port. + port: 8080 + targetPort: 8080 +` + + outputNoMerge = `apiVersion: config.kubernetes.io/v1alpha1 +kind: ResourceList +items: +- apiVersion: apps/v1 + kind: Deployment + metadata: + name: test + labels: + name: test + app: nginx + annotations: + config.kubernetes.io/index: 0 + config.kubernetes.io/path: config/test_deployment.yaml + spec: + replicas: 11 + selector: + matchLabels: + name: test + app: nginx + template: + metadata: + labels: + name: test + app: nginx + spec: + containers: + - name: test + image: nginx:v1.7 + ports: + - name: http + containerPort: 8080 +- apiVersion: v1 + kind: Service + metadata: + name: test + labels: + name: test + app: nginx + annotations: + config.kubernetes.io/index: 0 + config.kubernetes.io/path: config/test_service.yaml + spec: + selector: + name: test + app: nginx + ports: + - name: http + # This i the port. + port: 8080 + targetPort: 8080 +` + + outputOverride = `apiVersion: config.kubernetes.io/v1alpha1 +kind: ResourceList +items: +- apiVersion: apps/v1 + kind: Deployment + metadata: + name: test + labels: + name: test + app: nginx + annotations: + config.kubernetes.io/index: 0 + config.kubernetes.io/path: config/test_deployment.yaml + spec: + replicas: 11 + selector: + matchLabels: + name: test + app: nginx + template: + metadata: + labels: + name: test + app: nginx + spec: + containers: + - name: test + image: nginx:v1.9 + ports: + - name: http + containerPort: 8080 + resources: + limits: + cpu: 500m +- apiVersion: v1 + kind: Service + metadata: + name: test + labels: + name: test + app: nginx + annotations: + config.kubernetes.io/index: 0 + config.kubernetes.io/path: config/test_service.yaml + spec: + selector: + name: test + app: nginx + ports: + - name: http + # This i the port. + port: 8080 + targetPort: 8080 +` +) + +func TestCmd_wrap(t *testing.T) { + _, dir, _, ok := runtime.Caller(0) + if !assert.True(t, ok) { + t.FailNow() + } + dir = filepath.Dir(dir) + + c := GetWrapRunner() + c.Command.SetIn(bytes.NewBufferString(input)) + out := &bytes.Buffer{} + c.Command.SetOut(out) + args := []string{"--", filepath.Join(dir, "test", "test.sh")} + c.Command.SetArgs(args) + c.XArgs.Args = args + + if !assert.NoError(t, c.Command.Execute()) { + t.FailNow() + } + + assert.Equal(t, output, out.String()) +} + +func TestCmd_wrapNoMerge(t *testing.T) { + _, dir, _, ok := runtime.Caller(0) + if !assert.True(t, ok) { + t.FailNow() + } + dir = filepath.Dir(dir) + + c := GetWrapRunner() + c.getEnv = func(key string) string { + if key == KustMergeEnv { + return "false" + } + return "" + } + c.Command.SetIn(bytes.NewBufferString(input)) + out := &bytes.Buffer{} + c.Command.SetOut(out) + args := []string{"--", filepath.Join(dir, "test", "test.sh")} + c.Command.SetArgs(args) + c.XArgs.Args = args + if !assert.NoError(t, c.Command.Execute()) { + t.FailNow() + } + + assert.Equal(t, outputNoMerge, out.String()) +} + +func TestCmd_wrapOverride(t *testing.T) { + _, dir, _, ok := runtime.Caller(0) + if !assert.True(t, ok) { + t.FailNow() + } + dir = filepath.Dir(dir) + + c := GetWrapRunner() + c.getEnv = func(key string) string { + if key == KustOverrideDirEnv { + return filepath.Join(dir, "test") + } + return "" + } + c.Command.SetIn(bytes.NewBufferString(input)) + out := &bytes.Buffer{} + c.Command.SetOut(out) + args := []string{"--", filepath.Join(dir, "test", "test.sh")} + c.Command.SetArgs(args) + c.XArgs.Args = args + if !assert.NoError(t, c.Command.Execute()) { + t.FailNow() + } + + assert.Equal(t, outputOverride, out.String()) +} diff --git a/cmd/kyaml/cmd/cmdxargs.go b/cmd/kyaml/cmd/cmdxargs.go new file mode 100644 index 000000000..d281dda82 --- /dev/null +++ b/cmd/kyaml/cmd/cmdxargs.go @@ -0,0 +1,226 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "fmt" + "os" + "os/exec" + "strings" + "unicode" + + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +// GetXArgsRunner returns a command runner. +func GetXArgsRunner() *XArgsRunner { + r := &XArgsRunner{} + c := &cobra.Command{ + Use: "xargs -- CMD...", + Short: "Convert functionConfig to commandline flags and envs", + Long: `Convert functionConfig to commandline flags and envs. + +xargs reads a ResourceList from stdin and parses the functionConfig field. xargs then +reads each of the fields under .spec and parses them as flags. If the fields have non-scalar +values, then xargs encoded the values as yaml strings. + + CMD: + The command to run and pass the functionConfig as arguments. +`, + Example: ` +# given this example functionConfig in config.yaml +kind: Foo +spec: + flag1: value1 + flag2: value2 +items: +- 2 +- 1 + +# this command: +$ kyaml cat pkg/ --function-config config.yaml --wrap-kind ResourceList | kyaml run-fns xargs -- app + +# is equivalent to this command: +$ kyaml cat pkg/ --function-config config.yaml --wrap-kind ResourceList | app --flag1=value1 --flag2=value2 2 1 + +# echo: prints the app arguments +$ kyaml cat pkg/ --function-config config.yaml --wrap-kind ResourceList | kyaml reconcile xargs -- echo +--flag1=value1 --flag2=value2 2 1 + +# env: prints the app env +$ kyaml cat pkg/ --function-config config.yaml --wrap-kind ResourceList | kyaml reconcile xargs -- env + +# cat: prints the app stdin -- prints the package contents and functionConfig wrapped in a +# ResourceList +$ kyaml cat pkg/ --function-config config.yaml --wrap-kind ResourceList | kyaml reconcile xargs --no-flags -- env + +`, + RunE: r.runE, + SilenceUsage: true, + FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true}, + Args: cobra.MinimumNArgs(1), + } + r.Command = c + r.Command.Flags().BoolVar(&r.EnvOnly, "env-only", false, "only add env vars, not flags") + c.Flags().StringVar(&r.WrapKind, "wrap-kind", "List", "wrap the input xargs give to the command in this type.") + c.Flags().StringVar(&r.WrapVersion, "wrap-version", "v1", "wrap the input xargs give to the command in this type.") + return r +} + +// Runner contains the run function +type XArgsRunner struct { + Command *cobra.Command + Args []string + EnvOnly bool + WrapKind string + WrapVersion string +} + +func XArgsCommand() *cobra.Command { + return GetXArgsRunner().Command +} + +func (r *XArgsRunner) runE(c *cobra.Command, _ []string) error { + if len(r.Args) == 0 { + r.Args = os.Args + } + cmdIndex := -1 + for i := range r.Args { + if r.Args[i] == "--" { + cmdIndex = i + 1 + break + } + } + if cmdIndex < 0 { + return fmt.Errorf("must specify -- before command") + } + r.Args = r.Args[cmdIndex:] + run := exec.Command(r.Args[0]) + + if len(r.Args) > 1 { + r.Args = r.Args[cmdIndex+1:] + } else { + r.Args = []string{} + } + run.Stdout = c.OutOrStdout() + run.Stderr = c.ErrOrStderr() + + rw := &kio.ByteReadWriter{ + Reader: c.InOrStdin(), + } + nodes, err := rw.Read() + if err != nil { + return err + } + + env := os.Environ() + + // append the config to the flags + if err = func() error { + if rw.FunctionConfig == nil { + return nil + } + str, err := rw.FunctionConfig.String() + if err != nil { + return err + } + // add the API object to the env + env = append(env, fmt.Sprintf("KUST_FUNCTION_CONFIG=%s", str)) + + // parse the fields + meta := rw.FunctionConfig.Field("metadata") + if meta != nil { + err = meta.Value.VisitFields(func(node *yaml.MapNode) error { + if !r.EnvOnly { + r.Args = append(r.Args, fmt.Sprintf("--%s=%s", + node.Key.YNode().Value, parseYNode(node.Value.YNode()))) + } + env = append(env, fmt.Sprintf("%s=%s", strings.ToUpper(node.Key.YNode().Value), + node.Value.YNode().Value)) + return nil + }) + if err != nil { + return err + } + } + + spec := rw.FunctionConfig.Field("spec") + if spec != nil { + err = spec.Value.VisitFields(func(node *yaml.MapNode) error { + if !r.EnvOnly { + r.Args = append(r.Args, fmt.Sprintf("--%s=%s", + node.Key.YNode().Value, parseYNode(node.Value.YNode()))) + } + env = append(env, fmt.Sprintf("%s=%s", strings.ToUpper(node.Key.YNode().Value), + node.Value.YNode().Value)) + return nil + }) + if err != nil { + return err + } + } + + if !r.EnvOnly { + items := rw.FunctionConfig.Field("items") + if items != nil { + err = items.Value.VisitElements(func(node *yaml.RNode) error { + r.Args = append(r.Args, parseYNode(node.YNode())) + return nil + }) + if err != nil { + return err + } + } + } + + if r.WrapKind != "" { + if kind := rw.FunctionConfig.Field("kind"); !yaml.IsFieldEmpty(kind) { + kind.Value.YNode().Value = r.WrapKind + } + rw.WrappingKind = r.WrapKind + } + if r.WrapVersion != "" { + if version := rw.FunctionConfig.Field("apiVersion"); !yaml.IsFieldEmpty(version) { + version.Value.YNode().Value = r.WrapVersion + } + rw.WrappingApiVersion = r.WrapVersion + } + return nil + }(); err != nil { + return err + } + run.Args = append(run.Args, r.Args...) + run.Env = append(run.Env, env...) + + // write ResourceList to stdin + if err = func() error { + in, err := run.StdinPipe() + if err != nil { + return err + } + defer in.Close() + rw.Writer = in + if r.WrapKind != kio.ResourceListKind { + rw.FunctionConfig = nil + } + return rw.Write(nodes) + }(); err != nil { + return err + } + + return run.Run() +} + +func parseYNode(node *yaml.Node) string { + node.Value = strings.TrimSpace(node.Value) + for _, b := range node.Value { + if unicode.IsSpace(b) { + // wrap in '' -- contains whitespace + return fmt.Sprintf("'%s'", node.Value) + } + } + return node.Value +} diff --git a/cmd/kyaml/cmd/cmdxargs_test.go b/cmd/kyaml/cmd/cmdxargs_test.go new file mode 100644 index 000000000..f20265d3a --- /dev/null +++ b/cmd/kyaml/cmd/cmdxargs_test.go @@ -0,0 +1,117 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package cmd_test + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/cmd/kyaml/cmd" +) + +const ( + flagsInput = `kind: ResourceList +items: +- apiVersion: apps/v1 + kind: Deployment + spec: + template: + spec: + containers: + - name: nginx + image: nginx +- apiVersion: apps/v1 + kind: Service + spec: {} +functionConfig: + kind: Foo + spec: + a: b + c: d + e: f + items: + - 1 + - 3 + - 2 + - 4 +` + + resourceInput = `apiVersion: config.kubernetes.io/v1alpha1 +kind: ResourceList +items: +- apiVersion: apps/v1 + kind: Deployment + spec: + template: + spec: + containers: + - name: nginx + image: nginx +- apiVersion: apps/v1 + kind: Service + spec: {} +functionConfig: + kind: Foo +` + + resourceOutput = `apiVersion: v1 +kind: List +items: +- apiVersion: apps/v1 + kind: Deployment + spec: + template: + spec: + containers: + - name: nginx + image: nginx +- apiVersion: apps/v1 + kind: Service + spec: {} +` +) + +func TestXArgs_flags(t *testing.T) { + c := cmd.GetXArgsRunner() + c.Command.SetIn(bytes.NewBufferString(flagsInput)) + out := &bytes.Buffer{} + c.Command.SetOut(out) + c.Command.SetArgs([]string{"--", "echo"}) + + c.Args = []string{"--", "echo"} + if !assert.NoError(t, c.Command.Execute()) { + t.FailNow() + } + assert.Equal(t, `--a=b --c=d --e=f 1 3 2 4 +`, out.String()) +} + +func TestXArgs_input(t *testing.T) { + c := cmd.GetXArgsRunner() + c.Command.SetIn(bytes.NewBufferString(resourceInput)) + out := &bytes.Buffer{} + c.Command.SetOut(out) + c.Command.SetArgs([]string{"--", "cat"}) + + c.Args = []string{"--", "cat"} + if !assert.NoError(t, c.Command.Execute()) { + t.FailNow() + } + assert.Equal(t, resourceOutput, out.String()) +} + +func TestCmd_env(t *testing.T) { + c := cmd.GetXArgsRunner() + c.Command.SetIn(bytes.NewBufferString(flagsInput)) + out := &bytes.Buffer{} + c.Command.SetOut(out) + c.Command.SetArgs([]string{"--env-only", "--", "env"}) + + c.Args = []string{"--", "env"} + if !assert.NoError(t, c.Command.Execute()) { + t.FailNow() + } + assert.Contains(t, out.String(), "\nA=b\nC=d\nE=f\n") +} diff --git a/cmd/kyaml/cmd/run-fns.go b/cmd/kyaml/cmd/run-fns.go new file mode 100644 index 000000000..94431dac8 --- /dev/null +++ b/cmd/kyaml/cmd/run-fns.go @@ -0,0 +1,98 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/kyaml/runfn" +) + +// GetCatRunner returns a RunFnRunner. +func GetRunFnRunner() *RunFnRunner { + r := &RunFnRunner{} + c := &cobra.Command{ + Use: "run-fns DIR", + Short: "Apply config functions to Resources.", + Long: `Apply config functions to Resources. + +run-fns sequentially invokes all config functions in the directly, providing Resources +in the directory as input to the first function, and writing the output of the last +function back to the directory. + +The ordering of functions is determined by the order they are encountered when walking the +directory. To clearly specify an ordering of functions, multiple functions may be +declared in the same file, separated by '---' (the functions will be invoked in the +order they appear in the file). + +### Arguments: + + DIR: + Path to local directory. + + +### Config Functions: + + Config functions are specified as Kubernetes types containing a metadata.configFn.container.image + field. This fields tells run-fns how to invoke the container. + + Example config function: + + # in file example/fn.yaml + apiVersion: fn.example.com/v1beta1 + kind: ExampleFunctionKind + metadata: + configFn: + container: + # function is invoked as a container running this image + image: gcr.io/example/examplefunction:v1.0.1 + annotations: + config.kubernetes.io/local-config: "true" # tools should ignore this + spec: + configField: configValue + + In the preceding example, 'kyaml run-fns example/' would identify the function by + the metadata.configFn field. It would then write all Resources in the directory to + a container stdin (running the gcr.io/example/examplefunction:v1.0.1 image). It + would then writer the container stdout back to example/, replacing the directory + file contents. +`, + Example: ` +kyaml run-fns example/ +`, + RunE: r.runE, + Args: cobra.ExactArgs(1), + } + c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true, + "also print resources from subpackages.") + r.Command = c + r.Command.Flags().BoolVar( + &r.DryRun, "dry-run", false, "print results to stdout") + r.Command.Flags().StringSliceVar( + &r.FnPaths, "fn-path", []string{}, + "directories containing functions without configuration") + r.Command.AddCommand(XArgsCommand()) + r.Command.AddCommand(WrapCommand()) + return r +} + +func RunFnCommand() *cobra.Command { + return GetRunFnRunner().Command +} + +// RunFnRunner contains the run function +type RunFnRunner struct { + IncludeSubpackages bool + Command *cobra.Command + DryRun bool + FnPaths []string +} + +func (r *RunFnRunner) runE(c *cobra.Command, args []string) error { + rec := runfn.RunFns{Path: args[0], FunctionPaths: r.FnPaths} + if r.DryRun { + rec.Output = c.OutOrStdout() + } + return rec.Execute() + +} diff --git a/cmd/kyaml/cmd/test/override.yaml b/cmd/kyaml/cmd/test/override.yaml new file mode 100644 index 000000000..829d48ad8 --- /dev/null +++ b/cmd/kyaml/cmd/test/override.yaml @@ -0,0 +1,15 @@ +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + + + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test +spec: + template: + spec: + containers: + - name: test + image: nginx:v1.9 diff --git a/cmd/kyaml/cmd/test/test.sh b/cmd/kyaml/cmd/test/test.sh new file mode 100755 index 000000000..c32b86108 --- /dev/null +++ b/cmd/kyaml/cmd/test/test.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + + + + +cat <