refactor cmd/config to internal

This commit is contained in:
Phillip Wittrock
2019-12-12 10:37:52 -08:00
parent 634c780d1b
commit 98d2be5550
31 changed files with 82 additions and 81 deletions

View File

@@ -0,0 +1,135 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands
import (
"fmt"
"os"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"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"
)
// GetCatRunner returns a command CatRunner.
func GetCatRunner(name string) *CatRunner {
r := &CatRunner{}
c := &cobra.Command{
Use: "cat DIR...",
Short: commands.CatShort,
Long: commands.CatLong,
Example: commands.CatExamples,
RunE: r.runE,
}
fixDocs(name, c)
c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true,
"also print resources from subpackages.")
c.Flags().BoolVar(&r.Format, "format", true,
"format resource config yaml before printing.")
c.Flags().BoolVar(&r.KeepAnnotations, "annotate", false,
"annotate resources with their file origins.")
c.Flags().StringVar(&r.WrapKind, "wrap-kind", "",
"if set, wrap the output in this list type kind.")
c.Flags().StringVar(&r.WrapApiVersion, "wrap-version", "",
"if set, wrap the output in this list type apiVersion.")
c.Flags().StringVar(&r.FunctionConfig, "function-config", "",
"path to function config to put in ResourceList -- only if wrapped in a ResourceList.")
c.Flags().StringSliceVar(&r.Styles, "style", []string{},
"yaml styles to apply. may be 'TaggedStyle', 'DoubleQuotedStyle', 'LiteralStyle', "+
"'FoldedStyle', 'FlowStyle'.")
c.Flags().BoolVar(&r.StripComments, "strip-comments", false,
"remove comments from yaml.")
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.OutputDest, "dest", "",
"if specified, write output to a file rather than stdout")
r.Command = c
return r
}
func CatCommand(name string) *cobra.Command {
return GetCatRunner(name).Command
}
// CatRunner contains the run function
type CatRunner struct {
IncludeSubpackages bool
Format bool
KeepAnnotations bool
WrapKind string
WrapApiVersion string
FunctionConfig string
OutputDest string
Styles []string
StripComments bool
IncludeLocal bool
ExcludeNonLocal bool
Command *cobra.Command
}
func (r *CatRunner) runE(c *cobra.Command, args []string) error {
// if there is a function-config specified, emit it
var functionConfig *yaml.RNode
if r.FunctionConfig != "" {
configs, err := kio.LocalPackageReader{PackagePath: r.FunctionConfig,
OmitReaderAnnotations: !r.KeepAnnotations}.Read()
if err != nil {
return err
}
if len(configs) != 1 {
return fmt.Errorf("expected exactly 1 functionConfig, found %d", len(configs))
}
functionConfig = configs[0]
}
var inputs []kio.Reader
for _, a := range args {
inputs = append(inputs, kio.LocalPackageReader{
PackagePath: a,
IncludeSubpackages: r.IncludeSubpackages,
})
}
if len(inputs) == 0 {
inputs = append(inputs, &kio.ByteReader{Reader: c.InOrStdin()})
}
var fltr []kio.Filter
// don't include reconcilers
fltr = append(fltr, &filters.IsLocalConfig{
IncludeLocalConfig: r.IncludeLocal,
ExcludeNonLocalConfig: r.ExcludeNonLocal,
})
if r.Format {
fltr = append(fltr, filters.FormatFilter{})
}
if r.StripComments {
fltr = append(fltr, filters.StripCommentsFilter{})
}
var out = c.OutOrStdout()
if r.OutputDest != "" {
o, err := os.Create(r.OutputDest)
if err != nil {
return handleError(c, errors.Wrap(err))
}
defer o.Close()
out = o
}
var outputs []kio.Writer
outputs = append(outputs, kio.ByteWriter{
Writer: out,
KeepReaderAnnotations: r.KeepAnnotations,
WrappingKind: r.WrapKind,
WrappingAPIVersion: r.WrapApiVersion,
FunctionConfig: functionConfig,
Style: yaml.GetStyle(r.Styles...),
})
return handleError(c, kio.Pipeline{Inputs: inputs, Filters: fltr, Outputs: outputs}.Execute())
}

View File

@@ -0,0 +1,570 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands_test
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
)
// TODO(pwittrock): write tests for reading / writing ResourceLists
func TestCmd_files(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-cat-test")
if !assert.NoError(t, err) {
return
}
defer os.RemoveAll(d)
err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(`
kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
spec:
selector:
app: nginx
`), 0600)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`
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
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`), 0600)
if !assert.NoError(t, err) {
return
}
// fmt the files
b := &bytes.Buffer{}
r := commands.GetCatRunner("")
r.Command.SetArgs([]string{d})
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
return
}
if !assert.Equal(t, `kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
selector:
app: nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
labels:
app: nginx
annotations:
app: nginx
config.kubernetes.io/package: .
config.kubernetes.io/path: f2.yaml
spec:
replicas: 3
`, b.String()) {
return
}
}
func TestCmd_filesWithReconcilers(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-cat-test")
if !assert.NoError(t, err) {
return
}
defer os.RemoveAll(d)
err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(`
kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
spec:
selector:
app: nginx
`), 0600)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`
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
---
kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`), 0600)
if !assert.NoError(t, err) {
return
}
// fmt the files
b := &bytes.Buffer{}
r := commands.GetCatRunner("")
r.Command.SetArgs([]string{d, "--include-local"})
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
return
}
if !assert.Equal(t, `kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
selector:
app: nginx
---
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
---
kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
config.kubernetes.io/package: .
config.kubernetes.io/path: f2.yaml
spec:
replicas: 3
`, b.String()) {
return
}
}
func TestCmd_filesWithoutNonReconcilers(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-cat-test")
if !assert.NoError(t, err) {
return
}
defer os.RemoveAll(d)
err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(`
kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
spec:
selector:
app: nginx
`), 0600)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`
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
---
kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`), 0600)
if !assert.NoError(t, err) {
return
}
// fmt the files
b := &bytes.Buffer{}
r := commands.GetCatRunner("")
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: 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()) {
return
}
}
func TestCmd_outputTruncateFile(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-cat-test")
if !assert.NoError(t, err) {
return
}
defer os.RemoveAll(d)
err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(`
kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
spec:
selector:
app: nginx
`), 0600)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`
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
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`), 0600)
if !assert.NoError(t, err) {
return
}
f, err := ioutil.TempFile("", "kustomize-cat-test-dest")
if !assert.NoError(t, err) {
return
}
defer os.RemoveAll(d)
// fmt the files
b := &bytes.Buffer{}
r := commands.GetCatRunner("")
r.Command.SetArgs([]string{d, "--dest", f.Name()})
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
return
}
if !assert.Equal(t, ``, b.String()) {
return
}
actual, err := ioutil.ReadFile(f.Name())
if !assert.NoError(t, err) {
return
}
if !assert.Equal(t, `kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
selector:
app: nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
labels:
app: nginx
annotations:
app: nginx
config.kubernetes.io/package: .
config.kubernetes.io/path: f2.yaml
spec:
replicas: 3
`, string(actual)) {
return
}
}
func TestCmd_outputCreateFile(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-cat-test")
if !assert.NoError(t, err) {
return
}
defer os.RemoveAll(d)
err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(`
kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
spec:
selector:
app: nginx
`), 0600)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`
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
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`), 0600)
if !assert.NoError(t, err) {
return
}
d2, err := ioutil.TempDir("", "kustomize-cat-test-dest")
if !assert.NoError(t, err) {
return
}
defer os.RemoveAll(d2)
f := filepath.Join(d2, "output.yaml")
// fmt the files
b := &bytes.Buffer{}
r := commands.GetCatRunner("")
r.Command.SetArgs([]string{d, "--dest", f})
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
return
}
if !assert.Equal(t, ``, b.String()) {
return
}
actual, err := ioutil.ReadFile(f)
if !assert.NoError(t, err) {
return
}
if !assert.Equal(t, `kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
selector:
app: nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
labels:
app: nginx
annotations:
app: nginx
config.kubernetes.io/package: .
config.kubernetes.io/path: f2.yaml
spec:
replicas: 3
`, string(actual)) {
return
}
}

View File

@@ -0,0 +1,139 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands
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()
return handleError(c, err)
}

View File

@@ -0,0 +1,307 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands
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())
}

View File

@@ -0,0 +1,226 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands
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 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 handleError(c, 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 commands_test
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
)
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 := commands.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 := commands.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 := commands.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")
}

View File

@@ -0,0 +1,88 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//
package commands
import (
"fmt"
"sort"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/sets"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func GetCountRunner(name string) *CountRunner {
r := &CountRunner{}
c := &cobra.Command{
Use: "count DIR...",
Short: commands.CountShort,
Long: commands.CountLong,
Example: commands.CountExamples,
RunE: r.runE,
}
fixDocs(name, c)
c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true,
"also print resources from subpackages.")
c.Flags().BoolVar(&r.Kind, "kind", true,
"count resources by kind.")
r.Command = c
return r
}
func CountCommand(name string) *cobra.Command {
return GetCountRunner(name).Command
}
// CountRunner contains the run function
type CountRunner struct {
IncludeSubpackages bool
Kind bool
Command *cobra.Command
}
func (r *CountRunner) runE(c *cobra.Command, args []string) error {
var inputs []kio.Reader
for _, a := range args {
inputs = append(inputs, kio.LocalPackageReader{
PackagePath: a,
IncludeSubpackages: r.IncludeSubpackages,
})
}
if len(inputs) == 0 {
inputs = append(inputs, &kio.ByteReader{Reader: c.InOrStdin()})
}
var out []kio.Writer
if r.Kind {
out = append(out, kio.WriterFunc(func(nodes []*yaml.RNode) error {
count := map[string]int{}
k := sets.String{}
for _, n := range nodes {
m, _ := n.GetMeta()
count[m.Kind]++
k.Insert(m.Kind)
}
order := k.List()
sort.Strings(order)
for _, k := range order {
fmt.Fprintf(c.OutOrStdout(), "%s: %d\n", k, count[k])
}
return nil
}))
} else {
out = append(out, kio.WriterFunc(func(nodes []*yaml.RNode) error {
fmt.Fprintf(c.OutOrStdout(), "%d\n", len(nodes))
return nil
}))
}
return handleError(c, kio.Pipeline{
Inputs: inputs,
Outputs: out,
}.Execute())
}

