Merge pull request #1789 from pwittrock/run

Add kyaml command for invoking container filters
This commit is contained in:
Kubernetes Prow Robot
2019-11-13 11:31:21 -08:00
committed by GitHub
24 changed files with 1578 additions and 95 deletions

View File

@@ -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{})

View File

@@ -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()) {

143
cmd/kyaml/cmd/cmdwrap.go Normal file
View File

@@ -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
}

View File

@@ -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())
}

226
cmd/kyaml/cmd/cmdxargs.go Normal file
View File

@@ -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
}

View File

@@ -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")
}

98
cmd/kyaml/cmd/run-fns.go Normal file
View File

@@ -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()
}

View File

@@ -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

51
cmd/kyaml/cmd/test/test.sh Executable file
View File

@@ -0,0 +1,51 @@
#!/bin/bash
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
cat <<End-of-message
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${NAME}
labels:
app: nginx
name: ${NAME}
spec:
replicas: ${REPLICAS}
selector:
matchLabels:
app: nginx
name: ${NAME}
template:
metadata:
labels:
app: nginx
name: ${NAME}
spec:
containers:
- name: ${NAME}
image: nginx:v1.7
ports:
- containerPort: 8080
name: http
---
apiVersion: v1
kind: Service
metadata:
name: ${NAME}
labels:
app: nginx
name: ${NAME}
spec:
ports:
# This i the port.
- port: 8080
targetPort: 8080
name: http
selector:
app: nginx
name: ${NAME}
End-of-message

View File

