From 8b0f4bf714731752bdb839e745d1939bb1809d85 Mon Sep 17 00:00:00 2001 From: Phillip Wittrock Date: Fri, 8 Nov 2019 09:22:34 -0800 Subject: [PATCH] kyaml: Add kyaml filters as cli commands --- cmd/kyaml/.golangci.yml | 54 +++++ cmd/kyaml/LICENSE_TEMPLATE | 2 + cmd/kyaml/Makefile | 38 ++++ cmd/kyaml/README.md | 4 + cmd/kyaml/cmd/cat.go | 130 ++++++++++++ cmd/kyaml/cmd/cat_test.go | 304 +++++++++++++++++++++++++++ cmd/kyaml/cmd/count.go | 92 +++++++++ cmd/kyaml/cmd/count_test.go | 73 +++++++ cmd/kyaml/cmd/fmt.go | 127 ++++++++++++ cmd/kyaml/cmd/fmt_test.go | 162 +++++++++++++++ cmd/kyaml/cmd/grep.go | 144 +++++++++++++ cmd/kyaml/cmd/grep_test.go | 272 ++++++++++++++++++++++++ cmd/kyaml/cmd/merge.go | 80 +++++++ cmd/kyaml/cmd/tree.go | 229 ++++++++++++++++++++ cmd/kyaml/cmd/tree_test.go | 345 +++++++++++++++++++++++++++++++ cmd/kyaml/cmd/util.go | 36 ++++ cmd/kyaml/go.mod | 12 ++ cmd/kyaml/go.sum | 132 ++++++++++++ cmd/kyaml/inpututil/inpututil.go | 42 ++++ cmd/kyaml/main.go | 37 ++++ kyaml/Makefile | 2 +- kyaml/go.mod | 3 + kyaml/go.sum | 9 + kyaml/kio/kioutil/kioutil.go | 19 ++ kyaml/kio/tree.go | 2 +- travis/kyaml-pre-commit.sh | 3 + 26 files changed, 2351 insertions(+), 2 deletions(-) create mode 100644 cmd/kyaml/.golangci.yml create mode 100644 cmd/kyaml/LICENSE_TEMPLATE create mode 100644 cmd/kyaml/Makefile create mode 100644 cmd/kyaml/README.md create mode 100644 cmd/kyaml/cmd/cat.go create mode 100644 cmd/kyaml/cmd/cat_test.go create mode 100644 cmd/kyaml/cmd/count.go create mode 100644 cmd/kyaml/cmd/count_test.go create mode 100644 cmd/kyaml/cmd/fmt.go create mode 100644 cmd/kyaml/cmd/fmt_test.go create mode 100644 cmd/kyaml/cmd/grep.go create mode 100644 cmd/kyaml/cmd/grep_test.go create mode 100644 cmd/kyaml/cmd/merge.go create mode 100644 cmd/kyaml/cmd/tree.go create mode 100644 cmd/kyaml/cmd/tree_test.go create mode 100644 cmd/kyaml/cmd/util.go create mode 100644 cmd/kyaml/go.mod create mode 100644 cmd/kyaml/go.sum create mode 100644 cmd/kyaml/inpututil/inpututil.go create mode 100644 cmd/kyaml/main.go diff --git a/cmd/kyaml/.golangci.yml b/cmd/kyaml/.golangci.yml new file mode 100644 index 000000000..4b800e332 --- /dev/null +++ b/cmd/kyaml/.golangci.yml @@ -0,0 +1,54 @@ +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +run: + deadline: 5m + +linters: + # please, do not use `enable-all`: it's deprecated and will be removed soon. + # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint + disable-all: true + enable: + - bodyclose + - deadcode + - depguard + - dogsled + - dupl +# - errcheck +# - funlen +# - gochecknoinits +# - goconst +# - gocritic + - gocyclo + - gofmt + - goimports +# - golint + - gosec + - gosimple + - govet + - ineffassign + - interfacer +# - lll + - misspell + - nakedret +# - scopelint + - staticcheck + - structcheck +# - stylecheck + - typecheck + - unconvert +# - unparam + - unused + - varcheck +# - whitespace + + +linters-settings: + dupl: + threshold: 400 + lll: + line-length: 170 + gocyclo: + min-complexity: 30 + golint: + min-confidence: 0.85 diff --git a/cmd/kyaml/LICENSE_TEMPLATE b/cmd/kyaml/LICENSE_TEMPLATE new file mode 100644 index 000000000..0c2b3b655 --- /dev/null +++ b/cmd/kyaml/LICENSE_TEMPLATE @@ -0,0 +1,2 @@ +Copyright {{.Year}} {{.Holder}} +SPDX-License-Identifier: Apache-2.0 diff --git a/cmd/kyaml/Makefile b/cmd/kyaml/Makefile new file mode 100644 index 000000000..23936d59d --- /dev/null +++ b/cmd/kyaml/Makefile @@ -0,0 +1,38 @@ +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +.PHONY: generate license fix vet fmt test build tidy + +GOPATH := $(shell go env GOPATH) + +build: + go build -v -o $(GOPATH)/bin/kyaml . + +all: generate license fix vet fmt test lint tidy + +fix: + go fix ./... + +fmt: + go fmt ./... + +generate: + go generate ./... + +license: + (which $(GOPATH)/bin/addlicense || go get github.com/google/addlicense) + $(GOPATH)/bin/addlicense -y 2019 -c "The Kubernetes Authors." -f LICENSE_TEMPLATE . + +tidy: + go mod tidy + +lint: + (which $(GOPATH)/bin/golangci-lint || go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.19.1) + $(GOPATH)/bin/golangci-lint run ./... + +test: + go test -cover ./... + +vet: + go vet ./... + diff --git a/cmd/kyaml/README.md b/cmd/kyaml/README.md new file mode 100644 index 000000000..f81711fe0 --- /dev/null +++ b/cmd/kyaml/README.md @@ -0,0 +1,4 @@ +# cmd/kyaml + +This package exists to expose kyaml filters directly as cli commands for the purposes +of development of the kyaml package and as a reference implementation for using the libraries. diff --git a/cmd/kyaml/cmd/cat.go b/cmd/kyaml/cmd/cat.go new file mode 100644 index 000000000..ad8f46919 --- /dev/null +++ b/cmd/kyaml/cmd/cat.go @@ -0,0 +1,130 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "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() *CatRunner { + r := &CatRunner{} + c := &cobra.Command{ + Use: "cat DIR...", + Short: "Print Resource Config from a local directory", + Long: `Print Resource Config from a local directory. + + DIR: + Path to local directory. +`, + Example: `# print Resource config from a directory +kyaml cat my-dir/ + +# wrap Resource config from a directory in an ResourceList +kyaml cat my-dir/ --wrap-kind ResourceList --wrap-version kyaml.kustomize.dev/v1alpha1 --function-config fn.yaml + +# unwrap Resource config from a directory in an ResourceList +... | kyaml cat +`, + RunE: r.runE, + } + 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.IncludeReconcilers, "include-reconcilers", false, + "if true, include reconciler Resources in the output.") + c.Flags().BoolVar(&r.ExcludeNonReconcilers, "exclude-non-reconcilers", false, + "if true, exclude non-reconciler Resources in the output.") + r.Command = c + return r +} + +func CatCommand() *cobra.Command { + return GetCatRunner().Command +} + +// CatRunner contains the run function +type CatRunner struct { + IncludeSubpackages bool + Format bool + KeepAnnotations bool + WrapKind string + WrapApiVersion string + FunctionConfig string + Styles []string + StripComments bool + IncludeReconcilers bool + ExcludeNonReconcilers bool + Command *cobra.Command +} + +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.IsReconcilerFilter{ + ExcludeReconcilers: !r.IncludeReconcilers, + IncludeNonReconcilers: !r.ExcludeNonReconcilers, + }) + if r.Format { + fltr = append(fltr, filters.FormatFilter{}) + } + if r.StripComments { + fltr = append(fltr, filters.StripCommentsFilter{}) + } + + var outputs []kio.Writer + outputs = append(outputs, kio.ByteWriter{ + Writer: c.OutOrStdout(), + KeepReaderAnnotations: r.KeepAnnotations, + WrappingKind: r.WrapKind, + WrappingApiVersion: r.WrapApiVersion, + FunctionConfig: functionConfig, + Style: yaml.GetStyle(r.Styles...), + }) + + return kio.Pipeline{Inputs: inputs, Filters: fltr, Outputs: outputs}.Execute() +} diff --git a/cmd/kyaml/cmd/cat_test.go b/cmd/kyaml/cmd/cat_test.go new file mode 100644 index 000000000..755e52186 --- /dev/null +++ b/cmd/kyaml/cmd/cat_test.go @@ -0,0 +1,304 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package cmd_test + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/cmd/kyaml/cmd" +) + +// 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: gcr.io/example/image:version +kind: Abstraction +metadata: + name: foo +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 := cmd.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: gcr.io/example/image:version +kind: Abstraction +metadata: + name: foo +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 := cmd.GetCatRunner() + r.Command.SetArgs([]string{d, "--include-reconcilers"}) + 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: gcr.io/example/image:version +kind: Abstraction +metadata: + name: foo + annotations: + config.kubernetes.io/package: . + config.kubernetes.io/path: f2.yaml +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: gcr.io/example/image:version +kind: Abstraction +metadata: + name: foo +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 := cmd.GetCatRunner() + r.Command.SetArgs([]string{d, "--include-reconcilers", "--exclude-non-reconcilers"}) + r.Command.SetOut(b) + if !assert.NoError(t, r.Command.Execute()) { + return + } + + if !assert.Equal(t, `apiVersion: gcr.io/example/image:version +kind: Abstraction +metadata: + name: foo + annotations: + config.kubernetes.io/package: . + config.kubernetes.io/path: f2.yaml +spec: + replicas: 3 +`, b.String()) { + return + } +} diff --git a/cmd/kyaml/cmd/count.go b/cmd/kyaml/cmd/count.go new file mode 100644 index 000000000..c22d56c22 --- /dev/null +++ b/cmd/kyaml/cmd/count.go @@ -0,0 +1,92 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 +// +package cmd + +import ( + "fmt" + "sort" + + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/sets" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func GetCountRunner() *CountRunner { + r := &CountRunner{} + c := &cobra.Command{ + Use: "count DIR...", + Short: "Count Resources Config from a local directory", + Long: `Count Resources Config from a local directory. + + DIR: + Path to local directory. +`, + Example: `# print Resource counts from a directory +kyaml count my-dir/ +`, + RunE: r.runE, + } + 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() *cobra.Command { + return GetCountRunner().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 kio.Pipeline{ + Inputs: inputs, + Outputs: out, + }.Execute() +} diff --git a/cmd/kyaml/cmd/count_test.go b/cmd/kyaml/cmd/count_test.go new file mode 100644 index 000000000..ad0e7cc3f --- /dev/null +++ b/cmd/kyaml/cmd/count_test.go @@ -0,0 +1,73 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package cmd_test + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/cmd/kyaml/cmd" +) + +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 := cmd.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 + } +} diff --git a/cmd/kyaml/cmd/fmt.go b/cmd/kyaml/cmd/fmt.go new file mode 100644 index 000000000..16c043bef --- /dev/null +++ b/cmd/kyaml/cmd/fmt.go @@ -0,0 +1,127 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/kio/filters" +) + +// FmtCmd returns a command FmtRunner. +func GetFmtRunner() *FmtRunner { + r := &FmtRunner{} + c := &cobra.Command{ + Use: "fmt", + Short: "Format yaml configuration files", + Long: `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) +`, + Example: ` + # format file1.yaml and file2.yml + kyaml fmt file1.yaml file2.yml + + # format all *.yaml and *.yml recursively traversing directories + kyaml fmt my-dir/ + + # format kubectl output + kubectl get -o yaml deployments | kyaml fmt + + # format kustomize output + kustomize build | kyaml fmt +`, + RunE: r.runE, + PreRunE: r.preRunE, + } + 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() *cobra.Command { + return GetFmtRunner().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 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 err + } + } + return nil +} diff --git a/cmd/kyaml/cmd/fmt_test.go b/cmd/kyaml/cmd/fmt_test.go new file mode 100644 index 000000000..4be7327a7 --- /dev/null +++ b/cmd/kyaml/cmd/fmt_test.go @@ -0,0 +1,162 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package cmd_test + +import ( + "bytes" + "io/ioutil" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/cmd/kyaml/cmd" + "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 := cmd.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 := cmd.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 := cmd.GetFmtRunner() + r.Command.SetOut(out) + r.Command.SetIn(in) + + // fmt the files + r = cmd.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 := cmd.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 := cmd.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") +} diff --git a/cmd/kyaml/cmd/grep.go b/cmd/kyaml/cmd/grep.go new file mode 100644 index 000000000..3a507eb10 --- /dev/null +++ b/cmd/kyaml/cmd/grep.go @@ -0,0 +1,144 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 +// +package cmd + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/api/resource" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/kio/filters" +) + +// Cmd returns a command GrepRunner. +func GetGrepRunner() *GrepRunner { + r := &GrepRunner{} + c := &cobra.Command{ + Use: "grep QUERY [DIR]...", + Short: "Search for matching Resources in a directory or from stdin", + Long: `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. +`, + Example: `# find Deployment Resources +kyaml grep "kind=Deployment" my-dir/ + +# find Resources named nginx +kyaml grep "metadata.name=nginx" my-dir/ + +# use tree to display matching Resources +kyaml grep "metadata.name=nginx" my-dir/ | kyaml tree + +# look for Resources matching a specific container image +kyaml grep "spec.template.spec.containers[name=nginx].image=nginx:1\.7\.9" my-dir/ | kyaml tree +`, + PreRunE: r.preRunE, + RunE: r.runE, + Args: cobra.MinimumNArgs(1), + } + 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() *cobra.Command { + return GetGrepRunner().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 kio.Pipeline{ + Inputs: inputs, + Filters: filters, + Outputs: []kio.Writer{kio.ByteWriter{ + Writer: c.OutOrStdout(), + KeepReaderAnnotations: r.KeepAnnotations, + }}, + }.Execute() +} diff --git a/cmd/kyaml/cmd/grep_test.go b/cmd/kyaml/cmd/grep_test.go new file mode 100644 index 000000000..eb59f94ee --- /dev/null +++ b/cmd/kyaml/cmd/grep_test.go @@ -0,0 +1,272 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package cmd_test + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/cmd/kyaml/cmd" +) + +// 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 := cmd.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 := cmd.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 := cmd.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 = cmd.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 := cmd.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 + } +} diff --git a/cmd/kyaml/cmd/merge.go b/cmd/kyaml/cmd/merge.go new file mode 100644 index 000000000..cd2640eaf --- /dev/null +++ b/cmd/kyaml/cmd/merge.go @@ -0,0 +1,80 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/kio/filters" +) + +func GetMergeRunner() *MergeRunner { + r := &MergeRunner{} + c := &cobra.Command{ + Use: "merge [SOURCE_DIR...] [DESTINATION_DIR]", + Short: "Merge Resource configuration files", + Long: `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: + + kyaml help merge +`, + Example: `cat resources_and_patches.yaml | kyaml merge > merged_resources.yaml`, + RunE: r.runE, + } + r.Command = c + r.Command.Flags().BoolVar(&r.InvertOrder, "invert-order", false, + "if true, merge Resources in the reverse order") + return r +} + +func MergeCommand() *cobra.Command { + return GetMergeRunner().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 kio.Pipeline{Inputs: inputs, Filters: filters, Outputs: outputs}.Execute() +} diff --git a/cmd/kyaml/cmd/tree.go b/cmd/kyaml/cmd/tree.go new file mode 100644 index 000000000..593ca1172 --- /dev/null +++ b/cmd/kyaml/cmd/tree.go @@ -0,0 +1,229 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "path/filepath" + "strings" + + "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() *TreeRunner { + r := &TreeRunner{} + c := &cobra.Command{ + Use: "tree DIR", + Short: "Display Resource structure from a directory or stdin", + Long: `Display Resource structure from a directory or stdin. + +kyaml 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. + +kyaml tree has build-in support for printing common fields, such as replicas, container images, +container names, etc. + +kyaml tree supports printing arbitrary fields using the '--field' flag. + +By default, kyaml tree uses the directory structure for the tree structure, however when printing +from the cluster, the Resource graph structure may be used instead. +`, + Example: `# print Resources using directory structure +kyaml tree my-dir/ + +# print replicas, container name, and container image and fields for Resources +kyaml tree my-dir --replicas --image --name + +# print all common Resource fields +kyaml tree my-dir/ --all + +# print the "foo"" annotation +kyaml tree my-dir/ --field "metadata.annotations.foo" + +# print the "foo"" annotation +kubectl get all -o yaml | kyaml tree my-dir/ --structure=graph \ + --field="status.conditions[type=Completed].status" + +# print live Resources from a cluster using graph for structure +kubectl get all -o yaml | kyaml tree --replicas --name --image --structure=graph + + +# print live Resources using graph for structure +kubectl get all,applications,releasetracks -o yaml | kyaml tree --structure=graph \ + --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" +`, + RunE: r.runE, + Args: cobra.MaximumNArgs(1), + } + 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.includeReconcilers, "include-reconcilers", false, + "if true, include reconciler Resources in the output.") + c.Flags().BoolVar(&r.excludeNonReconcilers, "exclude-non-reconcilers", false, + "if true, exclude non-reconciler Resources in the output.") + c.Flags().StringVar(&r.structure, "graph-structure", "directory", + "Graph structure to use for printing the tree. may be 'directory' or 'owners'.") + + r.Command = c + return r +} + +func TreeCommand() *cobra.Command { + return GetTreeRunner().Command +} + +// TreeRunner contains the run function +type TreeRunner struct { + IncludeSubpackages bool + Command *cobra.Command + name bool + resources bool + ports bool + images bool + replicas bool + all bool + env bool + args bool + cmd bool + fields []string + includeReconcilers bool + excludeNonReconcilers bool + structure string +} + +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.IsReconcilerFilter{ + ExcludeReconcilers: !r.includeReconcilers, + IncludeNonReconcilers: !r.excludeNonReconcilers, + }} + + return 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}, + } +} diff --git a/cmd/kyaml/cmd/tree_test.go b/cmd/kyaml/cmd/tree_test.go new file mode 100644 index 000000000..a5bbb2c46 --- /dev/null +++ b/cmd/kyaml/cmd/tree_test.go @@ -0,0 +1,345 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package cmd_test + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/cmd/kyaml/cmd" +) + +// 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: gcr.io/example/reconciler:v1 +kind: Abstraction +metadata: + name: foo +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 := cmd.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 := cmd.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 := cmd.GetTreeRunner() + r.Command.SetArgs([]string{d, "--include-reconcilers"}) + 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: 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 := cmd.GetTreeRunner() + r.Command.SetArgs([]string{d, "--include-reconcilers", "--exclude-non-reconcilers"}) + 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 + } +} diff --git a/cmd/kyaml/cmd/util.go b/cmd/kyaml/cmd/util.go new file mode 100644 index 000000000..d747661d4 --- /dev/null +++ b/cmd/kyaml/cmd/util.go @@ -0,0 +1,36 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 +// +package cmd + +import ( + "fmt" + "strings" +) + +// 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 +} diff --git a/cmd/kyaml/go.mod b/cmd/kyaml/go.mod new file mode 100644 index 000000000..31cbb29c7 --- /dev/null +++ b/cmd/kyaml/go.mod @@ -0,0 +1,12 @@ +module sigs.k8s.io/kustomize/cmd/kyaml + +go 1.12 + +require ( + github.com/spf13/cobra v0.0.5 + github.com/stretchr/testify v1.4.0 + k8s.io/apimachinery v0.0.0-20191107105744-2c7f8d2b0fd8 + sigs.k8s.io/kustomize/kyaml v0.0.0 +) + +replace sigs.k8s.io/kustomize/kyaml v0.0.0 => ../../kyaml diff --git a/cmd/kyaml/go.sum b/cmd/kyaml/go.sum new file mode 100644 index 000000000..3210abe87 --- /dev/null +++ b/cmd/kyaml/go.sum @@ -0,0 +1,132 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d h1:LCPbGQ34PMrwad11aMZ+dbz5SAsq/0ySjRwQ8I9Qwd8= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/apimachinery v0.0.0-20191107105744-2c7f8d2b0fd8 h1:IMbv9UOD3FKoJGrF2u3EtJIeQC99bnXxS7JKciNFUi8= +k8s.io/apimachinery v0.0.0-20191107105744-2c7f8d2b0fd8/go.mod h1:DJOb3m0kw91A0YaUsaoYChi4d7xVF84HLiuRCxGsV04= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/cmd/kyaml/inpututil/inpututil.go b/cmd/kyaml/inpututil/inpututil.go new file mode 100644 index 000000000..557b35b64 --- /dev/null +++ b/cmd/kyaml/inpututil/inpututil.go @@ -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 +} diff --git a/cmd/kyaml/main.go b/cmd/kyaml/main.go new file mode 100644 index 000000000..0c329ad54 --- /dev/null +++ b/cmd/kyaml/main.go @@ -0,0 +1,37 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "os" + + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/cmd/kyaml/cmd" + "sigs.k8s.io/kustomize/kyaml/yaml/merge2" + "sigs.k8s.io/kustomize/kyaml/yaml/merge3" +) + +var root = &cobra.Command{ + Use: "kyaml", + Short: "kyaml reference comand", + Long: `Description: + Reference implementation for using the kyaml libraries. +`, + Example: ``, +} + +func main() { + root.AddCommand(cmd.GrepCommand()) + root.AddCommand(cmd.TreeCommand()) + root.AddCommand(cmd.CatCommand()) + root.AddCommand(cmd.FmtCommand()) + root.AddCommand(cmd.MergeCommand()) + root.AddCommand(cmd.CountCommand()) + root.AddCommand(&cobra.Command{Use: "merge", Long: merge2.Help}) + root.AddCommand(&cobra.Command{Use: "merge3", Long: merge3.Help}) + + if err := root.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/kyaml/Makefile b/kyaml/Makefile index de4536c95..23936d59d 100644 --- a/kyaml/Makefile +++ b/kyaml/Makefile @@ -8,7 +8,7 @@ GOPATH := $(shell go env GOPATH) build: go build -v -o $(GOPATH)/bin/kyaml . -all: generate license fix vet fmt test lint lint tidy +all: generate license fix vet fmt test lint tidy fix: go fix ./... diff --git a/kyaml/go.mod b/kyaml/go.mod index 16723e205..7bb60a053 100644 --- a/kyaml/go.mod +++ b/kyaml/go.mod @@ -5,7 +5,10 @@ go 1.12 require ( github.com/davecgh/go-spew v1.1.1 github.com/go-errors/errors v1.0.1 + github.com/kr/pretty v0.1.0 // indirect github.com/stretchr/testify v1.4.0 github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + gopkg.in/yaml.v2 v2.2.4 // indirect gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d ) diff --git a/kyaml/go.sum b/kyaml/go.sum index 72708136d..7e5637da6 100644 --- a/kyaml/go.sum +++ b/kyaml/go.sum @@ -3,6 +3,11 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -12,7 +17,11 @@ github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mB github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d h1:LCPbGQ34PMrwad11aMZ+dbz5SAsq/0ySjRwQ8I9Qwd8= gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/kyaml/kio/kioutil/kioutil.go b/kyaml/kio/kioutil/kioutil.go index f307c48ab..0ea18bff1 100644 --- a/kyaml/kio/kioutil/kioutil.go +++ b/kyaml/kio/kioutil/kioutil.go @@ -65,6 +65,25 @@ func Map(nodes []*yaml.RNode, fn func(*yaml.RNode) (*yaml.RNode, error)) ([]*yam return returnNodes, nil } +func MapMeta(nodes []*yaml.RNode, fn func(*yaml.RNode, yaml.ResourceMeta) (*yaml.RNode, error)) ( + []*yaml.RNode, error) { + var returnNodes []*yaml.RNode + for i := range nodes { + meta, err := nodes[i].GetMeta() + if err != nil { + return nil, err + } + n, err := fn(nodes[i], meta) + if err != nil { + return nil, err + } + if n != nil { + returnNodes = append(returnNodes, n) + } + } + return returnNodes, nil +} + // SortNodes sorts nodes in place: // - by PathAnnotation annotation // - by IndexAnnotation annotation diff --git a/kyaml/kio/tree.go b/kyaml/kio/tree.go index 14e3766b2..109b0f45d 100644 --- a/kyaml/kio/tree.go +++ b/kyaml/kio/tree.go @@ -20,7 +20,7 @@ type TreeStructure string const ( // TreeStructurePackage configures TreeWriter to generate the tree structure off of the // Resources packages. - TreeStructurePackage TreeStructure = "package" + TreeStructurePackage TreeStructure = "directory" // TreeStructureOwners configures TreeWriter to generate the tree structure off of the // Resource owners. diff --git a/travis/kyaml-pre-commit.sh b/travis/kyaml-pre-commit.sh index 68c43d9cf..e76566f86 100755 --- a/travis/kyaml-pre-commit.sh +++ b/travis/kyaml-pre-commit.sh @@ -2,4 +2,7 @@ set -e cd kyaml +make all + +cd ../cmd/kyaml make all \ No newline at end of file