View File

@@ -0,0 +1,73 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands_test
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
)
func TestCountCommand_files(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-count-test")
if !assert.NoError(t, err) {
return
}
defer os.RemoveAll(d)
err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(`
kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
spec:
selector:
app: nginx
`), 0600)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`), 0600)
if !assert.NoError(t, err) {
return
}
// fmt the files
b := &bytes.Buffer{}
r := commands.GetCountRunner("")
r.Command.SetArgs([]string{d})
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
return
}
if !assert.Equal(t, "Deployment: 2\nService: 1\n", b.String()) {
return
}
}

View File

@@ -0,0 +1,93 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/filters"
)
// FmtCmd returns a command FmtRunner.
func GetFmtRunner(name string) *FmtRunner {
r := &FmtRunner{}
c := &cobra.Command{
Use: "fmt",
Short: commands.FmtShort,
Long: commands.FmtLong,
Example: commands.FmtExamples,
RunE: r.runE,
PreRunE: r.preRunE,
}
fixDocs(name, c)
c.Flags().StringVar(&r.FilenamePattern, "pattern", filters.DefaultFilenamePattern,
`pattern to use for generating filenames for resources -- may contain the following
formatting substitution verbs {'%n': 'metadata.name', '%s': 'metadata.namespace', '%k': 'kind'}`)
c.Flags().BoolVar(&r.SetFilenames, "set-filenames", false,
`if true, set default filenames on Resources without them`)
c.Flags().BoolVar(&r.KeepAnnotations, "keep-annotations", false,
`if true, keep index and filename annotations set on Resources.`)
c.Flags().BoolVar(&r.Override, "override", false,
`if true, override existing filepath annotations.`)
r.Command = c
return r
}
func FmtCommand(name string) *cobra.Command {
return GetFmtRunner(name).Command
}
// FmtRunner contains the run function
type FmtRunner struct {
Command *cobra.Command
FilenamePattern string
SetFilenames bool
KeepAnnotations bool
Override bool
}
func (r *FmtRunner) preRunE(c *cobra.Command, args []string) error {
if r.SetFilenames {
r.KeepAnnotations = true
}
return nil
}
func (r *FmtRunner) runE(c *cobra.Command, args []string) error {
f := []kio.Filter{filters.FormatFilter{}}
// format with file names
if r.SetFilenames {
f = append(f, &filters.FileSetter{
FilenamePattern: r.FilenamePattern,
Override: r.Override,
})
}
// format stdin if there are no args
if len(args) == 0 {
rw := &kio.ByteReadWriter{
Reader: c.InOrStdin(),
Writer: c.OutOrStdout(),
KeepReaderAnnotations: r.KeepAnnotations,
}
return handleError(c, kio.Pipeline{
Inputs: []kio.Reader{rw}, Filters: f, Outputs: []kio.Writer{rw}}.Execute())
}
for i := range args {
path := args[i]
rw := &kio.LocalPackageReadWriter{
NoDeleteFiles: true,
PackagePath: path,
KeepReaderAnnotations: r.KeepAnnotations}
err := kio.Pipeline{
Inputs: []kio.Reader{rw}, Filters: f, Outputs: []kio.Writer{rw}}.Execute()
if err != nil {
return handleError(c, err)
}
}
return nil
}

View File

@@ -0,0 +1,162 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands_test
import (
"bytes"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
"sigs.k8s.io/kustomize/kyaml/kio/filters/testyaml"
)
// TestCmd_files verifies the fmt command formats the files
func TestFmtCommand_files(t *testing.T) {
f1, err := ioutil.TempFile("", "cmdfmt*.yaml")
if !assert.NoError(t, err) {
return
}
defer os.RemoveAll(f1.Name())
err = ioutil.WriteFile(f1.Name(), testyaml.UnformattedYaml1, 0600)
if !assert.NoError(t, err) {
return
}
f2, err := ioutil.TempFile("", "cmdfmt*.yaml")
if !assert.NoError(t, err) {
return
}
defer os.RemoveAll(f2.Name())
err = ioutil.WriteFile(f2.Name(), testyaml.UnformattedYaml2, 0600)
if !assert.NoError(t, err) {
return
}
// fmt the files
r := commands.GetFmtRunner("")
r.Command.SetArgs([]string{f1.Name(), f2.Name()})
err = r.Command.Execute()
if !assert.NoError(t, err) {
return
}
// verify the contents
b, err := ioutil.ReadFile(f1.Name())
if !assert.NoError(t, err) {
return
}
if !assert.Equal(t, string(testyaml.FormattedYaml1), string(b)) {
return
}
b, err = ioutil.ReadFile(f2.Name())
if !assert.NoError(t, err) {
return
}
if !assert.Equal(t, string(testyaml.FormattedYaml2), string(b)) {
return
}
}
func TestFmtCommand_stdin(t *testing.T) {
out := &bytes.Buffer{}
r := commands.GetFmtRunner("")
r.Command.SetOut(out)
r.Command.SetIn(bytes.NewReader(testyaml.UnformattedYaml1))
// fmt the input
err := r.Command.Execute()
assert.NoError(t, err)
// verify the output
assert.Equal(t, string(testyaml.FormattedYaml1), out.String())
}
// TestCmd_filesAndstdin verifies that if both files and stdin input are provided, only
// the files are formatted and the input is ignored
func TestFmtCmd_filesAndStdin(t *testing.T) {
f1, err := ioutil.TempFile("", "cmdfmt*.yaml")
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(f1.Name(), testyaml.UnformattedYaml1, 0600)
if !assert.NoError(t, err) {
return
}
f2, err := ioutil.TempFile("", "cmdfmt*.yaml")
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(f2.Name(), testyaml.UnformattedYaml2, 0600)
if !assert.NoError(t, err) {
return
}
out := &bytes.Buffer{}
in := &bytes.Buffer{}
r := commands.GetFmtRunner("")
r.Command.SetOut(out)
r.Command.SetIn(in)
// fmt the files
r = commands.GetFmtRunner("")
r.Command.SetArgs([]string{f1.Name(), f2.Name()})
err = r.Command.Execute()
if !assert.NoError(t, err) {
return
}
// verify the output
b, err := ioutil.ReadFile(f1.Name())
if !assert.NoError(t, err) {
return
}
if !assert.Equal(t, string(testyaml.FormattedYaml1), string(b)) {
return
}
b, err = ioutil.ReadFile(f2.Name())
if !assert.NoError(t, err) {
return
}
if !assert.Equal(t, string(testyaml.FormattedYaml2), string(b)) {
return
}
err = r.Command.Execute()
if !assert.NoError(t, err) {
return
}
if !assert.Equal(t, "", out.String()) {
return
}
}
// TestCmd_files verifies the fmt command formats the files
func TestCmd_failFiles(t *testing.T) {
// fmt the files
r := commands.GetFmtRunner("")
r.Command.SetArgs([]string{"notrealfile"})
err := r.Command.Execute()
assert.EqualError(t, err, "lstat notrealfile: no such file or directory")
}
// TestCmd_files verifies the fmt command formats the files
func TestCmd_failFileContents(t *testing.T) {
out := &bytes.Buffer{}
r := commands.GetFmtRunner("")
r.Command.SetOut(out)
r.Command.SetIn(strings.NewReader(`{`))
// fmt the input
err := r.Command.Execute()
// expect an error
assert.EqualError(t, err, "yaml: line 1: did not find expected node content")
}

View File

@@ -0,0 +1,125 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//
package commands
import (
"fmt"
"strings"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/resource"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/filters"
)
// Cmd returns a command GrepRunner.
func GetGrepRunner(name string) *GrepRunner {
r := &GrepRunner{}
c := &cobra.Command{
Use: "grep QUERY [DIR]...",
Short: commands.GrepShort,
Long: commands.GrepLong,
Example: commands.GrepExamples,
PreRunE: r.preRunE,
RunE: r.runE,
Args: cobra.MinimumNArgs(1),
}
fixDocs(name, c)
c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true,
"also print resources from subpackages.")
c.Flags().BoolVar(&r.KeepAnnotations, "annotate", true,
"annotate resources with their file origins.")
c.Flags().BoolVarP(&r.InvertMatch, "invert-match", "v", false,
" Selected Resources are those not matching any of the specified patterns..")
r.Command = c
return r
}
func GrepCommand(name string) *cobra.Command {
return GetGrepRunner(name).Command
}
// GrepRunner contains the run function
type GrepRunner struct {
IncludeSubpackages bool
KeepAnnotations bool
Command *cobra.Command
filters.GrepFilter
Format bool
}
func (r *GrepRunner) preRunE(c *cobra.Command, args []string) error {
r.GrepFilter.Compare = func(a, b string) (int, error) {
qa, err := resource.ParseQuantity(a)
if err != nil {
return 0, fmt.Errorf("%s: %v", a, err)
}
qb, err := resource.ParseQuantity(b)
if err != nil {
return 0, err
}
return qa.Cmp(qb), err
}
parts, err := parseFieldPath(args[0])
if err != nil {
return err
}
var last []string
if strings.Contains(parts[len(parts)-1], ">=") {
last = strings.Split(parts[len(parts)-1], ">=")
r.MatchType = filters.GreaterThanEq
} else if strings.Contains(parts[len(parts)-1], "<=") {
last = strings.Split(parts[len(parts)-1], "<=")
r.MatchType = filters.LessThanEq
} else if strings.Contains(parts[len(parts)-1], ">") {
last = strings.Split(parts[len(parts)-1], ">")
r.MatchType = filters.GreaterThan
} else if strings.Contains(parts[len(parts)-1], "<") {
last = strings.Split(parts[len(parts)-1], "<")
r.MatchType = filters.LessThan
} else {
last = strings.Split(parts[len(parts)-1], "=")
r.MatchType = filters.Regexp
}
if len(last) > 2 {
return fmt.Errorf(
"ambiguous match -- multiple of ['<', '>', '<=', '>=', '=' in final path element: %s",
parts[len(parts)-1])
}
if len(last) > 1 {
r.Value = last[1]
}
r.Path = append(parts[:len(parts)-1], last[0])
return nil
}
func (r *GrepRunner) runE(c *cobra.Command, args []string) error {
var filters = []kio.Filter{r.GrepFilter}
var inputs []kio.Reader
for _, a := range args[1:] {
inputs = append(inputs, kio.LocalPackageReader{
PackagePath: a,
IncludeSubpackages: r.IncludeSubpackages,
})
}
if len(inputs) == 0 {
inputs = append(inputs, &kio.ByteReader{Reader: c.InOrStdin()})
}
return handleError(c, kio.Pipeline{
Inputs: inputs,
Filters: filters,
Outputs: []kio.Writer{kio.ByteWriter{
Writer: c.OutOrStdout(),
KeepReaderAnnotations: r.KeepAnnotations,
}},
}.Execute())
}

View File

@@ -0,0 +1,272 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands_test
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
)
// TestGrepCommand_files verifies grep reads the files and filters them
func TestGrepCommand_files(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-kyaml-test")
if !assert.NoError(t, err) {
return
}
defer os.RemoveAll(d)
err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(`
kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
spec:
selector:
app: nginx
`), 0600)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`), 0600)
if !assert.NoError(t, err) {
return
}
// fmt the files
b := &bytes.Buffer{}
r := commands.GetGrepRunner("")
r.Command.SetArgs([]string{"metadata.name=foo", d})
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
return
}
if !assert.Equal(t, `kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
config.kubernetes.io/index: 0
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
config.kubernetes.io/index: 1
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
selector:
app: nginx
`, b.String()) {
return
}
}
// TestCmd_stdin verifies the grep command reads stdin if no files are provided
func TestGrepCmd_stdin(t *testing.T) {
// fmt the files
b := &bytes.Buffer{}
r := commands.GetGrepRunner("")
r.Command.SetArgs([]string{"metadata.name=foo"})
r.Command.SetOut(b)
r.Command.SetIn(bytes.NewBufferString(`
kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
spec:
selector:
app: nginx
---
kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`))
if !assert.NoError(t, r.Command.Execute()) {
return
}
if !assert.Equal(t, `kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
config.kubernetes.io/index: 0
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
config.kubernetes.io/index: 1
spec:
selector:
app: nginx
`, b.String()) {
return
}
}
// TestGrepCmd_errInputs verifies the grep command errors on invalid matches
func TestGrepCmd_errInputs(t *testing.T) {
b := &bytes.Buffer{}
r := commands.GetGrepRunner("")
r.Command.SetArgs([]string{"metadata.name=foo=bar"})
r.Command.SetOut(b)
r.Command.SetIn(bytes.NewBufferString(`
kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
`))
err := r.Command.Execute()
if !assert.Error(t, err) {
return
}
assert.Contains(t, err.Error(), "ambiguous match")
// fmt the files
b = &bytes.Buffer{}
r = commands.GetGrepRunner("")
r.Command.SetArgs([]string{"spec.template.spec.containers[a[b=c].image=foo"})
r.Command.SetOut(b)
r.Command.SetIn(bytes.NewBufferString(`
kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
`))
err = r.Command.Execute()
if !assert.Error(t, err) {
return
}
assert.Contains(t, err.Error(), "unrecognized path element:")
}
// TestGrepCommand_escapeDots verifies the grep command correctly escapes '\.' in inputs
func TestGrepCommand_escapeDots(t *testing.T) {
// fmt the files
b := &bytes.Buffer{}
r := commands.GetGrepRunner("")
r.Command.SetArgs([]string{"spec.template.spec.containers[name=nginx].image=nginx:1\\.7\\.9",
"--annotate=false"})
r.Command.SetOut(b)
r.Command.SetIn(bytes.NewBufferString(`
kind: Deployment
metadata:
labels:
app: nginx1.8
name: nginx1.8
annotations:
app: nginx1.8
spec:
replicas: 1
template:
spec:
containers:
- name: nginx
image: nginx:1.8.1
---
kind: Deployment
metadata:
labels:
app: nginx1.7
name: nginx1.7
annotations:
app: nginx1.7
spec:
replicas: 1
template:
spec:
containers:
- name: nginx
image: nginx:1.7.9
`))
err := r.Command.Execute()
if !assert.NoError(t, err) {
return
}
if !assert.Equal(t, `kind: Deployment
metadata:
labels:
app: nginx1.7
name: nginx1.7
annotations:
app: nginx1.7
spec:
replicas: 1
template:
spec:
containers:
- name: nginx
image: nginx:1.7.9
`, b.String()) {
return
}
}