@@ -84,10 +84,10 @@ kubectl get all,applications,releasetracks -o yaml | kyaml tree --structure=grap
c.Flags().BoolVar(&r.env, "env", false, "print env field")
c.Flags().BoolVar(&r.all, "all", false, "print all field infos")
c.Flags().StringSliceVar(&r.fields, "field", []string{}, "print field")
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.")
c.Flags().StringVar(&r.structure, "graph-structure", "directory",
"Graph structure to use for printing the tree. may be 'directory' or 'owners'.")
@@ -101,21 +101,21 @@ func TreeCommand() *cobra.Command {
// TreeRunner contains the run function
type TreeRunner struct {
IncludeSubpackages bool
Command *cobra.Command
name bool
resources bool
ports bool
images bool
replicas bool
all bool
env bool
args bool
cmd bool
fields []string
includeReconcilers bool
excludeNonReconcilers bool
structure string
IncludeSubpackages bool
Command *cobra.Command
name bool
resources bool
ports bool
images bool
replicas bool
all bool
env bool
args bool
cmd bool
fields []string
includeLocal bool
excludeNonLocal bool
structure string
}
func (r *TreeRunner) runE(c *cobra.Command, args []string) error {
@@ -189,9 +189,9 @@ func (r *TreeRunner) runE(c *cobra.Command, args []string) error {
}
// show reconcilers in tree
fltrs := []kio.Filter{&filters.IsReconcilerFilter{
ExcludeReconcilers: !r.includeReconcilers,
IncludeNonReconcilers: !r.excludeNonReconcilers,
fltrs := []kio.Filter{&filters.IsLocalConfig{
IncludeLocalConfig: r.includeLocal,
ExcludeNonLocalConfig: r.excludeNonLocal,
}}
return handleError(c, kio.Pipeline{

View File

@@ -24,10 +24,15 @@ func TestTreeCommand_files(t *testing.T) {
}
err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(`
apiVersion: gcr.io/example/reconciler:v1
apiVersion: v1
kind: Abstraction
metadata:
name: foo
configFn:
container:
image: gcr.io/example/reconciler:v1
annotations:
config.kubernetes.io/local-config: "true"
spec:
replicas: 1
---
@@ -259,7 +264,7 @@ spec:
// fmt the files
b := &bytes.Buffer{}
r := cmd.GetTreeRunner()
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
@@ -306,10 +311,15 @@ spec:
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`
apiVersion: gcr.io/example/reconciler:v1
apiVersion: v1
kind: Abstraction
metadata:
name: foo
configFn:
container:
image: gcr.io/example/reconciler:v1
annotations:
config.kubernetes.io/local-config: "true"
spec:
replicas: 1
---
@@ -331,7 +341,7 @@ spec:
// fmt the files
b := &bytes.Buffer{}
r := cmd.GetTreeRunner()
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

View File

@@ -32,6 +32,7 @@ func main() {
root.AddCommand(cmd.FmtCommand())
root.AddCommand(cmd.MergeCommand())
root.AddCommand(cmd.CountCommand())
root.AddCommand(cmd.RunFnCommand())
root.AddCommand(&cobra.Command{Use: "merge", Long: merge2.Help})
root.AddCommand(&cobra.Command{Use: "merge3", Long: merge3.Help})

View File

@@ -17,7 +17,7 @@ import (
const (
ResourceListKind = "ResourceList"
ResourceListApiVersion = "kyaml.kustomize.dev/v1alpha1"
ResourceListApiVersion = "config.kubernetes.io/v1alpha1"
)
// ByteReadWriter reads from an input and writes to an output.

View File

@@ -34,7 +34,7 @@ i: j
}
func TestByteReader_Read_wrappedResourceßßList(t *testing.T) {
r := &ByteReader{Reader: bytes.NewBufferString(`apiVersion: kyaml.kustomize.dev/v1alpha1
r := &ByteReader{Reader: bytes.NewBufferString(`apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
functionConfig:
foo: bar

View File

@@ -45,7 +45,7 @@ g:
if !assert.NoError(t, err) {
return
}
assert.Equal(t, `apiVersion: kyaml.kustomize.dev/v1alpha1
assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- c: d # second

View File

@@ -5,12 +5,13 @@ package filters
import (
"bytes"
"fmt"
"os"
"os/exec"
"regexp"
"strings"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
@@ -24,6 +25,8 @@ import (
// The full set of environment variables from the parent process
// are passed to the container.
type ContainerFilter struct {
mountPath string
// Image is the container image to use to create a container.
Image string `yaml:"image,omitempty"`
@@ -38,6 +41,10 @@ type ContainerFilter struct {
checkInput func(string)
}
func (c *ContainerFilter) SetMountPath(path string) {
c.mountPath = path
}
// GrepFilter implements kio.GrepFilter
func (c *ContainerFilter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
// get the command to filter the Resources
@@ -89,6 +96,10 @@ func (c *ContainerFilter) getArgs() []string {
// don't make fs readonly because things like heredoc rely on writing tmp files
"--security-opt=no-new-privileges", // don't allow the user to escalate privileges
}
// mount the directory containing the function as read-only
if c.mountPath != "" {
args = append(args, "-v", fmt.Sprintf("%s:/local/:ro", c.mountPath))
}
// export the local environment vars to the container
for _, pair := range os.Environ() {
@@ -141,7 +152,8 @@ type IsReconcilerFilter struct {
func (c *IsReconcilerFilter) Filter(inputs []*yaml.RNode) ([]*yaml.RNode, error) {
var out []*yaml.RNode
for i := range inputs {
isContainerResource := GetContainerName(inputs[i]) != ""
img, _ := GetContainerName(inputs[i])
isContainerResource := img != ""
if isContainerResource && !c.ExcludeReconcilers {
out = append(out, inputs[i])
}
@@ -153,19 +165,20 @@ func (c *IsReconcilerFilter) Filter(inputs []*yaml.RNode) ([]*yaml.RNode, error)
}
// GetContainerName returns the container image for an API if one exists
func GetContainerName(n *yaml.RNode) string {
func GetContainerName(n *yaml.RNode) (string, string) {
meta, _ := n.GetMeta()
container := meta.Annotations["kyaml.kustomize.dev/container"]
// path to the function, this will be mounted into the container
path := meta.Annotations[kioutil.PathAnnotation]
container := meta.Annotations["config.kubernetes.io/container"]
if container != "" {
return container
return container, path
}
if match.MatchString(meta.ApiVersion) {
return meta.ApiVersion
image, err := n.Pipe(yaml.Lookup("metadata", "configFn", "container", "image"))
if err != nil || yaml.IsMissingOrNull(image) {
return "", path
}
return ""
return image.YNode().Value, path
}
// match specifies the set of apiVersions to recognize as being container images
var match = regexp.MustCompile(`(docker\.io|.*\.?gcr\.io)/.*(:.*)?`)

View File

@@ -5,7 +5,9 @@ package filters
import (
"bytes"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
@@ -60,6 +62,54 @@ metadata:
assert.True(t, foundKyaml)
}
func TestFilter_commandMountPath(t *testing.T) {
cfg, err := yaml.Parse(`apiversion: apps/v1
kind: Deployment
metadata:
name: foo
`)
if !assert.NoError(t, err) {
return
}
instance := &ContainerFilter{
Image: "example.com:version",
Config: cfg,
mountPath: filepath.Join("mount", "path"),
}
os.Setenv("KYAML_TEST", "FOO")
cmd, err := instance.getCommand()
if !assert.NoError(t, err) {
return
}
expected := []string{
"docker", "run",
"--rm",
"-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR",
"--network", "none",
"--user", "nobody",
"--security-opt=no-new-privileges",
"-v", fmt.Sprintf("%s:/local/:ro", filepath.Join("mount", "path")),
}
for _, e := range os.Environ() {
// the process env
expected = append(expected, "-e", strings.Split(e, "=")[0])
}
expected = append(expected, "example.com:version")
assert.Equal(t, expected, cmd.Args)
foundKyaml := false
for _, e := range cmd.Env {
// verify the command has the right environment variables to pass to the container
split := strings.Split(e, "=")
if split[0] == "KYAML_TEST" {
assert.Equal(t, "FOO", split[1])
foundKyaml = true
}
}
assert.True(t, foundKyaml)
}
func TestFilter_Filter(t *testing.T) {
cfg, err := yaml.Parse(`apiVersion: apps/v1
kind: Deployment
@@ -92,7 +142,7 @@ metadata:
args: []string{"sed", "s/Deployment/StatefulSet/g"},
checkInput: func(s string) {
called = true
if !assert.Equal(t, `apiVersion: kyaml.kustomize.dev/v1alpha1
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
@@ -174,7 +224,7 @@ metadata:
args: []string{"sh", "-c", "cat <&0"},
checkInput: func(s string) {
called = true
if !assert.Equal(t, `apiVersion: kyaml.kustomize.dev/v1alpha1
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiversion: apps/v1
@@ -226,36 +276,30 @@ metadata:
func Test_GetContainerName(t *testing.T) {
// make sure gcr.io works
n, err := yaml.Parse(`apiVersion: gcr.io/foo/bar:something
n, err := yaml.Parse(`apiVersion: v1beta1
kind: MyThing
metadata:
configFn:
container:
image: gcr.io/foo/bar:something
`)
if !assert.NoError(t, err) {
return
}
c := GetContainerName(n)
c, _ := GetContainerName(n)
assert.Equal(t, "gcr.io/foo/bar:something", c)
// make sure regional gcr.io works
n, err = yaml.Parse(`apiVersion: us.gcr.io/foo/bar:something
kind: MyThing
`)
if !assert.NoError(t, err) {
return
}
c = GetContainerName(n)
assert.Equal(t, "us.gcr.io/foo/bar:something", c)
// container from annotation
n, err = yaml.Parse(`apiVersion: v1
kind: MyThing
metadata:
annotations:
kyaml.kustomize.dev/container: gcr.io/foo/bar:something
config.kubernetes.io/container: gcr.io/foo/bar:something
`)
if !assert.NoError(t, err) {
return
}
c = GetContainerName(n)
c, _ = GetContainerName(n)
assert.Equal(t, "gcr.io/foo/bar:something", c)
// doesn't have a container
@@ -266,16 +310,6 @@ metadata:
if !assert.NoError(t, err) {
return
}
c = GetContainerName(n)
c, _ = GetContainerName(n)
assert.Equal(t, "", c)
// make sure docker.io works
n, err = yaml.Parse(`apiVersion: docker.io/foo/bar:something
kind: MyThing
`)
if !assert.NoError(t, err) {
return
}
c = GetContainerName(n)
assert.Equal(t, "docker.io/foo/bar:something", c)
}

View File

@@ -0,0 +1,38 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package filters
import (
"sigs.k8s.io/kustomize/kyaml/yaml"
)
const LocalConfigAnnotation = "config.kubernetes.io/local-config"
// IsLocalConfig filters Resources using the config.kubernetes.io/local-config annotation
type IsLocalConfig struct {
// IncludeLocalConfig will include local-config if set to true
IncludeLocalConfig bool `yaml:"includeLocalConfig,omitempty"`
// ExcludeNonLocalConfig will exclude non local-config if set to true
ExcludeNonLocalConfig bool `yaml:"excludeNonLocalConfig,omitempty"`
}
// Filter implements kio.Filter
func (c *IsLocalConfig) Filter(inputs []*yaml.RNode) ([]*yaml.RNode, error) {
var out []*yaml.RNode
for i := range inputs {
meta, err := inputs[i].GetMeta()
if err != nil {
return nil, err
}
_, local := meta.Annotations[LocalConfigAnnotation]
if local && c.IncludeLocalConfig {
out = append(out, inputs[i])
} else if !local && !c.ExcludeNonLocalConfig {
out = append(out, inputs[i])
}
}
return out, nil
}

View File

@@ -12,7 +12,7 @@ import (
)
const (
mergeSourceAnnotation = "kyaml.kustomize.dev/merge-source"
mergeSourceAnnotation = "config.kubernetes.io/merge-source"
mergeSourceOriginal = "original"
mergeSourceUpdated = "updated"
mergeSourceDest = "dest"

98
kyaml/runfn/runfn.go Normal file
View File

@@ -0,0 +1,98 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package runfn
import (
"io"
"path/filepath"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/filters"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// RunFns runs the set of configuration functions in a local directory against
// the Resources in that directory
type RunFns struct {
// Path is the path to the directory containing functions
Path string
// FunctionPaths Paths allows functions to be specified outside the configuration
// directory
FunctionPaths []string
// Output can be set to write the result to Output rather than back to the directory
Output io.Writer
// containerFilterProvider may be override by tests to fake invoking containers
containerFilterProvider func(string, string, *yaml.RNode) kio.Filter
}
// Execute runs the command
func (r RunFns) Execute() error {
// make the path absolute so it works on mac
var err error
r.Path, err = filepath.Abs(r.Path)
if err != nil {
return errors.Wrap(err)
}
// default the containerFilterProvider if it hasn't been override. Split out for testing.
(&r).init()
// identify the configuration functions in the directory
buff := &kio.PackageBuffer{}
err = kio.Pipeline{
Inputs: []kio.Reader{kio.LocalPackageReader{PackagePath: r.Path}},
Filters: []kio.Filter{&filters.IsReconcilerFilter{}},
Outputs: []kio.Writer{buff},
}.Execute()
if err != nil {
return err
}
// accept a
for i := range r.FunctionPaths {
err := kio.Pipeline{
Inputs: []kio.Reader{kio.LocalPackageReader{PackagePath: r.FunctionPaths[i]}},
Outputs: []kio.Writer{buff},
}.Execute()
if err != nil {
return err
}
}
// reconcile each local API
var fltrs []kio.Filter
for i := range buff.Nodes {
api := buff.Nodes[i]
img, path := filters.GetContainerName(api)
fltrs = append(fltrs, r.containerFilterProvider(img, path, api))
}
pkgIO := &kio.LocalPackageReadWriter{PackagePath: r.Path}
inputs := []kio.Reader{pkgIO}
var outputs []kio.Writer
if r.Output == nil {
// write back to the package
outputs = append(outputs, pkgIO)
} else {
// write to the output instead of the directory
outputs = append(outputs, kio.ByteWriter{Writer: r.Output})
}
return kio.Pipeline{Inputs: inputs, Filters: fltrs, Outputs: outputs}.Execute()
}
// init initializes the RunFns with a containerFilterProvider.
func (r *RunFns) init() {
// if containerFilterProvider hasn't been set, use the default
if r.containerFilterProvider == nil {
r.containerFilterProvider = func(image, path string, api *yaml.RNode) kio.Filter {
cf := &filters.ContainerFilter{Image: image, Config: api}
cf.SetMountPath(filepath.Join(r.Path, path))
return cf
}
}
}

248
kyaml/runfn/runfn_test.go Normal file
View File

@@ -0,0 +1,248 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package runfn
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/copyutil"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/filters"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func TestRunFns_Execute(t *testing.T) {
instance := RunFns{}
instance.init()
api, err := yaml.Parse(`apiVersion: apps/v1
kind:
`)
if !assert.NoError(t, err) {
return
}
filter := instance.containerFilterProvider("example.com:version", "", api)
assert.Equal(t, &filters.ContainerFilter{Image: "example.com:version", Config: api}, filter)
}
func TestCmd_Execute(t *testing.T) {
dir, err := ioutil.TempDir("", "kustomize-kyaml-test")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.RemoveAll(dir)
_, filename, _, ok := runtime.Caller(0)
if !assert.True(t, ok) {
t.FailNow()
}
ds, err := filepath.Abs(filepath.Join(filepath.Dir(filename), "test", "testdata"))
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, copyutil.CopyDir(ds, dir)) {
t.FailNow()
}
if !assert.NoError(t, os.Chdir(filepath.Dir(dir))) {
return
}
// write a test filter
f := `apiVersion: v1
kind: ValueReplacer
metadata:
configFn:
container:
image: gcr.io/example.com/image:version
stringMatch: Deployment
replace: StatefulSet
`
if !assert.NoError(t, ioutil.WriteFile(
filepath.Join(dir, "filter.yaml"), []byte(f), 0600)) {
return
}
instance := RunFns{
Path: dir,
containerFilterProvider: func(s, _ string, node *yaml.RNode) kio.Filter {
// parse the filter from the input
filter := yaml.YFilter{}
b := &bytes.Buffer{}
e := yaml.NewEncoder(b)
if !assert.NoError(t, e.Encode(node.YNode())) {
t.FailNow()
}
e.Close()
d := yaml.NewDecoder(b)
if !assert.NoError(t, d.Decode(&filter)) {
t.FailNow()
}
return filters.Modifier{
Filters: []yaml.YFilter{{Filter: yaml.Lookup("kind")}, filter},
}
},
}
if !assert.NoError(t, instance.Execute()) {
t.FailNow()
}
b, err := ioutil.ReadFile(
filepath.Join(dir, "java", "java-deployment.resource.yaml"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.Contains(t, string(b), "kind: StatefulSet")
}
func TestCmd_Execute_APIs(t *testing.T) {
dir, err := ioutil.TempDir("", "kustomize-kyaml-test")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.RemoveAll(dir)
_, filename, _, ok := runtime.Caller(0)
if !assert.True(t, ok) {
t.FailNow()
}
ds, err := filepath.Abs(filepath.Join(filepath.Dir(filename), "test", "testdata"))
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, copyutil.CopyDir(ds, dir)) {
t.FailNow()
}
if !assert.NoError(t, os.Chdir(filepath.Dir(dir))) {
return
}
// write a test filter
f := `apiVersion: v1
kind: ValueReplacer
metadata:
configFn:
container:
image: gcr.io/example.com/image:version
stringMatch: Deployment
replace: StatefulSet
`
tmpF, err := ioutil.TempFile("", "filter*.yaml")
if !assert.NoError(t, err) {
return
}
os.RemoveAll(tmpF.Name())
if !assert.NoError(t, ioutil.WriteFile(tmpF.Name(), []byte(f), 0600)) {
return
}
instance := RunFns{
FunctionPaths: []string{tmpF.Name()},
Path: dir,
containerFilterProvider: func(s, _ string, node *yaml.RNode) kio.Filter {
// parse the filter from the input
filter := yaml.YFilter{}
b := &bytes.Buffer{}
e := yaml.NewEncoder(b)
if !assert.NoError(t, e.Encode(node.YNode())) {
t.FailNow()
}
e.Close()
d := yaml.NewDecoder(b)
if !assert.NoError(t, d.Decode(&filter)) {
t.FailNow()
}
return filters.Modifier{
Filters: []yaml.YFilter{{Filter: yaml.Lookup("kind")}, filter},
}
},
}
err = instance.Execute()
if !assert.NoError(t, err) {
return
}
b, err := ioutil.ReadFile(
filepath.Join(dir, "java", "java-deployment.resource.yaml"))
if !assert.NoError(t, err) {
return
}
assert.Contains(t, string(b), "kind: StatefulSet")
}
func TestCmd_Execute_Stdout(t *testing.T) {
dir, err := ioutil.TempDir("", "kustomize-kyaml-test")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.RemoveAll(dir)
_, filename, _, ok := runtime.Caller(0)
if !assert.True(t, ok) {
t.FailNow()
}
ds, err := filepath.Abs(filepath.Join(filepath.Dir(filename), "test", "testdata"))
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, copyutil.CopyDir(ds, dir)) {
t.FailNow()
}
if !assert.NoError(t, os.Chdir(filepath.Dir(dir))) {
return
}
// write a test filter
f := `apiVersion: v1
kind: ValueReplacer
metadata:
configFn:
container:
image: gcr.io/example.com/image:version
stringMatch: Deployment
replace: StatefulSet
`
if !assert.NoError(t, ioutil.WriteFile(
filepath.Join(dir, "filter.yaml"), []byte(f), 0600)) {
return
}
out := &bytes.Buffer{}
instance := RunFns{
Output: out,
Path: dir,
containerFilterProvider: func(s, _ string, node *yaml.RNode) kio.Filter {
// parse the filter from the input
filter := yaml.YFilter{}
b := &bytes.Buffer{}
e := yaml.NewEncoder(b)
if !assert.NoError(t, e.Encode(node.YNode())) {
t.FailNow()
}
e.Close()
d := yaml.NewDecoder(b)
if !assert.NoError(t, d.Decode(&filter)) {
t.FailNow()
}
return filters.Modifier{
Filters: []yaml.YFilter{{Filter: yaml.Lookup("kind")}, filter},
}
},
}
if !assert.NoError(t, instance.Execute()) {
return
}
b, err := ioutil.ReadFile(
filepath.Join(dir, "java", "java-deployment.resource.yaml"))
if !assert.NoError(t, err) {
return
}
assert.NotContains(t, string(b), "kind: StatefulSet")
assert.Contains(t, out.String(), "kind: StatefulSet")
}

View File

@@ -0,0 +1,10 @@
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
labels:
app.kubernetes.io/component: undefined
app.kubernetes.io/instance: undefined
data: {}

View File

@@ -0,0 +1,36 @@
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
labels:
app: java
spec:
selector:
matchLabels:
app: java
template:
metadata:
labels:
app: java
spec:
restartPolicy: Always
containers:
- name: app
image: gcr.io/project/app:version
command:
- java
- -jar
- /app.jar
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: app-config
env:
- name: JAVA_OPTS
value: -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
-Djava.security.egd=file:/dev/./urandom
imagePullPolicy: Always
minReadySeconds: 5

View File

@@ -0,0 +1,15 @@
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
apiVersion: v1
kind: Service
metadata:
name: app
labels:
app: java
spec:
selector:
app: java
ports:
- name: "8080"
port: 8080
targetPort: 8080