diff --git a/cmd/config/configcobra/cmds.go b/cmd/config/configcobra/cmds.go index ebbf15a4a..6151809a5 100644 --- a/cmd/config/configcobra/cmds.go +++ b/cmd/config/configcobra/cmds.go @@ -80,6 +80,8 @@ func NewConfigCommand(name string) *cobra.Command { root.AddCommand(commands.SetCommand(name)) root.AddCommand(commands.ListSettersCommand(name)) root.AddCommand(commands.CreateSetterCommand(name)) + root.AddCommand(commands.SinkCommand(name)) + root.AddCommand(commands.SourceCommand(name)) root.AddCommand(&cobra.Command{ Use: "docs-merge", diff --git a/cmd/config/docs/commands/run-fns.md b/cmd/config/docs/commands/run-fns.md index 48e934d8f..08b8f6020 100644 --- a/cmd/config/docs/commands/run-fns.md +++ b/cmd/config/docs/commands/run-fns.md @@ -22,7 +22,7 @@ order they appear in the file). #### Config Functions: - Config functions are specified as Kubernetes types containing a metadata.annotations.[config.k8s.io/function] + Config functions are specified as Kubernetes types containing a metadata.annotations.[config.kubernetes.io/function] field specifying an image for the container to run. This image tells run how to invoke the container. Example config function: @@ -32,7 +32,7 @@ order they appear in the file). kind: ExampleFunctionKind metadata: annotations: - config.k8s.io/function: | + config.kubernetes.io/function: | container: # function is invoked as a container running this image image: gcr.io/example/examplefunction:v1.0.1 @@ -41,7 +41,7 @@ order they appear in the file). configField: configValue In the preceding example, 'kustomize config run example/' would identify the function by - the metadata.annotations.[config.k8s.io/function] field. It would then write all Resources in the directory to + the metadata.annotations.[config.kubernetes.io/function] 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. diff --git a/cmd/config/docs/commands/sink.md b/cmd/config/docs/commands/sink.md new file mode 100644 index 000000000..ed31de43e --- /dev/null +++ b/cmd/config/docs/commands/sink.md @@ -0,0 +1,18 @@ +## sink + +[Alpha] Implement a Sink by writing input to a local directory. + +### Synopsis + +[Alpha] Implement a Sink by writing input to a local directory. + + kustomize config sink DIR + + DIR: + Path to local directory. + +`sink` writes its input to a directory + +### Examples + + kustomize config source DIR/ | your-function | kustomize config sink DIR/ diff --git a/cmd/config/docs/commands/source.md b/cmd/config/docs/commands/source.md new file mode 100644 index 000000000..66f12e9f8 --- /dev/null +++ b/cmd/config/docs/commands/source.md @@ -0,0 +1,21 @@ +## source + +[Alpha] Implement a Source by reading a local directory. + +### Synopsis + +[Alpha] Implement a Source by reading a local directory. + + kustomize config source DIR + + DIR: + Path to local directory. + +`source` emits configuration to act as input to a function + +### Examples + + # emity configuration directory as input source to a function + kustomize config source DIR/ + + kustomize config source DIR/ | your-function | kustomize config sink DIR/ diff --git a/cmd/config/internal/commands/cat.go b/cmd/config/internal/commands/cat.go index 48b2a542e..5db747fdb 100644 --- a/cmd/config/internal/commands/cat.go +++ b/cmd/config/internal/commands/cat.go @@ -121,6 +121,13 @@ func (r *CatRunner) runE(c *cobra.Command, args []string) error { out = o } + // remove this annotation explicitly, the ByteWriter won't clear it by + // default because it doesn't set it + clear := []string{"config.kubernetes.io/path"} + if r.KeepAnnotations { + clear = nil + } + var outputs []kio.Writer outputs = append(outputs, kio.ByteWriter{ Writer: out, @@ -129,6 +136,7 @@ func (r *CatRunner) runE(c *cobra.Command, args []string) error { WrappingAPIVersion: r.WrapApiVersion, FunctionConfig: functionConfig, Style: yaml.GetStyle(r.Styles...), + ClearAnnotations: clear, }) return handleError(c, kio.Pipeline{Inputs: inputs, Filters: fltr, Outputs: outputs}.Execute()) diff --git a/cmd/config/internal/commands/cat_test.go b/cmd/config/internal/commands/cat_test.go index d9441713d..089d8c9b5 100644 --- a/cmd/config/internal/commands/cat_test.go +++ b/cmd/config/internal/commands/cat_test.go @@ -90,8 +90,6 @@ metadata: name: foo annotations: app: nginx2 - config.kubernetes.io/package: '.' - config.kubernetes.io/path: 'f1.yaml' spec: replicas: 1 --- @@ -100,8 +98,6 @@ metadata: name: foo annotations: app: nginx - config.kubernetes.io/package: '.' - config.kubernetes.io/path: 'f1.yaml' spec: selector: app: nginx @@ -114,8 +110,6 @@ metadata: app: nginx annotations: app: nginx - config.kubernetes.io/package: '.' - config.kubernetes.io/path: 'f2.yaml' spec: replicas: 3 `, b.String()) { @@ -196,8 +190,6 @@ metadata: name: foo annotations: app: nginx2 - config.kubernetes.io/package: '.' - config.kubernetes.io/path: 'f1.yaml' spec: replicas: 1 --- @@ -206,8 +198,6 @@ metadata: name: foo annotations: app: nginx - config.kubernetes.io/package: '.' - config.kubernetes.io/path: 'f1.yaml' spec: selector: app: nginx @@ -218,8 +208,6 @@ 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 @@ -233,8 +221,6 @@ metadata: name: bar annotations: app: nginx - config.kubernetes.io/package: '.' - config.kubernetes.io/path: 'f2.yaml' spec: replicas: 3 `, b.String()) { @@ -314,8 +300,6 @@ 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 @@ -414,8 +398,6 @@ metadata: name: foo annotations: app: nginx2 - config.kubernetes.io/package: '.' - config.kubernetes.io/path: 'f1.yaml' spec: replicas: 1 --- @@ -424,8 +406,6 @@ metadata: name: foo annotations: app: nginx - config.kubernetes.io/package: '.' - config.kubernetes.io/path: 'f1.yaml' spec: selector: app: nginx @@ -438,8 +418,6 @@ metadata: app: nginx annotations: app: nginx - config.kubernetes.io/package: '.' - config.kubernetes.io/path: 'f2.yaml' spec: replicas: 3 `, string(actual)) { @@ -536,8 +514,6 @@ metadata: name: foo annotations: app: nginx2 - config.kubernetes.io/package: '.' - config.kubernetes.io/path: 'f1.yaml' spec: replicas: 1 --- @@ -546,8 +522,6 @@ metadata: name: foo annotations: app: nginx - config.kubernetes.io/package: '.' - config.kubernetes.io/path: 'f1.yaml' spec: selector: app: nginx @@ -560,8 +534,6 @@ metadata: app: nginx annotations: app: nginx - config.kubernetes.io/package: '.' - config.kubernetes.io/path: 'f2.yaml' spec: replicas: 3 `, string(actual)) { diff --git a/cmd/config/internal/commands/grep_test.go b/cmd/config/internal/commands/grep_test.go index 6d869af35..6c6f15f86 100644 --- a/cmd/config/internal/commands/grep_test.go +++ b/cmd/config/internal/commands/grep_test.go @@ -76,7 +76,6 @@ metadata: annotations: app: nginx2 config.kubernetes.io/index: '0' - config.kubernetes.io/package: '.' config.kubernetes.io/path: 'f1.yaml' spec: replicas: 1 @@ -87,7 +86,6 @@ metadata: annotations: app: nginx config.kubernetes.io/index: '1' - config.kubernetes.io/package: '.' config.kubernetes.io/path: 'f1.yaml' spec: selector: diff --git a/cmd/config/internal/commands/sink.go b/cmd/config/internal/commands/sink.go new file mode 100644 index 000000000..f1b05d9d6 --- /dev/null +++ b/cmd/config/internal/commands/sink.go @@ -0,0 +1,48 @@ +// 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" +) + +// GetSinkRunner returns a command for Sink. +func GetSinkRunner(name string) *SinkRunner { + r := &SinkRunner{} + c := &cobra.Command{ + Use: "sink DIR", + Short: commands.SinkShort, + Long: commands.SinkLong, + Example: commands.SinkExamples, + RunE: r.runE, + Args: cobra.ExactArgs(1), + } + fixDocs(name, c) + r.Command = c + return r +} + +func SinkCommand(name string) *cobra.Command { + return GetSinkRunner(name).Command +} + +// SinkRunner contains the run function +type SinkRunner struct { + Command *cobra.Command +} + +func (r *SinkRunner) runE(c *cobra.Command, args []string) error { + err := kio.Pipeline{ + Inputs: []kio.Reader{&kio.ByteReader{Reader: c.InOrStdin()}}, + Outputs: []kio.Writer{ + &kio.LocalPackageWriter{ + PackagePath: args[0], + ClearAnnotations: []string{"config.kubernetes.io/path"}, + }, + }, + }.Execute() + return handleError(c, err) +} diff --git a/cmd/config/internal/commands/sink_test.go b/cmd/config/internal/commands/sink_test.go new file mode 100644 index 000000000..d09e9bd6f --- /dev/null +++ b/cmd/config/internal/commands/sink_test.go @@ -0,0 +1,140 @@ +// 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 TestSinkCommand(t *testing.T) { + d, err := ioutil.TempDir("", "kustomize-source-test") + if !assert.NoError(t, err) { + t.FailNow() + } + defer os.RemoveAll(d) + + // fmt the files + b := &bytes.Buffer{} + r := commands.GetSinkRunner("") + r.Command.SetIn(bytes.NewBufferString(`apiVersion: config.kubernetes.io/v1alpha1 +kind: ResourceList +items: +- kind: Deployment + metadata: + labels: + app: nginx2 + name: foo + annotations: + app: nginx2 + config.kubernetes.io/index: '0' + 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/path: 'f1.yaml' + spec: + selector: + app: nginx +- apiVersion: v1 + kind: Abstraction + metadata: + name: foo + annotations: + config.kubernetes.io/function: | + container: + image: gcr.io/example/reconciler:v1 + config.kubernetes.io/local-config: "true" + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f2.yaml' + spec: + replicas: 3 +- apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app: nginx + name: bar + annotations: + app: nginx + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f2.yaml' + spec: + replicas: 3 +`)) + r.Command.SetArgs([]string{d}) + r.Command.SetOut(b) + if !assert.NoError(t, r.Command.Execute()) { + t.FailNow() + } + + actual, err := ioutil.ReadFile(filepath.Join(d, "f1.yaml")) + if !assert.NoError(t, err) { + t.FailNow() + } + expected := `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 +` + if !assert.Equal(t, expected, string(actual)) { + t.FailNow() + } + + actual, err = ioutil.ReadFile(filepath.Join(d, "f2.yaml")) + if !assert.NoError(t, err) { + t.FailNow() + } + expected = `apiVersion: v1 +kind: Abstraction +metadata: + name: foo + annotations: + config.kubernetes.io/function: | + container: + image: gcr.io/example/reconciler:v1 + 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 +` + if !assert.Equal(t, expected, string(actual)) { + t.FailNow() + } +} diff --git a/cmd/config/internal/commands/source.go b/cmd/config/internal/commands/source.go new file mode 100644 index 000000000..5926d3af9 --- /dev/null +++ b/cmd/config/internal/commands/source.go @@ -0,0 +1,77 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package commands + +import ( + "fmt" + + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +// GetSourceRunner returns a command for Source. +func GetSourceRunner(name string) *SourceRunner { + r := &SourceRunner{} + c := &cobra.Command{ + Use: "source DIR", + Short: commands.SourceShort, + Long: commands.SourceLong, + Example: commands.SourceExamples, + RunE: r.runE, + Args: cobra.ExactArgs(1), + } + fixDocs(name, c) + c.Flags().StringVar(&r.WrapKind, "wrap-kind", kio.ResourceListKind, + "output using this format.") + c.Flags().StringVar(&r.WrapApiVersion, "wrap-version", kio.ResourceListAPIVersion, + "output using this format.") + c.Flags().StringVar(&r.FunctionConfig, "function-config", "", + "path to function config.") + r.Command = c + _ = c.MarkFlagFilename("function-config", "yaml", "json", "yml") + return r +} + +func SourceCommand(name string) *cobra.Command { + return GetSourceRunner(name).Command +} + +// SourceRunner contains the run function +type SourceRunner struct { + WrapKind string + WrapApiVersion string + FunctionConfig string + Command *cobra.Command +} + +func (r *SourceRunner) 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}.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 outputs []kio.Writer + outputs = append(outputs, kio.ByteWriter{ + Writer: c.OutOrStdout(), + KeepReaderAnnotations: true, + WrappingKind: r.WrapKind, + WrappingAPIVersion: r.WrapApiVersion, + FunctionConfig: functionConfig, + }) + + err := kio.Pipeline{ + Inputs: []kio.Reader{kio.LocalPackageReader{PackagePath: args[0]}}, + Outputs: outputs}.Execute() + return handleError(c, err) +} diff --git a/cmd/config/internal/commands/source_test.go b/cmd/config/internal/commands/source_test.go new file mode 100644 index 000000000..a4c01bb1c --- /dev/null +++ b/cmd/config/internal/commands/source_test.go @@ -0,0 +1,136 @@ +// 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 TestSourceCommand(t *testing.T) { + d, err := ioutil.TempDir("", "kustomize-source-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/function: | + container: + image: gcr.io/example/reconciler:v1 + 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.GetSourceRunner("") + r.Command.SetArgs([]string{d}) + r.Command.SetOut(b) + if !assert.NoError(t, r.Command.Execute()) { + return + } + + if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 +kind: ResourceList +items: +- kind: Deployment + metadata: + labels: + app: nginx2 + name: foo + annotations: + app: nginx2 + config.kubernetes.io/index: '0' + 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/path: 'f1.yaml' + spec: + selector: + app: nginx +- apiVersion: v1 + kind: Abstraction + metadata: + name: foo + annotations: + config.kubernetes.io/function: | + container: + image: gcr.io/example/reconciler:v1 + config.kubernetes.io/local-config: "true" + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f2.yaml' + spec: + replicas: 3 +- apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app: nginx + name: bar + annotations: + app: nginx + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f2.yaml' + spec: + replicas: 3 +`, b.String()) { + return + } +} diff --git a/cmd/config/internal/commands/tree_test.go b/cmd/config/internal/commands/tree_test.go index bd104d72a..b326a489f 100644 --- a/cmd/config/internal/commands/tree_test.go +++ b/cmd/config/internal/commands/tree_test.go @@ -104,7 +104,6 @@ metadata: namespace: default annotations: app: nginx2 - config.kubernetes.io/package: . config.kubernetes.io/path: f1.yaml spec: replicas: 1 @@ -118,7 +117,6 @@ metadata: namespace: default annotations: app: nginx2 - config.kubernetes.io/package: . config.kubernetes.io/path: f1.yaml spec: replicas: 1 @@ -132,7 +130,6 @@ metadata: namespace: default annotations: app: nginx2 - config.kubernetes.io/package: . config.kubernetes.io/path: f1.yaml spec: replicas: 1 @@ -146,7 +143,6 @@ metadata: namespace: default2 annotations: app: nginx2 - config.kubernetes.io/package: . config.kubernetes.io/path: f1.yaml spec: replicas: 1 @@ -160,7 +156,6 @@ metadata: namespace: default annotations: app: nginx3 - config.kubernetes.io/package: . config.kubernetes.io/path: f1.yaml spec: replicas: 1 @@ -171,8 +166,7 @@ metadata: app: nginx annotations: app: nginx - config.kubernetes.io/package: bar-package - config.kubernetes.io/path: f2.yaml + config.kubernetes.io/path: bar-package/f2.yaml name: bar spec: replicas: 3 @@ -183,7 +177,6 @@ metadata: namespace: default annotations: app: nginx - config.kubernetes.io/package: . config.kubernetes.io/path: f1.yaml spec: selector: diff --git a/cmd/config/internal/generateddocs/commands/docs.go b/cmd/config/internal/generateddocs/commands/docs.go index d638549b7..aca3a2d7d 100644 --- a/cmd/config/internal/generateddocs/commands/docs.go +++ b/cmd/config/internal/generateddocs/commands/docs.go @@ -220,7 +220,7 @@ order they appear in the file). #### Config Functions: - Config functions are specified as Kubernetes types containing a metadata.annotations.[config.k8s.io/function] + Config functions are specified as Kubernetes types containing a metadata.annotations.[config.kubernetes.io/function] field specifying an image for the container to run. This image tells run how to invoke the container. Example config function: @@ -230,7 +230,7 @@ order they appear in the file). kind: ExampleFunctionKind metadata: annotations: - config.k8s.io/function: | + config.kubernetes.io/function: | container: # function is invoked as a container running this image image: gcr.io/example/examplefunction:v1.0.1 @@ -239,7 +239,7 @@ order they appear in the file). configField: configValue In the preceding example, 'kustomize config run example/' would identify the function by - the metadata.annotations.[config.k8s.io/function] field. It would then write all Resources in the directory to + the metadata.annotations.[config.kubernetes.io/function] 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. @@ -332,6 +332,37 @@ var SetExamples = ` name: test-app2 # {"description":"test environment","type":"string","x-kustomize":{"setBy":"dev","partialFieldSetters":[{"name":"name-prefix","value":"test"}]}} ...` +var SinkShort = `[Alpha] Implement a Sink by writing input to a local directory.` +var SinkLong = ` +[Alpha] Implement a Sink by writing input to a local directory. + + kustomize config sink DIR + + DIR: + Path to local directory. + +` + "`" + `sink` + "`" + ` writes its input to a directory +` +var SinkExamples = ` + kustomize config source DIR/ | your-function | kustomize config sink DIR/` + +var SourceShort = `[Alpha] Implement a Source by reading a local directory.` +var SourceLong = ` +[Alpha] Implement a Source by reading a local directory. + + kustomize config source DIR + + DIR: + Path to local directory. + +` + "`" + `source` + "`" + ` emits configuration to act as input to a function +` +var SourceExamples = ` + # emity configuration directory as input source to a function + kustomize config source DIR/ + + kustomize config source DIR/ | your-function | kustomize config sink DIR/` + var TreeShort = `[Alpha] Display Resource structure from a directory or stdin.` var TreeLong = ` [Alpha] Display Resource structure from a directory or stdin. diff --git a/cmd/config/main.go b/cmd/config/main.go index ff1b6f2eb..ebc89adcf 100644 --- a/cmd/config/main.go +++ b/cmd/config/main.go @@ -9,6 +9,7 @@ package main import ( "os" + "sigs.k8s.io/kustomize/cmd/config/complete" "sigs.k8s.io/kustomize/cmd/config/configcobra" "sigs.k8s.io/kustomize/kyaml/commandutil" ) @@ -16,7 +17,10 @@ import ( func main() { // enable the config commands os.Setenv(commandutil.EnableAlphaCommmandsEnvName, "true") - if err := configcobra.NewConfigCommand("").Execute(); err != nil { + cmd := configcobra.NewConfigCommand("") + complete.Complete(cmd).Complete("config") + + if err := cmd.Execute(); err != nil { os.Exit(1) } } diff --git a/functions/examples/injection-tshirt-sizes/README.md b/functions/examples/injection-tshirt-sizes/README.md index 204ea11d8..1da48c642 100644 --- a/functions/examples/injection-tshirt-sizes/README.md +++ b/functions/examples/injection-tshirt-sizes/README.md @@ -15,7 +15,7 @@ Resource configuration, and looks for invalid configuration. ## Function invocation The function is invoked by authoring a [local Resource](local-resource) -with `metadata.annotations.[config.k8s.io/function]` and running: +with `metadata.annotations.[config.kubernetes.io/function]` and running: kustomize config run local-resource/ diff --git a/functions/examples/injection-tshirt-sizes/local-resource/example-use.yaml b/functions/examples/injection-tshirt-sizes/local-resource/example-use.yaml index 71f3d8878..8a4c4bd10 100644 --- a/functions/examples/injection-tshirt-sizes/local-resource/example-use.yaml +++ b/functions/examples/injection-tshirt-sizes/local-resource/example-use.yaml @@ -5,7 +5,7 @@ apiVersion: examples.config.kubernetes.io/v1beta1 kind: Validator metadata: annotations: - config.k8s.io/function: | + config.kubernetes.io/function: | container: image: gcr.io/kustomize-functions/example-tshirt:v0.1.0 --- diff --git a/functions/examples/template-go-nginx/README.md b/functions/examples/template-go-nginx/README.md index f90913b28..44a677ae2 100644 --- a/functions/examples/template-go-nginx/README.md +++ b/functions/examples/template-go-nginx/README.md @@ -25,7 +25,7 @@ function input, and writing the function output. ## Function invocation The function is invoked by authoring a [local Resource](local-resource) -with `metadata.annotations.[config.k8s.io/function]` and running: +with `metadata.annotations.[config.kubernetes.io/function]` and running: kustomize config run local-resource/ diff --git a/functions/examples/template-go-nginx/local-resource/example-use.yaml b/functions/examples/template-go-nginx/local-resource/example-use.yaml index f0de94576..972e34996 100644 --- a/functions/examples/template-go-nginx/local-resource/example-use.yaml +++ b/functions/examples/template-go-nginx/local-resource/example-use.yaml @@ -6,7 +6,7 @@ kind: Nginx metadata: name: demo annotations: - config.k8s.io/function: | + config.kubernetes.io/function: | container: image: gcr.io/kustomize-functions/example-nginx:v0.1.0 spec: diff --git a/functions/examples/template-heredoc-cockroachdb/README.md b/functions/examples/template-heredoc-cockroachdb/README.md index 68bb23bf3..96be73e02 100644 --- a/functions/examples/template-heredoc-cockroachdb/README.md +++ b/functions/examples/template-heredoc-cockroachdb/README.md @@ -20,7 +20,7 @@ heavy lifting of implementing the function interface. ## Function invocation The function is invoked by authoring a [local Resource](local-resource) -with `metadata.annotations.[config.k8s.io/function]` and running: +with `metadata.annotations.[config.kubernetes.io/function]` and running: kustomize config run local-resource/ diff --git a/functions/examples/template-heredoc-cockroachdb/local-resource/example-use.yaml b/functions/examples/template-heredoc-cockroachdb/local-resource/example-use.yaml index fa598b488..37c48f040 100644 --- a/functions/examples/template-heredoc-cockroachdb/local-resource/example-use.yaml +++ b/functions/examples/template-heredoc-cockroachdb/local-resource/example-use.yaml @@ -7,7 +7,7 @@ kind: CockroachDB metadata: name: demo annotations: - config.k8s.io/function: | + config.kubernetes.io/function: | container: image: gcr.io/kustomize-functions/example-cockroachdb:v0.1.0 spec: diff --git a/functions/examples/validator-kubeval/README.md b/functions/examples/validator-kubeval/README.md index ffbc98608..85ca92b38 100644 --- a/functions/examples/validator-kubeval/README.md +++ b/functions/examples/validator-kubeval/README.md @@ -18,7 +18,7 @@ the `API` struct definition in [main.go](image/main.go) for documentation. ## Function invocation The function is invoked by authoring a [local Resource](local-resource) -with `metadata.annotations.[config.k8s.io/function]` and running: +with `metadata.annotations.[config.kubernetes.io/function]` and running: kustomize config run local-resource/ diff --git a/functions/examples/validator-kubeval/local-resource/example-use.yaml b/functions/examples/validator-kubeval/local-resource/example-use.yaml index 96fdb80f7..d18722b62 100644 --- a/functions/examples/validator-kubeval/local-resource/example-use.yaml +++ b/functions/examples/validator-kubeval/local-resource/example-use.yaml @@ -5,7 +5,7 @@ apiVersion: examples.config.kubernetes.io/v1beta1 kind: Kubeval metadata: annotations: - config.k8s.io/function: | + config.kubernetes.io/function: | container: image: gcr.io/kustomize-functions/example-validator-kubeval:v0.1.0 spec: diff --git a/functions/examples/validator-resource-requests/README.md b/functions/examples/validator-resource-requests/README.md index 9689a0b69..03e4685f8 100644 --- a/functions/examples/validator-resource-requests/README.md +++ b/functions/examples/validator-resource-requests/README.md @@ -15,7 +15,7 @@ Resource configuration, and looks for invalid configuration. ## Function invocation The function is invoked by authoring a [local Resource](local-resource) -with `metadata.annotations.[config.k8s.io/function]` and running: +with `metadata.annotations.[config.kubernetes.io/function]` and running: kustomize config run local-resource/ diff --git a/functions/examples/validator-resource-requests/local-resource/example-use.yaml b/functions/examples/validator-resource-requests/local-resource/example-use.yaml index 39f537f79..0aa371e6f 100644 --- a/functions/examples/validator-resource-requests/local-resource/example-use.yaml +++ b/functions/examples/validator-resource-requests/local-resource/example-use.yaml @@ -5,7 +5,7 @@ apiVersion: examples.config.kubernetes.io/v1beta1 kind: Validator metadata: annotations: - config.k8s.io/function: | + config.kubernetes.io/function: | container: image: gcr.io/kustomize-functions/example-validator:v0.1.0 --- diff --git a/kustomize/main.go b/kustomize/main.go index 8545bc22c..2dfb826a0 100644 --- a/kustomize/main.go +++ b/kustomize/main.go @@ -6,35 +6,17 @@ package main import ( "os" - "path/filepath" - "github.com/spf13/cobra" "sigs.k8s.io/kustomize/cmd/config/complete" "sigs.k8s.io/kustomize/kustomize/v3/internal/commands" ) func main() { cmd := commands.NewDefaultCommand() - completion(cmd) + complete.Complete(cmd).Complete("kustomize") if err := cmd.Execute(); err != nil { os.Exit(1) } os.Exit(0) } - -// completion performs shell completion if kustomize is being called to provide -// shell completion commands. -func completion(cmd *cobra.Command) { - // bash shell completion passes the command name as the first argument - // do this after configuring cmd so it has all the subcommands - if len(os.Args) > 1 { - // use the base name in case kustomize is called with an absolute path - name := filepath.Base(os.Args[1]) - if name == "kustomize" { - // complete calls kustomize with itself as an argument - complete.Complete(cmd).Complete("kustomize") - os.Exit(0) - } - } -} diff --git a/kyaml/kio/filters/container.go b/kyaml/kio/filters/container.go index cdc01293e..2af93e7ab 100644 --- a/kyaml/kio/filters/container.go +++ b/kyaml/kio/filters/container.go @@ -194,6 +194,13 @@ func (c *IsReconcilerFilter) Filter(inputs []*yaml.RNode) ([]*yaml.RNode, error) return out, nil } +const ( + FunctionAnnotationKey = "config.kubernetes.io/function" + oldFunctionAnnotationKey = "config.k8s.io/function" +) + +var functionAnnotationKeys = []string{FunctionAnnotationKey, oldFunctionAnnotationKey} + // GetContainerName returns the container image for an API if one exists func GetContainerName(n *yaml.RNode) (string, string) { meta, _ := n.GetMeta() @@ -201,11 +208,14 @@ func GetContainerName(n *yaml.RNode) (string, string) { // path to the function, this will be mounted into the container path := meta.Annotations[kioutil.PathAnnotation] - functionAnnotation := meta.Annotations["config.k8s.io/function"] - if functionAnnotation != "" { - annotationContent, _ := yaml.Parse(functionAnnotation) - image, _ := annotationContent.Pipe(yaml.Lookup("container", "image")) - return image.YNode().Value, path + // check previous keys for backwards compatibility + for _, s := range functionAnnotationKeys { + functionAnnotation := meta.Annotations[s] + if functionAnnotation != "" { + annotationContent, _ := yaml.Parse(functionAnnotation) + image, _ := annotationContent.Pipe(yaml.Lookup("container", "image")) + return image.YNode().Value, path + } } container := meta.Annotations["config.kubernetes.io/container"] diff --git a/kyaml/kio/filters/container_test.go b/kyaml/kio/filters/container_test.go index 314400e6f..5a573a8f1 100644 --- a/kyaml/kio/filters/container_test.go +++ b/kyaml/kio/filters/container_test.go @@ -333,12 +333,12 @@ metadata: c, _ = GetContainerName(n) assert.Equal(t, "gcr.io/foo/bar:something", c) - // container from config.k8s.io/function annotation + // container from config.kubernetes.io/function annotation n, err = yaml.Parse(`apiVersion: v1 kind: MyThing metadata: annotations: - config.k8s.io/function: | + config.kubernetes.io/function: | container: image: gcr.io/foo/bar:something `) diff --git a/kyaml/kio/kioutil/kioutil.go b/kyaml/kio/kioutil/kioutil.go index 56e8b74a5..b5d6fcc5c 100644 --- a/kyaml/kio/kioutil/kioutil.go +++ b/kyaml/kio/kioutil/kioutil.go @@ -20,9 +20,6 @@ const ( // PathAnnotation records the path to the file the Resource was read from PathAnnotation AnnotationKey = "config.kubernetes.io/path" - - // PackageAnnotation records the name of the package the Resource was read from - PackageAnnotation AnnotationKey = "config.kubernetes.io/package" ) func GetFileAnnotations(rn *yaml.RNode) (string, string, error) { diff --git a/kyaml/kio/pkgio_reader.go b/kyaml/kio/pkgio_reader.go index a618f612c..d4f53265d 100644 --- a/kyaml/kio/pkgio_reader.go +++ b/kyaml/kio/pkgio_reader.go @@ -263,7 +263,6 @@ func (r *LocalPackageReader) initReaderAnnotations(path string, _ os.FileInfo) { r.SetAnnotations = map[string]string{} } if !r.OmitReaderAnnotations { - r.SetAnnotations[kioutil.PackageAnnotation] = filepath.Dir(path) r.SetAnnotations[kioutil.PathAnnotation] = path } } diff --git a/kyaml/kio/pkgio_reader_test.go b/kyaml/kio/pkgio_reader_test.go index 74cef9dad..48d4e60da 100644 --- a/kyaml/kio/pkgio_reader_test.go +++ b/kyaml/kio/pkgio_reader_test.go @@ -109,14 +109,12 @@ func TestLocalPackageReader_Read_pkg(t *testing.T) { metadata: annotations: config.kubernetes.io/index: '0' - config.kubernetes.io/package: '.' config.kubernetes.io/path: 'a_test.yaml' `, `c: d # second metadata: annotations: config.kubernetes.io/index: '1' - config.kubernetes.io/package: '.' config.kubernetes.io/path: 'a_test.yaml' `, `# second thing @@ -128,7 +126,6 @@ g: metadata: annotations: config.kubernetes.io/index: '0' - config.kubernetes.io/package: '.' config.kubernetes.io/path: 'b_test.yaml' `, } @@ -171,14 +168,12 @@ func TestLocalPackageReader_Read_file(t *testing.T) { metadata: annotations: config.kubernetes.io/index: '0' - config.kubernetes.io/package: '.' config.kubernetes.io/path: 'a_test.yaml' `, `c: d # second metadata: annotations: config.kubernetes.io/index: '1' - config.kubernetes.io/package: '.' config.kubernetes.io/path: 'a_test.yaml' `, } @@ -270,14 +265,12 @@ func TestLocalPackageReader_Read_nestedDirs(t *testing.T) { metadata: annotations: config.kubernetes.io/index: '0' - config.kubernetes.io/package: 'a${SEP}b' config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml' `, `c: d # second metadata: annotations: config.kubernetes.io/index: '1' - config.kubernetes.io/package: 'a${SEP}b' config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml' `, `# second thing @@ -289,7 +282,6 @@ g: metadata: annotations: config.kubernetes.io/index: '0' - config.kubernetes.io/package: 'a${SEP}b' config.kubernetes.io/path: 'a${SEP}b${SEP}b_test.yaml' `, } @@ -328,14 +320,12 @@ func TestLocalPackageReader_Read_matchRegex(t *testing.T) { metadata: annotations: config.kubernetes.io/index: '0' - config.kubernetes.io/package: 'a${SEP}b' config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml' `, `c: d # second metadata: annotations: config.kubernetes.io/index: '1' - config.kubernetes.io/package: 'a${SEP}b' config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml' `, } @@ -371,14 +361,12 @@ func TestLocalPackageReader_Read_skipSubpackage(t *testing.T) { metadata: annotations: config.kubernetes.io/index: '0' - config.kubernetes.io/package: 'a${SEP}b' config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml' `, `c: d # second metadata: annotations: config.kubernetes.io/index: '1' - config.kubernetes.io/package: 'a${SEP}b' config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml' `, } @@ -414,14 +402,12 @@ func TestLocalPackageReader_Read_includeSubpackage(t *testing.T) { metadata: annotations: config.kubernetes.io/index: '0' - config.kubernetes.io/package: 'a${SEP}b' config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml' `, `c: d # second metadata: annotations: config.kubernetes.io/index: '1' - config.kubernetes.io/package: 'a${SEP}b' config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml' `, `# second thing @@ -433,7 +419,6 @@ g: metadata: annotations: config.kubernetes.io/index: '0' - config.kubernetes.io/package: 'a${SEP}c' config.kubernetes.io/path: 'a${SEP}c${SEP}c_test.yaml' `, } diff --git a/kyaml/kio/pkgio_writer.go b/kyaml/kio/pkgio_writer.go index 91977f7a9..2c428edd4 100644 --- a/kyaml/kio/pkgio_writer.go +++ b/kyaml/kio/pkgio_writer.go @@ -58,7 +58,6 @@ func (r LocalPackageWriter) Write(nodes []*yaml.RNode) error { } if !r.KeepReaderAnnotations { - r.ClearAnnotations = append(r.ClearAnnotations, kioutil.PackageAnnotation) r.ClearAnnotations = append(r.ClearAnnotations, kioutil.PathAnnotation) } diff --git a/kyaml/kio/tree.go b/kyaml/kio/tree.go index 0ea8f0ae6..8d084d9f1 100644 --- a/kyaml/kio/tree.go +++ b/kyaml/kio/tree.go @@ -258,7 +258,7 @@ func (p TreeWriter) index(nodes []*yaml.RNode) map[string][]*yaml.RNode { // not a resource continue } - pkg := meta.Annotations[kioutil.PackageAnnotation] + pkg := filepath.Dir(meta.Annotations[kioutil.PathAnnotation]) indexByPackage[pkg] = append(indexByPackage[pkg], nodes[i]) } return indexByPackage diff --git a/kyaml/kio/tree_test.go b/kyaml/kio/tree_test.go index fb9041c16..c167d66ca 100644 --- a/kyaml/kio/tree_test.go +++ b/kyaml/kio/tree_test.go @@ -21,8 +21,7 @@ metadata: namespace: default annotations: app: nginx3 - config.kubernetes.io/package: foo-package/3 - config.kubernetes.io/path: f3.yaml + config.kubernetes.io/path: foo-package/3/f3.yaml spec: replicas: 1 --- @@ -34,8 +33,7 @@ metadata: namespace: default annotations: app: nginx2 - config.kubernetes.io/package: foo-package - config.kubernetes.io/path: f1.yaml + config.kubernetes.io/path: foo-package/f1.yaml spec: replicas: 1 --- @@ -45,8 +43,7 @@ metadata: app: nginx annotations: app: nginx - config.kubernetes.io/package: bar-package - config.kubernetes.io/path: f2.yaml + config.kubernetes.io/path: bar-package/f2.yaml name: bar spec: replicas: 3 @@ -57,8 +54,7 @@ metadata: namespace: default annotations: app: nginx - config.kubernetes.io/package: foo-package - config.kubernetes.io/path: f1.yaml + config.kubernetes.io/path: foo-package/f1.yaml spec: selector: app: nginx @@ -94,7 +90,6 @@ metadata: namespace: default annotations: app: nginx3 - config.kubernetes.io/package: . config.kubernetes.io/path: f3.yaml spec: replicas: 1 @@ -107,8 +102,7 @@ metadata: namespace: default annotations: app: nginx2 - config.kubernetes.io/package: foo-package - config.kubernetes.io/path: f1.yaml + config.kubernetes.io/path: foo-package/f1.yaml spec: replicas: 1 --- @@ -118,8 +112,8 @@ metadata: app: nginx annotations: app: nginx - config.kubernetes.io/package: bar-package - config.kubernetes.io/path: f2.yaml + config.kubernetes.io/package: + config.kubernetes.io/path: bar-package/f2.yaml name: bar spec: replicas: 3 @@ -167,7 +161,6 @@ metadata: namespace: default annotations: app: nginx2 - config.kubernetes.io/package: . config.kubernetes.io/path: f1.yaml spec: replicas: 1 @@ -181,7 +174,6 @@ metadata: namespace: default annotations: app: nginx2 - config.kubernetes.io/package: . config.kubernetes.io/path: f1.yaml spec: replicas: 1 @@ -195,7 +187,6 @@ metadata: namespace: default annotations: app: nginx2 - config.kubernetes.io/package: . config.kubernetes.io/path: f1.yaml spec: replicas: 1 @@ -209,7 +200,6 @@ metadata: namespace: default2 annotations: app: nginx2 - config.kubernetes.io/package: . config.kubernetes.io/path: f1.yaml spec: replicas: 1 @@ -223,7 +213,6 @@ metadata: namespace: default annotations: app: nginx3 - config.kubernetes.io/package: . config.kubernetes.io/path: f1.yaml spec: replicas: 1 @@ -234,8 +223,7 @@ metadata: app: nginx annotations: app: nginx - config.kubernetes.io/package: bar-package - config.kubernetes.io/path: f2.yaml + config.kubernetes.io/path: bar-package/f2.yaml name: bar spec: replicas: 3 @@ -246,7 +234,6 @@ metadata: namespace: default annotations: app: nginx - config.kubernetes.io/package: . config.kubernetes.io/path: f1.yaml spec: selector: @@ -550,13 +537,12 @@ metadata: } if !assert.Equal(t, ` -└── - ├── [.] Service myapp-staging/cockroachdb - ├── [.] StatefulSet myapp-staging/cockroachdb - ├── [.] Pod myapp-staging/cockroachdb-0 - ├── [.] Pod myapp-staging/cockroachdb-1 - ├── [.] Pod myapp-staging/cockroachdb-2 - └── [.] Application myapp-staging/myapp +├── [.] Service myapp-staging/cockroachdb +├── [.] StatefulSet myapp-staging/cockroachdb +├── [.] Pod myapp-staging/cockroachdb-0 +├── [.] Pod myapp-staging/cockroachdb-1 +├── [.] Pod myapp-staging/cockroachdb-2 +└── [.] Application myapp-staging/myapp `, out.String()) { t.FailNow() } diff --git a/kyaml/yaml/types.go b/kyaml/yaml/types.go index 99d3eb668..af9f11692 100644 --- a/kyaml/yaml/types.go +++ b/kyaml/yaml/types.go @@ -6,7 +6,6 @@ package yaml import ( "bytes" "fmt" - "reflect" "strings" "gopkg.in/yaml.v3" @@ -70,6 +69,14 @@ func IsFieldEmpty(node *MapNode) bool { return false } +// GetValue returns underlying yaml.Node Value field +func GetValue(node *RNode) string { + if IsEmpty(node) { + return "" + } + return node.YNode().Value +} + func IsFieldNull(node *MapNode) bool { return node != nil && node.Value != nil && node.Value.YNode() != nil && node.Value.YNode().Tag == NullNodeTag @@ -280,23 +287,79 @@ func (r *ResourceIdentifier) GetKind() string { var ErrMissingMetadata = fmt.Errorf("missing Resource metadata") -// GetMeta returns the ResourceMeta for a RNode +// Field names +const ( + AnnotationsField = "annotations" + APIVersionField = "apiVersion" + KindField = "kind" + MetadataField = "metadata" + NameField = "name" + NamespaceField = "namespace" + LabelsField = "labels" +) + +// GetMeta returns the ResourceMeta for an RNode func (rn *RNode) GetMeta() (ResourceMeta, error) { + if IsEmpty(rn) { + return ResourceMeta{}, nil + } + missingMeta := true + n := rn + if n.YNode().Kind == DocumentNode { + // get the content is this is the document node + n = NewRNode(n.Content()[0]) + } + + // don't decode into the struct directly or it will fail on UTF-8 issues + // which appear in comments m := ResourceMeta{} - b := &bytes.Buffer{} - e := NewEncoder(b) - if err := e.Encode(rn.YNode()); err != nil { - return m, errors.Wrap(err) + + // TODO: consider optimizing this parsing + if f := n.Field(APIVersionField); !IsFieldEmpty(f) { + m.APIVersion = GetValue(f.Value) + missingMeta = false } - if err := e.Close(); err != nil { - return m, errors.Wrap(err) + if f := n.Field(KindField); !IsFieldEmpty(f) { + m.Kind = GetValue(f.Value) + missingMeta = false } - d := yaml.NewDecoder(b) - d.KnownFields(false) // only want to parse the metadata - if err := d.Decode(&m); err != nil { - return m, errors.Wrap(err) + + mf := n.Field(MetadataField) + if IsFieldEmpty(mf) { + if missingMeta { + return m, ErrMissingMetadata + } + return m, nil } - if reflect.DeepEqual(m, ResourceMeta{}) { + meta := mf.Value + + if f := meta.Field(NameField); !IsFieldEmpty(f) { + m.Name = f.Value.YNode().Value + missingMeta = false + } + if f := meta.Field(NamespaceField); !IsFieldEmpty(f) { + m.Namespace = GetValue(f.Value) + missingMeta = false + } + + if f := meta.Field(LabelsField); !IsFieldEmpty(f) { + m.Labels = map[string]string{} + _ = f.Value.VisitFields(func(node *MapNode) error { + m.Labels[GetValue(node.Key)] = GetValue(node.Value) + return nil + }) + missingMeta = false + } + if f := meta.Field(AnnotationsField); !IsFieldEmpty(f) { + m.Annotations = map[string]string{} + _ = f.Value.VisitFields(func(node *MapNode) error { + m.Annotations[GetValue(node.Key)] = GetValue(node.Value) + return nil + }) + missingMeta = false + } + + if missingMeta { return m, ErrMissingMetadata } return m, nil diff --git a/kyaml/yaml/types_test.go b/kyaml/yaml/types_test.go new file mode 100644 index 000000000..3812f4e37 --- /dev/null +++ b/kyaml/yaml/types_test.go @@ -0,0 +1,49 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package yaml + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// Test that non-UTF8 characters in comments don't cause failures +func TestRNode_GetMeta_UTF16(t *testing.T) { + sr, err := Parse(`apiVersion: rbac.istio.io/v1alpha1 +kind: ServiceRole +metadata: + name: wildcard + namespace: default + # If set to [“*”], it refers to all services in the namespace + annotations: + foo: bar +spec: + rules: + # There is one service in default namespace, should not result in a validation error + # If set to [“*”], it refers to all services in the namespace + - services: ["*"] + methods: ["GET", "HEAD"] +`) + if !assert.NoError(t, err) { + t.FailNow() + } + actual, err := sr.GetMeta() + if !assert.NoError(t, err) { + t.FailNow() + } + + expected := ResourceMeta{ + APIVersion: "rbac.istio.io/v1alpha1", + Kind: "ServiceRole", + ObjectMeta: ObjectMeta{ + Name: "wildcard", + Namespace: "default", + Annotations: map[string]string{"foo": "bar"}, + }, + } + if !assert.Equal(t, expected, actual) { + t.FailNow() + } +}