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/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/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/generateddocs/commands/docs.go b/cmd/config/internal/generateddocs/commands/docs.go index 039e3fbf4..aca3a2d7d 100644 --- a/cmd/config/internal/generateddocs/commands/docs.go +++ b/cmd/config/internal/generateddocs/commands/docs.go @@ -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.