View File

@@ -0,0 +1,68 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/filters"
)
func GetMergeRunner(name string) *MergeRunner {
r := &MergeRunner{}
c := &cobra.Command{
Use: "merge [SOURCE_DIR...] [DESTINATION_DIR]",
Short: commands.MergeShort,
Long: commands.MergeLong,
Example: commands.MergeExamples,
RunE: r.runE,
}
fixDocs(name, c)
r.Command = c
r.Command.Flags().BoolVar(&r.InvertOrder, "invert-order", false,
"if true, merge Resources in the reverse order")
return r
}
func MergeCommand(name string) *cobra.Command {
return GetMergeRunner(name).Command
}
// MergeRunner contains the run function
type MergeRunner struct {
Command *cobra.Command
InvertOrder bool
}
func (r *MergeRunner) runE(c *cobra.Command, args []string) error {
var inputs []kio.Reader
// add the packages in reverse order -- the arg list should be highest precedence first
// e.g. merge from -> to, but the MergeFilter is highest precedence last
for i := len(args) - 1; i >= 0; i-- {
inputs = append(inputs, kio.LocalPackageReader{PackagePath: args[i]})
}
// if there is no "from" package, read from stdin
rw := &kio.ByteReadWriter{
Reader: c.InOrStdin(),
Writer: c.OutOrStdout(),
KeepReaderAnnotations: true,
}
if len(inputs) < 2 {
inputs = append(inputs, rw)
}
// write to the "to" package if specified
var outputs []kio.Writer
if len(args) != 0 {
outputs = append(outputs, kio.LocalPackageWriter{PackagePath: args[len(args)-1]})
}
// if there is no "to" package, write to stdout
if len(outputs) == 0 {
outputs = append(outputs, rw)
}
filters := []kio.Filter{filters.MergeFilter{}, filters.FormatFilter{}}
return handleError(c, kio.Pipeline{Inputs: inputs, Filters: filters, Outputs: outputs}.Execute())
}

View File

