From 0e5e2648b345e847698791d3ae2fbf99679db73a Mon Sep 17 00:00:00 2001 From: Phillip Wittrock Date: Thu, 28 May 2020 12:27:14 -0700 Subject: [PATCH 1/2] write krm metadata to Krmfile instead of Kustomization --- cmd/config/ext/ext.go | 8 +- .../internal/commands/cmdlistsetters.go | 8 +- cmd/config/internal/commands/cmdset.go | 1 + .../commands/e2e/create_setter_test.go | 57 ++++++++++++ cmd/config/internal/commands/e2e/e2e_test.go | 83 ++++++++++-------- .../commands/e2e/list_setters_test.go | 43 ++++++++++ cmd/config/internal/commands/e2e/set_test.go | 62 +++++++++++++ .../internal/commands/e2e/test_util_test.go | 86 +++++++++++++++++++ kyaml/krmfile/doc.go | 19 ++++ kyaml/krmfile/krmfile.go | 10 +++ 10 files changed, 335 insertions(+), 42 deletions(-) create mode 100644 cmd/config/internal/commands/e2e/create_setter_test.go create mode 100644 cmd/config/internal/commands/e2e/list_setters_test.go create mode 100644 cmd/config/internal/commands/e2e/set_test.go create mode 100644 cmd/config/internal/commands/e2e/test_util_test.go create mode 100644 kyaml/krmfile/doc.go create mode 100644 kyaml/krmfile/krmfile.go diff --git a/cmd/config/ext/ext.go b/cmd/config/ext/ext.go index d9d64f2c1..4ebbea5f9 100644 --- a/cmd/config/ext/ext.go +++ b/cmd/config/ext/ext.go @@ -3,10 +3,14 @@ package ext -import "path/filepath" +import ( + "path/filepath" + + "sigs.k8s.io/kustomize/kyaml/krmfile" +) // GetOpenAPIFile returns the path to the file containing supplementary OpenAPI definitions. // Maybe be overridden to configure which file to read OpenAPI definitions from. var GetOpenAPIFile = func(args []string) (string, error) { - return filepath.Join(args[0], "kustomization"), nil + return filepath.Join(args[0], krmfile.KrmfileName), nil } diff --git a/cmd/config/internal/commands/cmdlistsetters.go b/cmd/config/internal/commands/cmdlistsetters.go index d5fc93610..273d9c671 100644 --- a/cmd/config/internal/commands/cmdlistsetters.go +++ b/cmd/config/internal/commands/cmdlistsetters.go @@ -126,13 +126,9 @@ func (r *ListSettersRunner) ListSubstitutions(c *cobra.Command, args []string) e s.Name, s.Pattern, setters}) } if len(r.List.Substitutions) == 0 { - // exit non-0 if no matching substitutions are found - if ExitOnError { - os.Exit(1) - } - } else { - table.Render() + return nil } + table.Render() return nil } diff --git a/cmd/config/internal/commands/cmdset.go b/cmd/config/internal/commands/cmdset.go index 6886d16ed..b0c364bef 100644 --- a/cmd/config/internal/commands/cmdset.go +++ b/cmd/config/internal/commands/cmdset.go @@ -159,6 +159,7 @@ func lookup(l setters.LookupSetters, c *cobra.Command, args []string) error { } table.Render() + fmt.Println(l.SetterCounts) if len(l.SetterCounts) == 0 { // exit non-0 if no matching setters are found os.Exit(1) diff --git a/cmd/config/internal/commands/e2e/create_setter_test.go b/cmd/config/internal/commands/e2e/create_setter_test.go new file mode 100644 index 000000000..294022f1e --- /dev/null +++ b/cmd/config/internal/commands/e2e/create_setter_test.go @@ -0,0 +1,57 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "testing" +) + +func TestCreateSetter(t *testing.T) { + tests := []test{ + { + name: "create_setter", + args: []string{"create-setter", ".", "replicas", "3"}, + files: map[string]string{ + "deployment.yaml": ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 +`, + "Krmfile": ` +apiVersion: config.k8s.io/v1alpha1 +kind: Krmfile +`, + }, + expectedFiles: map[string]string{ + "deployment.yaml": ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 # {"$openapi":"replicas"} +`, + "Krmfile": ` +apiVersion: config.k8s.io/v1alpha1 +kind: Krmfile +openAPI: + definitions: + io.k8s.cli.setters.replicas: + x-k8s-cli: + setter: + name: replicas + value: "3" +`, + }, + }, + } + runTests(t, tests) +} diff --git a/cmd/config/internal/commands/e2e/e2e_test.go b/cmd/config/internal/commands/e2e/e2e_test.go index 6ac3d7c5a..6fa2ce32f 100644 --- a/cmd/config/internal/commands/e2e/e2e_test.go +++ b/cmd/config/internal/commands/e2e/e2e_test.go @@ -12,6 +12,7 @@ import ( "path/filepath" "runtime" "strings" + "sync" "testing" "github.com/stretchr/testify/assert" @@ -19,12 +20,7 @@ import ( ) func TestRunE2e(t *testing.T) { - binDir, err := ioutil.TempDir("", "kustomize-test-") - if !assert.NoError(t, err) { - t.FailNow() - } - defer os.RemoveAll(binDir) - build(t, binDir) + binDir := build() tests := []struct { name string @@ -661,6 +657,7 @@ metadata: }, } + // TODO: dedup this with the shared version for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { @@ -710,36 +707,54 @@ metadata: } } -func build(t *testing.T, binDir string) { - build := exec.Command("go", "build", "-o", - filepath.Join(binDir, e2econtainerconfigBin)) - build.Dir = "e2econtainerconfig" - build.Stdout = os.Stdout - build.Stderr = os.Stderr - build.Env = os.Environ() - if !assert.NoError(t, build.Run()) { - t.FailNow() - } +var buildOnce sync.Once +var binDir string - build = exec.Command("go", "build", "-o", filepath.Join(binDir, kyamlBin)) - build.Dir = filepath.Join("..", "..", "..") - build.Stdout = os.Stdout - build.Stderr = os.Stderr - if !assert.NoError(t, build.Run()) { - t.FailNow() - } +func build() string { + // only build the binaries once + buildOnce.Do(func() { + var err error + binDir, err = ioutil.TempDir("", "kustomize-test-") + if err != nil { + panic(err) + } - if os.Getenv("KUSTOMIZE_DOCKER_E2E") == "false" { - return - } - build = exec.Command( - "docker", "build", ".", "-t", "gcr.io/kustomize-functions/e2econtainerconfig") - build.Dir = "e2econtainerconfig" - build.Stdout = os.Stdout - build.Stderr = os.Stderr - if !assert.NoError(t, build.Run()) { - t.FailNow() - } + build := exec.Command("go", "build", "-o", + filepath.Join(binDir, e2econtainerconfigBin)) + build.Dir = "e2econtainerconfig" + build.Stdout = os.Stdout + build.Stderr = os.Stderr + build.Env = os.Environ() + + err = build.Run() + if err != nil { + panic(err) + } + + build = exec.Command("go", "build", "-o", filepath.Join(binDir, kyamlBin)) + build.Dir = filepath.Join("..", "..", "..") + build.Stdout = os.Stdout + build.Stderr = os.Stderr + err = build.Run() + if err != nil { + panic(err) + } + + if os.Getenv("KUSTOMIZE_DOCKER_E2E") == "false" { + return + } + build = exec.Command( + "docker", "build", ".", "-t", "gcr.io/kustomize-functions/e2econtainerconfig") + build.Dir = "e2econtainerconfig" + build.Stdout = os.Stdout + build.Stderr = os.Stderr + err = build.Run() + if err != nil { + panic(err) + } + }) + + return binDir } var ( diff --git a/cmd/config/internal/commands/e2e/list_setters_test.go b/cmd/config/internal/commands/e2e/list_setters_test.go new file mode 100644 index 000000000..1358eacd3 --- /dev/null +++ b/cmd/config/internal/commands/e2e/list_setters_test.go @@ -0,0 +1,43 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import "testing" + +func TestListSetters(t *testing.T) { + tests := []test{ + { + name: "set", + args: []string{"list-setters", "."}, + files: map[string]string{ + "deployment.yaml": ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 # {"$openapi":"replicas"} +`, + "Krmfile": ` +apiVersion: config.k8s.io/v1alpha1 +kind: Krmfile +openAPI: + definitions: + io.k8s.cli.setters.replicas: + x-k8s-cli: + setter: + name: replicas + value: "3" +`, + }, + expectedStdOut: ` +NAME VALUE SET BY DESCRIPTION COUNT + replicas 3 1 +`, + }, + } + runTests(t, tests) +} diff --git a/cmd/config/internal/commands/e2e/set_test.go b/cmd/config/internal/commands/e2e/set_test.go new file mode 100644 index 000000000..1d751ee07 --- /dev/null +++ b/cmd/config/internal/commands/e2e/set_test.go @@ -0,0 +1,62 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import "testing" + +func TestSet(t *testing.T) { + tests := []test{ + { + name: "set", + args: []string{"set", ".", "replicas", "4"}, + files: map[string]string{ + "deployment.yaml": ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 # {"$openapi":"replicas"} +`, + "Krmfile": ` +apiVersion: config.k8s.io/v1alpha1 +kind: Krmfile +openAPI: + definitions: + io.k8s.cli.setters.replicas: + x-k8s-cli: + setter: + name: replicas + value: "3" +`, + }, + expectedFiles: map[string]string{ + "deployment.yaml": ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 4 # {"$openapi":"replicas"} +`, + "Krmfile": ` +apiVersion: config.k8s.io/v1alpha1 +kind: Krmfile +openAPI: + definitions: + io.k8s.cli.setters.replicas: + x-k8s-cli: + setter: + name: replicas + value: "4" +`, + }, + }, + } + runTests(t, tests) +} diff --git a/cmd/config/internal/commands/e2e/test_util_test.go b/cmd/config/internal/commands/e2e/test_util_test.go new file mode 100644 index 000000000..6835c4a0c --- /dev/null +++ b/cmd/config/internal/commands/e2e/test_util_test.go @@ -0,0 +1,86 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "bytes" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/kyaml/testutil" +) + +func TestMain(m *testing.M) { + d := build() + defer os.RemoveAll(d) + os.Exit(m.Run()) +} + +type test struct { + name string + args []string + files map[string]string + expectedFiles map[string]string + expectedErr string + expectedStdOut string +} + +func runTests(t *testing.T, tests []test) { + dir := build() + bin := filepath.Join(dir, kyamlBin) + + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + dataDir, err := ioutil.TempDir("", "kustomize-test-data-") + if !assert.NoError(t, err) { + t.FailNow() + } + defer os.RemoveAll(dataDir) + os.Chdir(dataDir) + + // write the input + for path, data := range tt.files { + err := ioutil.WriteFile(path, []byte(data), 0600) + testutil.AssertNoError(t, err) + } + + cmd := exec.Command(bin, tt.args...) + cmd.Dir = dataDir + var stdErr, stdOut bytes.Buffer + cmd.Stdout = &stdOut + cmd.Stderr = &stdErr + cmd.Env = os.Environ() + + err = cmd.Run() + if tt.expectedErr != "" { + if !assert.Contains(t, stdErr.String(), tt.expectedErr, stdErr.String()) { + t.FailNow() + } + return + } + testutil.AssertNoError(t, err, stdErr.String(), stdOut.String()) + + if tt.expectedStdOut != "" { + if !assert.Equal(t, strings.TrimSpace(stdOut.String()), strings.TrimSpace(tt.expectedStdOut)) { + t.FailNow() + } + } + + for path, data := range tt.expectedFiles { + b, err := ioutil.ReadFile(path) + testutil.AssertNoError(t, err, stdErr.String()) + + if !assert.Equal(t, strings.TrimSpace(data), strings.TrimSpace(string(b)), stdErr.String()) { + t.FailNow() + } + } + }) + } +} diff --git a/kyaml/krmfile/doc.go b/kyaml/krmfile/doc.go new file mode 100644 index 000000000..1c87e0b06 --- /dev/null +++ b/kyaml/krmfile/doc.go @@ -0,0 +1,19 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +// Package krmfile provides functionality for working with Krmfiles. +// +// Example Krmfile +// +// apiVersion: config.k8s.io/v1alpha1 +// kind: Krmfile +// openAPI: +// definitions: +// io.k8s.cli.setters.replicas: +// x-k8s-cli: +// setter: +// name: replicas +// value: "3" +// setBy: me +// description: "hello world" +package krmfile diff --git a/kyaml/krmfile/krmfile.go b/kyaml/krmfile/krmfile.go new file mode 100644 index 000000000..dbf5a9999 --- /dev/null +++ b/kyaml/krmfile/krmfile.go @@ -0,0 +1,10 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package krmfile + +// KRMFileName is the file where Krm metadata is stored +const ( + // KrmfileName is the name of the file that KRM metadata is written to + KrmfileName = "Krmfile" +) From 075846c731dcaa56ad739791bc9b88b1cdbcc71e Mon Sep 17 00:00:00 2001 From: Phillip Wittrock Date: Fri, 29 May 2020 13:03:11 -0700 Subject: [PATCH 2/2] Support initializing a directory with a Krmfile --- cmd/config/configcobra/cmds.go | 1 + cmd/config/docs/commands/init.md | 18 ++++ cmd/config/internal/commands/cmdinit.go | 60 +++++++++++++ cmd/config/internal/commands/cmdset.go | 1 - cmd/config/internal/commands/e2e/init_test.go | 33 ++++++++ cmd/config/internal/commands/init_test.go | 84 +++++++++++++++++++ .../internal/generateddocs/commands/docs.go | 14 ++++ 7 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 cmd/config/docs/commands/init.md create mode 100644 cmd/config/internal/commands/cmdinit.go create mode 100644 cmd/config/internal/commands/e2e/init_test.go create mode 100644 cmd/config/internal/commands/init_test.go diff --git a/cmd/config/configcobra/cmds.go b/cmd/config/configcobra/cmds.go index 82754e74d..2c32756e6 100644 --- a/cmd/config/configcobra/cmds.go +++ b/cmd/config/configcobra/cmds.go @@ -105,6 +105,7 @@ func NewConfigCommand(name string) *cobra.Command { root.AddCommand(commands.XArgsCommand()) root.AddCommand(commands.WrapCommand()) + root.AddCommand(commands.InitCommand(name)) root.AddCommand(commands.SetCommand(name)) root.AddCommand(commands.ListSettersCommand(name)) root.AddCommand(commands.CreateSetterCommand(name)) diff --git a/cmd/config/docs/commands/init.md b/cmd/config/docs/commands/init.md new file mode 100644 index 000000000..cc5e6f066 --- /dev/null +++ b/cmd/config/docs/commands/init.md @@ -0,0 +1,18 @@ +## init + +[Alpha] Initialize a directory with a Krmfile. + +### Synopsis + +[Alpha] Initialize a directory with a Krmfile. + + DIR: + Path to local directory. + +### Examples + + # create a Krmfile in the local directory + kustomize config init + + # create a Krmfile in my-dir/ + kustomize config init my-dir/ diff --git a/cmd/config/internal/commands/cmdinit.go b/cmd/config/internal/commands/cmdinit.go new file mode 100644 index 000000000..e72a0932d --- /dev/null +++ b/cmd/config/internal/commands/cmdinit.go @@ -0,0 +1,60 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package commands + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands" + "sigs.k8s.io/kustomize/kyaml/errors" + "sigs.k8s.io/kustomize/kyaml/krmfile" +) + +// GetInitRunner returns a command InitRunner. +func GetInitRunner(name string) *InitRunner { + r := &InitRunner{} + c := &cobra.Command{ + Use: "init DIR...", + Args: cobra.RangeArgs(0, 1), + Short: commands.InitShort, + Long: commands.InitLong, + Example: commands.InitExamples, + RunE: r.runE, + } + fixDocs(name, c) + r.Command = c + return r +} + +func InitCommand(name string) *cobra.Command { + return GetInitRunner(name).Command +} + +// InitRunner contains the init function +type InitRunner struct { + Command *cobra.Command +} + +func (r *InitRunner) runE(c *cobra.Command, args []string) error { + var dir string + if len(args) == 0 { + dir = "." + } else { + dir = args[0] + } + filename := filepath.Join(dir, krmfile.KrmfileName) + + if _, err := os.Stat(filename); err == nil || !os.IsNotExist(err) { + return errors.Errorf("directory already initialized with a Krmfile") + } + + return ioutil.WriteFile(filename, []byte(strings.TrimSpace(` +apiVersion: config.k8s.io/v1alpha1 +kind: Krmfile +`)), 0600) +} diff --git a/cmd/config/internal/commands/cmdset.go b/cmd/config/internal/commands/cmdset.go index b0c364bef..6886d16ed 100644 --- a/cmd/config/internal/commands/cmdset.go +++ b/cmd/config/internal/commands/cmdset.go @@ -159,7 +159,6 @@ func lookup(l setters.LookupSetters, c *cobra.Command, args []string) error { } table.Render() - fmt.Println(l.SetterCounts) if len(l.SetterCounts) == 0 { // exit non-0 if no matching setters are found os.Exit(1) diff --git a/cmd/config/internal/commands/e2e/init_test.go b/cmd/config/internal/commands/e2e/init_test.go new file mode 100644 index 000000000..7fd22187d --- /dev/null +++ b/cmd/config/internal/commands/e2e/init_test.go @@ -0,0 +1,33 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import "testing" + +func TestInit(t *testing.T) { + tests := []test{ + { + name: "init", + args: []string{"init"}, + expectedFiles: map[string]string{ + "Krmfile": ` +apiVersion: config.k8s.io/v1alpha1 +kind: Krmfile +`, + }, + }, + + { + name: "init", + args: []string{"init", "."}, + expectedFiles: map[string]string{ + "Krmfile": ` +apiVersion: config.k8s.io/v1alpha1 +kind: Krmfile +`, + }, + }, + } + runTests(t, tests) +} diff --git a/cmd/config/internal/commands/init_test.go b/cmd/config/internal/commands/init_test.go new file mode 100644 index 000000000..e49649712 --- /dev/null +++ b/cmd/config/internal/commands/init_test.go @@ -0,0 +1,84 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package commands_test + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/cmd/config/internal/commands" +) + +func TestInit_args(t *testing.T) { + d, err := ioutil.TempDir("", "kustomize-cat-test") + if !assert.NoError(t, err) { + return + } + defer os.RemoveAll(d) + + // fmt the files + b := &bytes.Buffer{} + r := commands.GetInitRunner("") + 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, "Krmfile")) + if !assert.NoError(t, err) { + t.FailNow() + } + + if !assert.Equal(t, strings.TrimSpace(` +apiVersion: config.k8s.io/v1alpha1 +kind: Krmfile +`), strings.TrimSpace(string(actual))) { + t.FailNow() + } + + if !assert.Equal(t, "", b.String()) { + t.FailNow() + } +} + +func TestInit_noargs(t *testing.T) { + d, err := ioutil.TempDir("", "kustomize-test-") + if !assert.NoError(t, err) { + return + } + defer os.RemoveAll(d) + + if !assert.NoError(t, os.Chdir(d)) { + t.FailNow() + } + + b := &bytes.Buffer{} + r := commands.GetInitRunner("") + r.Command.SetOut(b) + if !assert.NoError(t, r.Command.Execute()) { + t.FailNow() + } + + actual, err := ioutil.ReadFile(filepath.Join(d, "Krmfile")) + if !assert.NoError(t, err) { + t.FailNow() + } + + if !assert.Equal(t, strings.TrimSpace(` +apiVersion: config.k8s.io/v1alpha1 +kind: Krmfile +`), strings.TrimSpace(string(actual))) { + t.FailNow() + } + + if !assert.Equal(t, "", b.String()) { + t.FailNow() + } +} diff --git a/cmd/config/internal/generateddocs/commands/docs.go b/cmd/config/internal/generateddocs/commands/docs.go index 163399efb..05ce0a9d1 100644 --- a/cmd/config/internal/generateddocs/commands/docs.go +++ b/cmd/config/internal/generateddocs/commands/docs.go @@ -156,6 +156,20 @@ var GrepExamples = ` # look for Resources matching a specific container image kustomize config grep "spec.template.spec.containers[name=nginx].image=nginx:1\.7\.9" my-dir/ | kustomize config tree` +var InitShort = `[Alpha] Initialize a directory with a Krmfile.` +var InitLong = ` +[Alpha] Initialize a directory with a Krmfile. + + DIR: + Path to local directory. +` +var InitExamples = ` + # create a Krmfile in the local directory + kustomize config init + + # create a Krmfile in my-dir/ + kustomize config init my-dir/` + var ListSettersShort = `[Alpha] List setters for Resources.` var ListSettersLong = ` List setters for Resources.