@@ -0,0 +1,56 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/runfn"
)
// GetCatRunner returns a RunFnRunner.
func GetRunFnRunner(name string) *RunFnRunner {
r := &RunFnRunner{}
c := &cobra.Command{
Use: "run DIR",
Aliases: []string{"run-fns"},
Short: commands.RunFnsShort,
Long: commands.RunFnsLong,
Example: commands.RunFnsExamples,
RunE: r.runE,
Args: cobra.ExactArgs(1),
}
fixDocs(name, c)
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(name string) *cobra.Command {
return GetRunFnRunner(name).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 handleError(c, 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

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

@@ -0,0 +1,187 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands
import (
"path/filepath"
"strings"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/kio/filters"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func GetTreeRunner(name string) *TreeRunner {
r := &TreeRunner{}
c := &cobra.Command{
Use: "tree DIR",
Short: commands.TreeShort,
Long: commands.TreeLong,
Example: commands.TreeExamples,
RunE: r.runE,
Args: cobra.MaximumNArgs(1),
}
fixDocs(name, c)
c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true,
"also print resources from subpackages.")
// TODO(pwittrock): Figure out if these are the right things to expose, and consider making it
// a list of options instead of individual flags
c.Flags().BoolVar(&r.name, "name", false, "print name field")
c.Flags().BoolVar(&r.resources, "resources", false, "print resources field")
c.Flags().BoolVar(&r.ports, "ports", false, "print ports field")
c.Flags().BoolVar(&r.images, "image", false, "print image field")
c.Flags().BoolVar(&r.replicas, "replicas", false, "print replicas field")
c.Flags().BoolVar(&r.args, "args", false, "print args field")
c.Flags().BoolVar(&r.cmd, "command", false, "print command field")
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.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 any of: "+
strings.Join(kio.GraphStructures, ","))
r.Command = c
return r
}
func TreeCommand(name string) *cobra.Command {
return GetTreeRunner(name).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
includeLocal bool
excludeNonLocal bool
structure string
}
func (r *TreeRunner) runE(c *cobra.Command, args []string) error {
var input kio.Reader
var root = "."
if len(args) == 1 {
root = filepath.Clean(args[0])
input = kio.LocalPackageReader{PackagePath: args[0]}
} else {
input = &kio.ByteReader{Reader: c.InOrStdin()}
}
var fields []kio.TreeWriterField
for _, field := range r.fields {
path, err := parseFieldPath(field)
if err != nil {
return err
}
fields = append(fields, newField(path...))
}
if r.name || (r.all && !c.Flag("name").Changed) {
fields = append(fields,
newField("spec", "containers", "[name=.*]", "name"),
newField("spec", "template", "spec", "containers", "[name=.*]", "name"),
)
}
if r.images || (r.all && !c.Flag("image").Changed) {
fields = append(fields,
newField("spec", "containers", "[name=.*]", "image"),
newField("spec", "template", "spec", "containers", "[name=.*]", "image"),
)
}
if r.cmd || (r.all && !c.Flag("command").Changed) {
fields = append(fields,
newField("spec", "containers", "[name=.*]", "command"),
newField("spec", "template", "spec", "containers", "[name=.*]", "command"),
)
}
if r.args || (r.all && !c.Flag("args").Changed) {
fields = append(fields,
newField("spec", "containers", "[name=.*]", "args"),
newField("spec", "template", "spec", "containers", "[name=.*]", "args"),
)
}
if r.env || (r.all && !c.Flag("env").Changed) {
fields = append(fields,
newField("spec", "containers", "[name=.*]", "env"),
newField("spec", "template", "spec", "containers", "[name=.*]", "env"),
)
}
if r.replicas || (r.all && !c.Flag("replicas").Changed) {
fields = append(fields,
newField("spec", "replicas"),
)
}
if r.resources || (r.all && !c.Flag("resources").Changed) {
fields = append(fields,
newField("spec", "containers", "[name=.*]", "resources"),
newField("spec", "template", "spec", "containers", "[name=.*]", "resources"),
)
}
if r.ports || (r.all && !c.Flag("ports").Changed) {
fields = append(fields,
newField("spec", "containers", "[name=.*]", "ports"),
newField("spec", "template", "spec", "containers", "[name=.*]", "ports"),
newField("spec", "ports"),
)
}
// show reconcilers in tree
fltrs := []kio.Filter{&filters.IsLocalConfig{
IncludeLocalConfig: r.includeLocal,
ExcludeNonLocalConfig: r.excludeNonLocal,
}}
return handleError(c, kio.Pipeline{
Inputs: []kio.Reader{input},
Filters: fltrs,
Outputs: []kio.Writer{kio.TreeWriter{
Root: root,
Writer: c.OutOrStdout(),
Fields: fields,
Structure: kio.TreeStructure(r.structure)}},
}.Execute())
}
func newField(val ...string) kio.TreeWriterField {
if strings.HasPrefix(strings.Join(val, "."), "spec.template.spec.containers") {
return kio.TreeWriterField{
Name: "spec.template.spec.containers",
PathMatcher: yaml.PathMatcher{Path: val, StripComments: true},
SubName: val[len(val)-1],
}
}
if strings.HasPrefix(strings.Join(val, "."), "spec.containers") {
return kio.TreeWriterField{
Name: "spec.containers",
PathMatcher: yaml.PathMatcher{Path: val, StripComments: true},
SubName: val[len(val)-1],
}
}
return kio.TreeWriterField{
Name: strings.Join(val, "."),
PathMatcher: yaml.PathMatcher{Path: val, StripComments: true},
}
}

View File

@@ -0,0 +1,355 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands_test
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
)
// TestCmd_files verifies fmt reads the files and filters them
func TestTreeCommand_files(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-tree-test")
defer os.RemoveAll(d)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(`
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
---
kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
spec:
selector:
app: nginx
`), 0600)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`), 0600)
if !assert.NoError(t, err) {
return
}
// fmt the files
b := &bytes.Buffer{}
r := commands.GetTreeRunner("")
r.Command.SetArgs([]string{d})
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
return
}
if !assert.Equal(t, fmt.Sprintf(`%s
├── [f1.yaml] Deployment foo
├── [f1.yaml] Service foo
└── [f2.yaml] Deployment bar
`, d), b.String()) {
return
}
}
func TestTreeCommand_stdin(t *testing.T) {
// fmt the files
b := &bytes.Buffer{}
r := commands.GetTreeRunner("")
r.Command.SetArgs([]string{})
r.Command.SetIn(bytes.NewBufferString(`apiVersion: extensions/v1
kind: Deployment
metadata:
labels:
app: nginx2
name: foo3
namespace: default
annotations:
app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
---
apiVersion: extensions/v1
kind: Deployment
metadata:
labels:
app: nginx2
name: foo3
namespace: default
annotations:
app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx2
name: foo3
namespace: default
annotations:
app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx2
name: foo2
namespace: default2
annotations:
app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx3
name: foo
namespace: default
annotations:
app: nginx3
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
---
kind: Deployment
metadata:
labels:
app: nginx
annotations:
app: nginx
config.kubernetes.io/package: bar-package
config.kubernetes.io/path: f2.yaml
name: bar
spec:
replicas: 3
---
kind: Service
metadata:
name: foo
namespace: default
annotations:
app: nginx
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
selector:
app: nginx
`))
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
return
}
if !assert.Equal(t, fmt.Sprintf(`.
├── [f1.yaml] Deployment default/foo
├── [f1.yaml] Service default/foo
├── [f1.yaml] Deployment default/foo3
├── [f1.yaml] Deployment default/foo3
├── [f1.yaml] Deployment default/foo3
├── [f1.yaml] Deployment default2/foo2
└── bar-package
└── [f2.yaml] Deployment bar
`), b.String()) {
return
}
}
func TestTreeCommand_includeReconcilers(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-tree-test")
defer os.RemoveAll(d)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(`
kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
spec:
selector:
app: nginx
`), 0600)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`
apiVersion: gcr.io/example/reconciler:v1
kind: Abstraction
metadata:
name: foo
spec:
replicas: 1
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`), 0600)
if !assert.NoError(t, err) {
return
}
// fmt the files
b := &bytes.Buffer{}
r := commands.GetTreeRunner("")
r.Command.SetArgs([]string{d, "--include-local"})
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
return
}
if !assert.Equal(t, fmt.Sprintf(`%s
├── [f1.yaml] Deployment foo
├── [f1.yaml] Service foo
├── [f2.yaml] Deployment bar
└── [f2.yaml] Abstraction foo
`, d), b.String()) {
return
}
}
func TestTreeCommand_excludeNonReconcilers(t *testing.T) {
d, err := ioutil.TempDir("", "kustmoize-tree-test")
defer os.RemoveAll(d)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(`
kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
spec:
selector:
app: nginx
`), 0600)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`
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
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`), 0600)
if !assert.NoError(t, err) {
return
}
// fmt the files
b := &bytes.Buffer{}
r := commands.GetTreeRunner("")
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, fmt.Sprintf(`%s
└── [f2.yaml] Abstraction foo
`, d), b.String()) {
return
}
}

View File

@@ -0,0 +1,74 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//
package commands
import (
"fmt"
"os"
"strings"
"github.com/go-errors/errors"
"github.com/spf13/cobra"
)
// parseFieldPath parse a flag value into a field path
func parseFieldPath(path string) ([]string, error) {
// fixup '\.' so we don't split on it
match := strings.ReplaceAll(path, "\\.", "$$$$")
parts := strings.Split(match, ".")
for i := range parts {
parts[i] = strings.ReplaceAll(parts[i], "$$$$", ".")
}
// split the list index from the list field
var newParts []string
for i := range parts {
if !strings.Contains(parts[i], "[") {
newParts = append(newParts, parts[i])
continue
}
p := strings.Split(parts[i], "[")
if len(p) != 2 {
return nil, fmt.Errorf("unrecognized path element: %s. "+
"Should be of the form 'list[field=value]'", parts[i])
}
p[1] = "[" + p[1]
newParts = append(newParts, p[0], p[1])
}
return newParts, nil
}
func handleError(c *cobra.Command, err error) error {
if err == nil {
return nil
}
if StackOnError {
if err, ok := err.(*errors.Error); ok {
fmt.Fprint(os.Stderr, fmt.Sprintf("%s", err.Stack()))
}
}
if ExitOnError {
fmt.Fprintf(c.ErrOrStderr(), "Error: %v\n", err)
os.Exit(1)
}
return err
}
// ExitOnError if true, will cause commands to call os.Exit instead of returning an error.
// Used for skipping printing usage on failure.
var ExitOnError bool
// StackOnError if true, will print a stack trace on failure.
var StackOnError bool
const cmdName = "kustomize config"
// FixDocs replaces instances of old with new in the docs for c
func fixDocs(new string, c *cobra.Command) {
c.Use = strings.ReplaceAll(c.Use, cmdName, new)
c.Short = strings.ReplaceAll(c.Short, cmdName, new)
c.Long = strings.ReplaceAll(c.Long, cmdName, new)
c.Example = strings.ReplaceAll(c.Example, cmdName, new)
}

View File

@@ -0,0 +1,484 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Code generated by "mdtogo"; DO NOT EDIT.
package api
var ConfigFnLong = `# Configuration Functions API Semantics
Configuration Functions are functions packaged as executables in containers which enable
**shift-left practices**. They configure applications and infrastructure through
Kubernetes style Resource Configuration, but run locally pre-commit.
Configuration functions enable shift-left practices (client-side) through:
- Pre-commit / delivery validation and linting of configuration
- e.g. Fail if any containers don't have PodSecurityPolicy or CPU / Memory limits
- Implementation of abstractions as client actuated APIs (e.g. templating)
- e.g. Create a client-side *"CRD"* for generating configuration checked into git
- Aspect Orient configuration / Injection of cross-cutting configuration
- e.g. T-Shirt size containers by annotating Resources with ` + "`" + `small` + "`" + `, ` + "`" + `medium` + "`" + `, ` + "`" + `large` + "`" + `
and inject the cpu and memory resources into containers accordingly.
- e.g. Inject ` + "`" + `init` + "`" + ` and ` + "`" + `side-car` + "`" + ` containers into Resources based off of Resource
Type, annotations, etc.
Performing these on the client rather than the server enables:
- Configuration to be reviewed prior to being sent to the API server
- Configuration to be validated as part of the CD pipeline
- Configuration for Resources to validated holistically rather than individually
per-Resource -- e.g. ensure the ` + "`" + `Service.selector` + "`" + ` and ` + "`" + `Deployment.spec.template` + "`" + ` labels
match.
- MutatingWebHooks are scoped to a single Resource instance at a time.
- Low-level tweaks to the output of high-level abstractions -- e.g. add an ` + "`" + `init container` + "`" + `
to a client *"CRD"* Resource after it was generated.
- Composition and layering of multiple functions together
- Compose generation, injection, validation together
Configuration Functions are implemented as executable programs published in containers which:
- Accept as input (stdin):
- A list of Resource Configuration
- A Function Configuration (to configure the function itself)
- Emit as output (stdout + exit):
- A list of Resource Configuration
- An exit code for success / failure
### Function Specification
- Functions **SHOULD** be published as container images containing a ` + "`" + `CMD` + "`" + ` invoking an executable.
- Functions **MUST** accept input on STDIN a ` + "`" + `ResourceList` + "`" + ` containing the Resources and
` + "`" + `functionConfig` + "`" + `.
- Functions **MUST** emit output on STDOUT a ` + "`" + `ResourceList` + "`" + ` containing the modified
Resources.
- Functions **MUST** exit non-0 on failure, and exit 0 on success.
- Functions **MAY** emit output on STDERR with error messaging.
- Functions performing validation **SHOULD** exit failure and emit error messaging
on a validation failure.
- Functions generating Resources **SHOULD** retain non-conflicting changes on the
generated Resources -- e.g. 1. the function generates a Deployment, but doesn't
specify ` + "`" + `cpu` + "`" + `, 2. the user sets the ` + "`" + `cpu` + "`" + ` on the generated Resource, 3. the
function should keep the ` + "`" + `cpu` + "`" + ` when regenerating the Resource a second time.
- Functions **SHOULD** be usable outside ` + "`" + `kustomize config run` + "`" + ` -- e.g. though pipeline
mechanisms such as Tekton.
#### Input Format
Functions must accept on STDIN:
` + "`" + `ResourceList` + "`" + `:
- contains ` + "`" + `items` + "`" + ` field, same as ` + "`" + `List.items` + "`" + `
- contains ` + "`" + `functionConfig` + "`" + ` field -- a single item with the configuration for the function itself
Example ` + "`" + `ResourceList` + "`" + ` Input:
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
functionConfig:
apiVersion: example.com/v1beta1
kind: Nginx
metadata:
name: my-instance
annotations:
config.kubernetes.io/local-config: "true"
spec:
replicas: 5
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: my-instance
spec:
replicas: 3
...
- apiVersion: v1
kind: Service
metadata:
name: my-instance
spec:
...
#### Output Format
Functions must emit on STDOUT:
` + "`" + `ResourceList` + "`" + `:
- contains ` + "`" + `items` + "`" + ` field, same as ` + "`" + `List.items` + "`" + `
Example ` + "`" + `ResourceList` + "`" + ` Output:
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: my-instance
spec:
replicas: 5
...
- apiVersion: v1
kind: Service
metadata:
name: my-instance
spec:
...
#### Container Environment
When run by ` + "`" + `kustomize config run` + "`" + `, functions are run in containers with the
following environment:
- Network: ` + "`" + `none` + "`" + `
- User: ` + "`" + `nobody` + "`" + `
- Security Options: ` + "`" + `no-new-privileges` + "`" + `
- Volumes: the volume containing the ` + "`" + `functionConfig` + "`" + ` yaml is mounted under ` + "`" + `/local` + "`" + ` as ` + "`" + `ro` + "`" + `
### Example Function Implementation
Following is an example for implementing an nginx abstraction using a config
function.
#### ` + "`" + `nginx-template.sh` + "`" + `
` + "`" + `nginx-template.sh` + "`" + ` is a simple bash script which uses a *heredoc* as a templating solution
for generating Resources from the functionConfig input fields.
The script wraps itself using ` + "`" + `config run wrap -- $0` + "`" + ` which will:
1. Parse the ` + "`" + `ResourceList.functionConfig` + "`" + ` (provided to the container stdin) into env vars
2. Merge the stdout into the original list of Resources
3. Defaults filenames for newly generated Resources (if they are not set as annotations)
to ` + "`" + `config/NAME_KIND.yaml` + "`" + `
4. Format the output
#!/bin/bash
# script must run wrapped by ` + "`" + `kustomize config run wrap` + "`" + `
# for parsing input the functionConfig into env vars
if [ -z ${WRAPPED} ]; then
export WRAPPED=true
config run wrap -- $0
exit $?
fi
cat <<End-of-message
apiVersion: v1
kind: Service
metadata:
name: ${NAME}
labels:
app: nginx
instance: ${NAME}
spec:
ports:
- port: 80
targetPort: 80
name: http
selector:
app: nginx
instance: ${NAME}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${NAME}
labels:
app: nginx
instance: ${NAME}
spec:
replicas: ${REPLICAS}
selector:
matchLabels:
app: nginx
instance: ${NAME}
template:
metadata:
labels:
app: nginx
instance: ${NAME}
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
End-of-message
#### ` + "`" + `Dockerfile` + "`" + `
` + "`" + `Dockerfile` + "`" + ` installs ` + "`" + `kustomize config` + "`" + ` and copies the script into the container image.
FROM golang:1.13-stretch
RUN go get sigs.k8s.io/kustomize/cmd/config
RUN mv /go/bin/config /usr/bin/config
COPY nginx-template.sh /usr/bin/nginx-template.sh
CMD ["nginx-template.sh]
### Example Function Usage
Following is an example of running the ` + "`" + `kustomize config run` + "`" + ` using the preceding API.
#### ` + "`" + `nginx.yaml` + "`" + ` (Input)
` + "`" + `dir/nginx.yaml` + "`" + ` contains a reference to the Function. The contents of ` + "`" + `nginx.yaml` + "`" + `
are passed to the Function through the ` + "`" + `ResourceList.functionConfig` + "`" + ` field.
apiVersion: example.com/v1beta1
kind: Nginx
metadata:
name: my-instance
annotations:
config.kubernetes.io/local-config: "true"
configFn:
container:
image: gcr.io/example-functions/nginx-template:v1.0.0
spec:
replicas: 5
- ` + "`" + `configFn.container.image` + "`" + `: the image to use for this API
- ` + "`" + `annotations[config.kubernetes.io/local-config]` + "`" + `: mark this as not a Resource that should
be applied
#### ` + "`" + `kustomize config run dir/` + "`" + ` (Output)
` + "`" + `dir/my-instance_deployment.yaml` + "`" + ` contains the Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-instance
labels:
app: nginx
instance: my-instance
spec:
replicas: 5
selector:
matchLabels:
app: nginx
instance: my-instance
template:
metadata:
labels:
app: nginx
instance: my-instance
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
` + "`" + `dir/my-instance_service.yaml` + "`" + ` contains the Service:
apiVersion: v1
kind: Service
metadata:
name: my-instance
labels:
app: nginx
instance: my-instance
spec:
ports:
- port: 80
targetPort: 80
name: http
selector:
app: nginx
instance: my-instance`
var ConfigIoLong = `# Configuration IO API Semantics
Resource Configuration may be read / written from / to sources such as directories,
stdin|out or network. Tools may be composed using pipes such that the tools writing
Resource Configuration may be a different tool from the one that read the configuration.
In order for tools to be composed in this way, while preserving origin information --
such as the original file, index, etc.
Tools **SHOULD** write the following annotations when reading from sources,
and **SHOULD** respect the annotations when writing to sinks.
### ` + "`" + `config.kubernetes.io/path` + "`" + `
Records the slash-delimited, OS-agnostic, relative file path to a Resource.
This annotation **SHOULD** be set when reading Resources from files.
When writing Resources to a directory, the Resource **SHOULD** be written to the corresponding
path relative to that directory.
Example:
metadata:
annotations:
config.kubernetes.io/path: "relative/file/path.yaml"
### ` + "`" + `config.kubernetes.io/index` + "`" + `
Records the index of a Resource in file. In a multi-object files YAML file, Resources are separated
by three dashes (` + "`" + `---` + "`" + `), and the index represents the positon of the Resource starting from zero.
This annotation **SHOULD** be set when reading Resources from files.
When writing multiple Resources to the same file, the Resource **SHOULD** be written in the
relative order matching the index.
When this annotation is not specified, it implies a value of ` + "`" + `0` + "`" + `.
Example:
metadata:
annotations:
config.kubernetes.io/path: "relative/file/path.yaml"
config.kubernetes.io/index: 2
This represents the third Resource in the file.
### ` + "`" + `config.kubernetes.io/local-config` + "`" + `
` + "`" + `config.kubernetes.io/local-config` + "`" + ` declares that the configuration is to local tools
rather than a remote Resource. e.g. The ` + "`" + `Kustomization` + "`" + ` config in a ` + "`" + `kustomization.yaml` + "`" + `
**SHOULD** contain this annotation so that tools know it is not intended to be sent to
the Kubernetes api server.
Example:
metadata:
annotations:
config.kubernetes.io/local-config: "true"`
var Merge2Long = `# Merge (2-way)
2-way merges fields from a source to a destination, overriding the destination fields
where they differ.
### Merge Rules
Fields are recursively merged using the following rules:
- scalars
- if present only in the dest, it keeps its value
- if present in the src and is non-null, take the src value -- if ` + "`" + `null` + "`" + `, clear it
- example src: ` + "`" + `5` + "`" + `, dest: ` + "`" + `3` + "`" + ` => result: ` + "`" + `5` + "`" + `
- non-associative lists -- lists without a merge key
- if present only in the dest, it keeps its value
- if present in the src and is non-null, take the src value -- if ` + "`" + `null` + "`" + `, clear it
- example src: ` + "`" + `[1, 2, 3]` + "`" + `, dest: ` + "`" + `[a, b, c]` + "`" + ` => result: ` + "`" + `[1, 2, 3]` + "`" + `
- map keys and fields -- paired by the map-key / field-name
- if present only in the dest, it keeps its value
- if present only in the src, it is added to the dest
- if the field is present in both the src and dest, and the src value is
` + "`" + `null` + "`" + `, the field is removed from the dest
- if the field is present in both the src and dest, the value is recursively merged
- example src: ` + "`" + `{'key1': 'value1', 'key2': 'value2'}` + "`" + `,
dest: ` + "`" + `{'key2': 'value0', 'key3': 'value3'}` + "`" + `
=> result: ` + "`" + `{'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}` + "`" + `
- associative list elements -- paired by the associative key
- if present only in the dest, it keeps its value in the list
- if present only in the src, it is added to the dest list
- if the field is present in both the src and dest, the value is recursively merged
### Associative Keys
Associative keys are used to identify "same" elements within 2 different lists, and merge them.
The following fields are recognized as associative keys:
[` + "`" + `mountPath` + "`" + `, ` + "`" + `devicePath` + "`" + `, ` + "`" + `ip` + "`" + `, ` + "`" + `type` + "`" + `, ` + "`" + `topologyKey` + "`" + `, ` + "`" + `name` + "`" + `, ` + "`" + `containerPort` + "`" + `]
Any lists where all of the elements contain associative keys will be merged as associative lists.
### Example
> Source
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 3 # scalar
template:
spec:
containers: # associative list -- (name)
- name: nginx
image: nginx:1.7
command: ['new_run.sh', 'arg1'] # non-associative list
- name: sidecar2
image: sidecar2:v1
> Destination
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 1
template:
spec:
containers:
- name: nginx
image: nginx:1.6
command: ['old_run.sh', 'arg0']
- name: sidecar1
image: sidecar1:v1
> Result
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 3 # scalar
template:
spec:
containers: # associative list -- (name)
- name: nginx
image: nginx:1.7
command: ['new_run.sh', 'arg1'] # non-associative list
- name: sidecar1
image: sidecar1:v1
- name: sidecar2
image: sidecar2:v1`
var Merge3Long = `# Merge (3-way)
3-way merge identifies changes between an original source + updated source and merges the result
into a destination, overriding the destination fields where they have changed between
original and updated.
### Resource MergeRules
- Resources present in the original and deleted from the update are deleted.
- Resources missing from the original and added in the update are added.
- Resources present only in the dest are kept without changes.
- Resources present in both the update and the dest have their fields merged with the destination.
### Field Merge Rules
Fields are recursively merged using the following rules:
- scalars
- if present in either dest or updated and ` + "`" + `null` + "`" + `, clear the value
- if unchanged between original and updated, keep dest value
- if changed between original and updated (added, deleted, changed), take the updated value
- non-associative lists -- lists without a merge key
- if present in either dest or updated and ` + "`" + `null` + "`" + `, clear the value
- if unchanged between original and updated, keep dest value
- if changed between original and updated (added, deleted, changed), take the updated value
- map keys and fields -- paired by the map-key / field-name
- if present in either dest or updated and ` + "`" + `null` + "`" + `, clear the value
- if present only in the dest, it keeps its value
- if not-present in the dest, add the delta between original-updated as a field
- otherwise recursively merge the value between original, updated, dest
- associative list elements -- paired by the associative key
- if present only in the dest, it keeps its value
- if not-present in the dest, add the delta between original-updated as a field
- otherwise recursively merge the value between original, updated, dest
### Associative Keys
Associative keys are used to identify "same" elements within 2 different lists, and merge them.
The following fields are recognized as associative keys:
[` + "`" + `mountPath` + "`" + `, ` + "`" + `devicePath` + "`" + `, ` + "`" + `ip` + "`" + `, ` + "`" + `type` + "`" + `, ` + "`" + `topologyKey` + "`" + `, ` + "`" + `name` + "`" + `, ` + "`" + `containerPort` + "`" + `]
Any lists where all of the elements contain associative keys will be merged as associative lists.`

View File

@@ -0,0 +1,236 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Code generated by "mdtogo"; DO NOT EDIT.
package commands
var CatShort = `[Alpha] Print Resource Config from a local directory.`
var CatLong = `
[Alpha] Print Resource Config from a local directory.
DIR:
Path to local directory.
`
var CatExamples = `
# print Resource config from a directory
kustomize config cat my-dir/
# wrap Resource config from a directory in an ResourceList
kustomize config 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
... | kustomize config cat`
var CompletionShort = `Install shell completion.`
var CompletionLong = `
Install shell completion for kustomize commands and flags -- supports bash, fish and zsh.
kustomize install-completion
Registers the completion command with known shells (e.g. .bashrc, .bash_profile, etc):
complete -C /Users/USER/go/bin/kustomize kustomize
Because the completion command is embedded in kustomize directly, there is no need to update
it separately from the kustomize binary.
To uninstall shell completion run:
COMP_UNINSTALL=1 kustomize install-completion`
var CountShort = `[Alpha] Count Resources Config from a local directory.`
var CountLong = `
[Alpha] Count Resources Config from a local directory.
DIR:
Path to local directory.
`
var CountExamples = `
# print Resource counts from a directory
kustomize config count my-dir/`
var FmtShort = `[Alpha] Format yaml configuration files.`
var FmtLong = `
[Alpha] Format yaml configuration files.
Fmt will format input by ordering fields and unordered list items in Kubernetes
objects. Inputs may be directories, files or stdin, and their contents must
include both apiVersion and kind fields.
- Stdin inputs are formatted and written to stdout
- File inputs (args) are formatted and written back to the file
- Directory inputs (args) are walked, each encountered .yaml and .yml file
acts as an input
For inputs which contain multiple yaml documents separated by \n---\n,
each document will be formatted and written back to the file in the original
order.
Field ordering roughly follows the ordering defined in the source Kubernetes
resource definitions (i.e. go structures), falling back on lexicographical
sorting for unrecognized fields.
Unordered list item ordering is defined for specific Resource types and
field paths.
- .spec.template.spec.containers (by element name)
- .webhooks.rules.operations (by element value)
`
var FmtExamples = `
# format file1.yaml and file2.yml
kustomize config fmt file1.yaml file2.yml
# format all *.yaml and *.yml recursively traversing directories
kustomize config fmt my-dir/
# format kubectl output
kubectl get -o yaml deployments | kustomize config fmt
# format kustomize output
kustomize build | kustomize config fmt`
var GrepShort = `[Alpha] Search for matching Resources in a directory or from stdin`
var GrepLong = `
[Alpha] Search for matching Resources in a directory or from stdin.
QUERY:
Query to match expressed as 'path.to.field=value'.
Maps and fields are matched as '.field-name' or '.map-key'
List elements are matched as '[list-elem-field=field-value]'
The value to match is expressed as '=value'
'.' as part of a key or value can be escaped as '\.'
DIR:
Path to local directory.
`
var GrepExamples = `
# find Deployment Resources
kustomize config grep "kind=Deployment" my-dir/
# find Resources named nginx
kustomize config grep "metadata.name=nginx" my-dir/
# use tree to display matching Resources
kustomize config grep "metadata.name=nginx" my-dir/ | kustomize config tree
# look for Resources matching a specific container image
kustomize config grep "spec.template.spec.containers[name=nginx].image=nginx:1\.7\.9" my-dir/ | kustomize config tree`
var MergeShort = `[Alpha] Merge Resource configuration files`
var MergeLong = `
[Alpha] Merge Resource configuration files
Merge reads Kubernetes Resource yaml configuration files from stdin or sources packages and write
the result to stdout or a destination package.
Resources are merged using the Resource [apiVersion, kind, name, namespace] as the key. If any of
these are missing, merge will default the missing values to empty.
Resources specified later are high-precedence (the source) and Resources specified
earlier are lower-precedence (the destination).
For information on merge rules, run:
kustomize config docs merge
`
var MergeExamples = `
cat resources_and_patches.yaml | kustomize config merge > merged_resources.yaml`
var RunFnsShort = `[Alpha] Reoncile config functions to Resources.`
var RunFnsLong = `
[Alpha] Reconcile config functions to Resources.
run sequentially invokes all config functions in the directory, 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 field tells run 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, 'kustomize config run 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 write the container stdout back to example/, replacing the directory
file contents.
See ` + "`" + `kustomize help config docs-fn` + "`" + ` for more details on writing functions.
`
var RunFnsExamples = `
kustomize config run example/`
var TreeShort = `[Alpha] Display Resource structure from a directory or stdin.`
var TreeLong = `
[Alpha] Display Resource structure from a directory or stdin.
kustomize config tree may be used to print Resources in a directory or cluster, preserving structure
Args:
DIR:
Path to local directory directory.
Resource fields may be printed as part of the Resources by specifying the fields as flags.
kustomize config tree has build-in support for printing common fields, such as replicas, container images,
container names, etc.
kustomize config tree supports printing arbitrary fields using the '--field' flag.
By default, kustomize config tree uses the directory structure for the tree structure, however when printing
from the cluster, the Resource graph structure may be used instead.
`
var TreeExamples = `
# print Resources using directory structure
kustomize config tree my-dir/
# print replicas, container name, and container image and fields for Resources
kustomize config tree my-dir --replicas --image --name
# print all common Resource fields
kustomize config tree my-dir/ --all
# print the "foo"" annotation
kustomize config tree my-dir/ --field "metadata.annotations.foo"
# print the "foo"" annotation
kubectl get all -o yaml | kustomize config tree \
--field="status.conditions[type=Completed].status"
# print live Resources from a cluster using owners for graph structure
kubectl get all -o yaml | kustomize config tree --replicas --name --image \
--graph-structure=owners
# print live Resources with status condition fields
kubectl get all -o yaml | kustomize config tree \
--name --image --replicas \
--field="status.conditions[type=Completed].status" \
--field="status.conditions[type=Complete].status" \
--field="status.conditions[type=Ready].status" \
--field="status.conditions[type=ContainersReady].status"`

View File

@@ -0,0 +1,460 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Code generated by "mdtogo"; DO NOT EDIT.
package tutorials
var ConfigurationBasicsShort = `### Synopsis`
var ConfigurationBasicsLong = `
` + "`" + `kustomize config` + "`" + ` provides tools for working with local configuration directories.
First fetch a bundle of configuration to your local file system from the
Kubernetes examples repository.
git clone https://github.com/kubernetes/examples/
cd examples/
### ` + "`" + `tree` + "`" + ` -- view Resources and directory structure
` + "`" + `tree` + "`" + ` can be used to summarize the collection of Resources in a directory:
$ kustomize config tree mysql-wordpress-pd/
mysql-wordpress-pd
├── [gce-volumes.yaml] v1.PersistentVolume wordpress-pv-1
├── [gce-volumes.yaml] v1.PersistentVolume wordpress-pv-2
├── [local-volumes.yaml] v1.PersistentVolume local-pv-1
├── [local-volumes.yaml] v1.PersistentVolume local-pv-2
├── [mysql-deployment.yaml] v1.PersistentVolumeClaim mysql-pv-claim
├── [mysql-deployment.yaml] apps/v1.Deployment wordpress-mysql
├── [mysql-deployment.yaml] v1.Service wordpress-mysql
├── [wordpress-deployment.yaml] apps/v1.Deployment wordpress
├── [wordpress-deployment.yaml] v1.Service wordpress
└── [wordpress-deployment.yaml] v1.PersistentVolumeClaim wp-pv-claim
` + "`" + `tree` + "`" + ` may be provided flags to print the Resource field values. ` + "`" + `tree` + "`" + ` has a number of built-in
supported fields, and may also print arbitrary values using the ` + "`" + `--field` + "`" + ` flag to specify a field
path.
$ kustomize config tree mysql-wordpress-pd/ --name --image --replicas --ports
mysql-wordpress-pd
├── [gce-volumes.yaml] PersistentVolume wordpress-pv-1
├── [gce-volumes.yaml] PersistentVolume wordpress-pv-2
├── [local-volumes.yaml] PersistentVolume local-pv-1
├── [local-volumes.yaml] PersistentVolume local-pv-2
├── [mysql-deployment.yaml] PersistentVolumeClaim mysql-pv-claim
├── [mysql-deployment.yaml] Deployment wordpress-mysql
│   └── spec.template.spec.containers
│   └── 0
│   ├── name: mysql
│   ├── image: mysql:5.6
│   └── ports: [{name: mysql, containerPort: 3306}]
├── [mysql-deployment.yaml] Service wordpress-mysql
│   └── spec.ports: [{port: 3306}]
├── [wordpress-deployment.yaml] Deployment wordpress
│   └── spec.template.spec.containers
│   └── 0
│   ├── name: wordpress
│   ├── image: wordpress:4.8-apache
│   └── ports: [{name: wordpress, containerPort: 80}]
├── [wordpress-deployment.yaml] Service wordpress
│   └── spec.ports: [{port: 80}]
└── [wordpress-deployment.yaml] PersistentVolumeClaim wp-pv-claim
` + "`" + `tree` + "`" + ` can also be used with ` + "`" + `kubectl get` + "`" + ` to print cluster Resources using OwnersReferences
to build the tree structure.
kubectl apply -R -f cockroachdb/
kubectl get all -o yaml | kustomize config tree --graph-structure owners --name --image --replicas
.
├── [Resource] Deployment wp/wordpress
│   ├── spec.replicas: 1
│   ├── spec.template.spec.containers
│   │   └── 0
│   │   ├── name: wordpress
│   │   └── image: wordpress:4.8-apache
│   └── [Resource] ReplicaSet wp/wordpress-76b5d9f5c8
│   ├── spec.replicas: 1
│   ├── spec.template.spec.containers
│   │   └── 0
│   │   ├── name: wordpress
│   │   └── image: wordpress:4.8-apache
│   └── [Resource] Pod wp/wordpress-76b5d9f5c8-g656w
│   └── spec.containers
│   └── 0
│   ├── name: wordpress
│   └── image: wordpress:4.8-apache
├── [Resource] Service wp/wordpress
...
### ` + "`" + `cat` + "`" + ` -- view the full collection of Resources
$ kustomize config cat mysql-wordpress-pd/
apiVersion: v1
kind: PersistentVolume
metadata:
name: wordpress-pv-1
annotations:
config.kubernetes.io/path: gce-volumes.yaml
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 20Gi
gcePersistentDisk:
fsType: ext4
pdName: wordpress-1
---
apiVersion: v1
...
` + "`" + `cat` + "`" + ` prints the raw package Resources. This may be used to pipe them to other tools
such as ` + "`" + `kubectl apply -f -` + "`" + `.
## ` + "`" + `fmt` + "`" + ` -- format the Resources for a directory (like go fmt for Kubernetes Resources)
` + "`" + `fmt` + "`" + ` formats the Resource Configuration by applying a consistent style, including
ordering of fields and indentation.
$ kustomize config fmt mysql-wordpress-pd/
Run ` + "`" + `git diff` + "`" + ` and see the changes that have been applied.
### ` + "`" + `grep` + "`" + ` -- search for Resources by field values
` + "`" + `grep` + "`" + ` prints Resources matching some field value. The Resources are annotated with their
file source so they can be piped to other commands without losing this information.
$ kustomize config grep "metadata.name=wordpress" wordpress/
apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
app: wordpress
annotations:
config.kubernetes.io/path: wordpress-deployment.yaml
spec:
ports:
- port: 80
selector:
app: wordpress
tier: frontend
type: LoadBalancer
---
...
- list elements may be indexed by a field value using list[field=value]
- '.' as part of a key or value may be escaped as '\.'
$ kustomize config grep "spec.status.spec.containers[name=nginx].image=mysql:5\.6" wordpress/
apiVersion: apps/v1 # for k8s versions before 1.9.0 use apps/v1beta2 and before 1.8.0 use extensions/v1beta1
kind: Deployment
metadata:
name: wordpress-mysql
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: mysql
template:
metadata:
labels:
app: wordpress
tier: mysql
...
` + "`" + `grep` + "`" + ` may be used with kubectl to search for Resources in a cluster matching a value.
kubectl get all -o yaml | kustomize config grep "spec.replicas>0" | kustomize config tree --replicas
.
└──
├── [.] Deployment wp/wordpress
│   └── spec.replicas: 1
├── [.] ReplicaSet wp/wordpress-76b5d9f5c8
│   └── spec.replicas: 1
├── [.] Deployment wp/wordpress-mysql
│   └── spec.replicas: 1
└── [.] ReplicaSet wp/wordpress-mysql-f9447f458
└── spec.replicas: 1
### Error handling
If there is an error parsing the Resource configuration, kustomize will print an error with the file.
$ kustomize config grep "spec.template.spec.containers[name=\.*].resources.limits.cpu>1.0" ./staging/ | kustomize config tree --name --resources
Error: staging/persistent-volume-provisioning/quobyte/quobyte-admin-secret.yaml: [0]: yaml: unmarshal errors:
line 13: mapping key "type" already defined at line 9
Here the ` + "`" + `staging/persistent-volume-provisioning/quobyte/quobyte-admin-secret.yaml` + "`" + ` has a malformed
Resource. Remove the malformed Resources:
rm staging/persistent-volume-provisioning/quobyte/quobyte-admin-secret.yaml
rm staging/storage/vitess/etcd-service-template.yaml
When developing -- to get a stack trace for where an error was encountered,
use the ` + "`" + `--stack-trace` + "`" + ` flag:
$ kustomize config grep "spec.template.spec.containers[name=\.*].resources.limits.cpu>1.0" ./staging/ --stack-trace
go/src/sigs.k8s.io/kustomize/kyaml/yaml/types.go:260 (0x4d35c86)
(*RNode).GetMeta: return m, errors.Wrap(err)
go/src/sigs.k8s.io/kustomize/kyaml/kio/byteio_reader.go:130 (0x4d3e099)
(*ByteReader).Read: meta, err := node.GetMeta()
...
### Combine ` + "`" + `grep` + "`" + ` and ` + "`" + `tree` + "`" + `
` + "`" + `grep` + "`" + ` and ` + "`" + `tree` + "`" + ` may be combined to perform queries against configuration.
Query for ` + "`" + `replicas` + "`" + `:
$ kustomize config grep "spec.replicas>5" ./ | kustomize config tree --replicas
.
├── staging/sysdig-cloud
│   └── [sysdig-rc.yaml] ReplicationController sysdig-agent
│   └── spec.replicas: 100
└── staging/volumes/vsphere
└── [simple-statefulset.yaml] StatefulSet web
└── spec.replicas: 14
Query for ` + "`" + `resource.limits` + "`" + `
$ kustomize config grep "spec.template.spec.containers[name=\.*].resources.limits.memory>0" ./ | kustomize config tree --resources
.
├── cassandra
│   └── [cassandra-statefulset.yaml] StatefulSet cassandra
│   └── spec.template.spec.containers
│   └── 0
│   └── resources: {limits: {cpu: "500m", memory: 1Gi}, requests: {cpu: "500m", memory: 1Gi}}
├── staging/selenium
│   ├── [selenium-hub-deployment.yaml] Deployment selenium-hub
│   │   └── spec.template.spec.containers
│   │   └── 0
│   │   └── resources: {limits: {memory: 1000Mi, cpu: ".5"}}
│   ├── [selenium-node-chrome-deployment.yaml] Deployment selenium-node-chrome
│   │   └── spec.template.spec.containers
│   │   └── 0
│   │   └── resources: {limits: {memory: 1000Mi, cpu: ".5"}}
│   └── [selenium-node-firefox-deployment.yaml] Deployment selenium-node-firefox
│   └── spec.template.spec.containers
│   └── 0
│   └── resources: {limits: {memory: 1000Mi, cpu: ".5"}}
...
### Inverting ` + "`" + `grep` + "`" + `
The ` + "`" + `grep` + "`" + ` results may be inverted with the ` + "`" + `-v` + "`" + ` flag and used to find Resources that don't
match a query.
Find Resources that have an image specified, but the image doesn't have a tag:
$ kustomize config grep "spec.template.spec.containers[name=\.*].name=\.*" ./ | kustomize config grep "spec.template.spec.containers[name=\.*].image=\.*:\.*" -v | kustomize config tree --image --name
.
├── staging/newrelic
│   ├── [newrelic-daemonset.yaml] DaemonSet newrelic-agent
│   │   └── spec.template.spec.containers
│   │   └── 0
│   │   ├── name: newrelic
│   │   └── image: newrelic/nrsysmond
│   └── staging/newrelic-infrastructure
│   └── [newrelic-infra-daemonset.yaml] DaemonSet newrelic-infra-agent
│   └── spec.template.spec.containers
│   └── 0
│   ├── name: newrelic
│   └── image: newrelic/infrastructure
├── staging/nodesjs-mongodb
│   ├── [mongo-controller.yaml] ReplicationController mongo-controller
│   │   └── spec.template.spec.containers
│   │   └── 0
│   │   ├── name: mongo
│   │   └── image: mongo
│   └── [web-controller.yaml] ReplicationController web-controller
│   └── spec.template.spec.containers
│   └── 0
│   ├── name: web
│   └── image: <YOUR-CONTAINER>
...`
var FunctionBasicsShort = `### Synopsis`
var FunctionBasicsLong = `
` + "`" + `kustomize config` + "`" + ` enables encapsulating function for manipulating Resource
configuration inside containers, which are run using ` + "`" + `run` + "`" + `.
First fetch the kustomize repository, which contains a collection of example
functions
git clone https://github.com/kubernetes-sigs/kustomize
cd kustomize/functions/examples/
### Templating -- CockroachDB
This section demonstrates how to leverage templating based solutions from
` + "`" + `kustomize config` + "`" + `. The templating function is implemented as a ` + "`" + `bash` + "`" + ` script
using a ` + "`" + `heredoc` + "`" + `.
#### 1: Generate the Resources
` + "`" + `cd` + "`" + ` into the ` + "`" + `kustomize/functions/examples/template-heredoc-cockroachdb/` + "`" + `
directory, and invoke ` + "`" + `run` + "`" + ` on the ` + "`" + `local-resource/` + "`" + ` directory.
cd template-heredoc-cockroachdb/
# view the Resources
kustomize config tree local-resource/ --name --image --replicas
# run the function
kustomize config run local-resource/
# view the generated Resources
kustomize config tree local-resource/ --name --image --replicas
` + "`" + `run` + "`" + ` generated the directory ` + "`" + ` local-resource/config` + "`" + ` containing the generated
Resources.
#### 2. Modify the Generated Resources
- modify the generated Resources by adding an annotation, sidecar container, etc.
- modify the ` + "`" + `local-resources/example-use.yaml` + "`" + ` by changing the replicas
re-run ` + "`" + `run` + "`" + `. this will apply the updated replicas to the generated Resources,
but keep the fields that you manually added to the generated Resource configuration.
# run the function
kustomize config run local-resource/
` + "`" + `run` + "`" + ` facilitates a non-destructive *smart templating* approach that allows templating
to be composed with manual modifications directly to the template output, as well as
composition with other functions which may appy validation or injection of values.
#### 3. Function Implementation
the function implementation is located under the ` + "`" + `image/` + "`" + ` directory as a ` + "`" + `Dockerfile` + "`" + `
and a ` + "`" + `bash` + "`" + ` script.
### Templating -- Nginx
The steps in this section are identical to the CockroachDB templating example,
but the function implementation is very different, and implemented as a ` + "`" + `go` + "`" + `
program rather than a ` + "`" + `bash` + "`" + ` script.
#### 1: Generate the Resources
` + "`" + `cd` + "`" + ` into the ` + "`" + `kustomize/functions/examples/template-go-nginx/` + "`" + `
directory, and invoke ` + "`" + `run` + "`" + ` on the ` + "`" + `local-resource/` + "`" + ` directory.
cd template-go-nginx/
# view the Resources
kustomize config tree local-resource/ --name --image --replicas
# run the function
kustomize config run local-resource/
# view the generated Resources
kustomize config tree local-resource/ --name --image --replicas
` + "`" + `run` + "`" + ` generated the directory ` + "`" + ` local-resource/config` + "`" + ` containing the generated
Resources. this time it put the configuration in a single file rather than multiple
files. The mapping of Resources to files is controlled by the function itself through
annotations on the generated Resources.
#### 2. Modify the Generated Resources
- modify the generated Resources by adding an annotation, sidecar container, etc.
- modify the ` + "`" + `local-resources/example-use.yaml` + "`" + ` by changing the replicas
re-run ` + "`" + `run` + "`" + `. this will apply the updated replicas to the generated Resources,
but keep the fields that you manually added to the generated Resource configuration.
# run the function
kustomize config run local-resource/
Just like in the preceding section, the function is implemented using a non-destructive
approach which merges the generated Resources into previously generated instances.
#### 3. Function Implementation
the function implementation is located under the ` + "`" + `image/` + "`" + ` directory as a ` + "`" + `Dockerfile` + "`" + `
and a ` + "`" + `go` + "`" + ` program.
### Validation -- resource reservations
This section uses ` + "`" + `run` + "`" + ` to perform validation rather than generate Resources.
#### 1: Run the Validator
` + "`" + `cd` + "`" + ` into the ` + "`" + `kustomize/functions/examples/validator-resource-requests` + "`" + `
directory, and invoke ` + "`" + `run` + "`" + ` on the ` + "`" + `local-resource/` + "`" + ` directory.
# run the function
kustomize config run local-resource/
cpu-requests missing for a container in Deployment nginx (example-use.yaml [1])
Error: exit status 1
Usage:
...
#### 2: Fix the validation issue
The command will fail complaining that the nginx Deployment is missing ` + "`" + `cpu-requests` + "`" + `,
and print the name of the file + Resource index. Edit the file and uncomment the resources,
then re-run the functions.
kustomize config run local-resource/
The validation now passes.
### Injection -- resource reservations
This section uses ` + "`" + `run` + "`" + ` to perform injection of field values based off annotations
on the Resource.
#### 1: Run the Injector
` + "`" + `cd` + "`" + ` into the ` + "`" + `kustomize/functions/examples/inject-tshirt-sizes` + "`" + `
directory, and invoke ` + "`" + `run` + "`" + ` on the ` + "`" + `local-resource/` + "`" + ` directory.
# print the resources
kustomize config tree local-resource --resources --name
local-resource
├── [example-use.yaml] Validator
└── [example-use.yaml] Deployment nginx
└── spec.template.spec.containers
└── 0
└── name: nginx
# run the functions
kustomize config run local-resource/
# print the new resources
kustomize config tree local-resource --resources --name
├── [example-use.yaml] Validator
└── [example-use.yaml] Deployment nginx
└── spec.template.spec.containers
└── 0
├── name: nginx
└── resources: {requests: {cpu: 4, memory: 1GiB}}
#### 2: Change the tshirt-size
Change the ` + "`" + `tshirt-size` + "`" + ` annotation from ` + "`" + `medium` + "`" + ` to ` + "`" + `small` + "`" + ` and re-run the functions.
kustomize config run local-resource/
kustomize config tree local-resource/
local-resource
├── [example-use.yaml] Validator
└── [example-use.yaml] Deployment nginx
└── spec.template.spec.containers
└── 0
├── name: nginx
└── resources: {requests: {cpu: 200m, memory: 50MiB}}
The function has applied the reservations for the new tshirt-size
### Function Composition
Functions may be composed together. Try putting the Injection (tshirt-size) and
Validation functions together in the same .yaml file (separated by ` + "`" + `---` + "`" + `). Run
` + "`" + `run` + "`" + ` and observe that the first function in the file is applied to the Resources,
and then the second function in the file is applied.`

View File

@@ -0,0 +1,42 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package inpututil
import (
"sigs.k8s.io/kustomize/kyaml/yaml"
)
type MapInputsEFn func(*yaml.RNode, yaml.ResourceMeta) error
func MapInputsE(inputs []*yaml.RNode, fn MapInputsEFn) error {
for i := range inputs {
meta, err := inputs[i].GetMeta()
if err != nil {
return err
}
if err := fn(inputs[i], meta); err != nil {
return err
}
}
return nil
}
type MapInputsFn func(*yaml.RNode, yaml.ResourceMeta) ([]*yaml.RNode, error)
// runs the function against each input Resource, providing the parsed metadata
func MapInputs(inputs []*yaml.RNode, fn MapInputsFn) ([]*yaml.RNode, error) {
var outputs []*yaml.RNode
for i := range inputs {
meta, err := inputs[i].GetMeta()
if err != nil {
return nil, err
}
o, err := fn(inputs[i], meta)
if err != nil {
return nil, err
}
outputs = append(outputs, o...)
}
return outputs, nil
}