Isolated content of pkg/kustomize

This commit is contained in:
Jeffrey Regan
2018-05-11 14:01:10 -07:00
parent 7f06454de8
commit 8aad8f447b
127 changed files with 0 additions and 0 deletions

96
pkg/commands/addpatch.go Normal file
View File

@@ -0,0 +1,96 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"errors"
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/kustomize/constants"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
type addPatchOptions struct {
patchFilePath string
}
// newCmdAddPatch adds the name of a file containing a patch to the kustomization file.
func newCmdAddPatch(out, errOut io.Writer, fsys fs.FileSystem) *cobra.Command {
var o addPatchOptions
cmd := &cobra.Command{
Use: "patch",
Short: "Add the name of a file containing a patch to the kustomization file.",
Example: `
add patch {filepath}`,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args)
if err != nil {
return err
}
err = o.Complete(cmd, args)
if err != nil {
return err
}
return o.RunAddPatch(out, errOut, fsys)
},
}
return cmd
}
// Validate validates addPatch command.
func (o *addPatchOptions) Validate(args []string) error {
if len(args) != 1 {
return errors.New("must specify a patch file")
}
o.patchFilePath = args[0]
return nil
}
// Complete completes addPatch command.
func (o *addPatchOptions) Complete(cmd *cobra.Command, args []string) error {
return nil
}
// RunAddPatch runs addPatch command (do real work).
func (o *addPatchOptions) RunAddPatch(out, errOut io.Writer, fsys fs.FileSystem) error {
_, err := fsys.Stat(o.patchFilePath)
if err != nil {
return err
}
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
return err
}
m, err := mf.read()
if err != nil {
return err
}
if stringInSlice(o.patchFilePath, m.Patches) {
return fmt.Errorf("patch %s already in kustomization file", o.patchFilePath)
}
m.Patches = append(m.Patches, o.patchFilePath)
return mf.write(m)
}

View File

@@ -0,0 +1,94 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"bytes"
"os"
"testing"
"strings"
"k8s.io/kubectl/pkg/kustomize/constants"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
const (
patchFileName = "myWonderfulPatch.yaml"
patchFileContent = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
`
)
func TestAddPatchHappyPath(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(patchFileName, []byte(patchFileContent))
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdAddPatch(buf, os.Stderr, fakeFS)
args := []string{patchFileName}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadFile(constants.KustomizationFileName)
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
if !strings.Contains(string(content), patchFileName) {
t.Errorf("expected patch name in kustomization")
}
}
func TestAddPatchAlreadyThere(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(patchFileName, []byte(patchFileContent))
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdAddPatch(buf, os.Stderr, fakeFS)
args := []string{patchFileName}
err := cmd.RunE(cmd, args)
if err != nil {
t.Fatalf("unexpected cmd error: %v", err)
}
// adding an existing patch should return an error
err = cmd.RunE(cmd, args)
if err == nil {
t.Errorf("expected already there problem")
}
if err.Error() != "patch "+patchFileName+" already in kustomization file" {
t.Errorf("unexpected error %v", err)
}
}
func TestAddPatchNoArgs(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
cmd := newCmdAddPatch(buf, os.Stderr, fakeFS)
err := cmd.Execute()
if err == nil {
t.Errorf("expected error: %v", err)
}
if err.Error() != "must specify a patch file" {
t.Errorf("incorrect error: %v", err.Error())
}
}

View File

@@ -0,0 +1,96 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"errors"
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/kustomize/constants"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
type addResourceOptions struct {
resourceFilePath string
}
// newCmdAddResource adds the name of a file containing a resource to the kustomization file.
func newCmdAddResource(out, errOut io.Writer, fsys fs.FileSystem) *cobra.Command {
var o addResourceOptions
cmd := &cobra.Command{
Use: "resource",
Short: "Add the name of a file containing a resource to the kustomization file.",
Example: `
add resource {filepath}`,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args)
if err != nil {
return err
}
err = o.Complete(cmd, args)
if err != nil {
return err
}
return o.RunAddResource(out, errOut, fsys)
},
}
return cmd
}
// Validate validates addResource command.
func (o *addResourceOptions) Validate(args []string) error {
if len(args) != 1 {
return errors.New("must specify a resource file")
}
o.resourceFilePath = args[0]
return nil
}
// Complete completes addResource command.
func (o *addResourceOptions) Complete(cmd *cobra.Command, args []string) error {
return nil
}
// RunAddResource runs addResource command (do real work).
func (o *addResourceOptions) RunAddResource(out, errOut io.Writer, fsys fs.FileSystem) error {
_, err := fsys.Stat(o.resourceFilePath)
if err != nil {
return err
}
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
return err
}
m, err := mf.read()
if err != nil {
return err
}
if stringInSlice(o.resourceFilePath, m.Resources) {
return fmt.Errorf("resource %s already in kustomization file", o.resourceFilePath)
}
m.Resources = append(m.Resources, o.resourceFilePath)
return mf.write(m)
}

View File

@@ -0,0 +1,110 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"bytes"
"os"
"testing"
"strings"
"k8s.io/kubectl/pkg/kustomize/constants"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
const (
resourceFileName = "myWonderfulResource.yaml"
resourceFileContent = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
`
kustomizationContent = `namePrefix: some-prefix
# Labels to add to all objects and selectors.
# These labels would also be used to form the selector for apply --prune
# Named differently than “labels” to avoid confusion with metadata for this object
commonLabels:
app: helloworld
commonAnnotations:
note: This is an example annotation
resources: []
#- service.yaml
#- ../some-dir/
# There could also be configmaps in Base, which would make these overlays
configMapGenerator: []
# There could be secrets in Base, if just using a fork/rebase workflow
secretGenerator: []
`
)
func TestAddResourceHappyPath(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(resourceFileName, []byte(resourceFileContent))
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdAddResource(buf, os.Stderr, fakeFS)
args := []string{resourceFileName}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadFile(constants.KustomizationFileName)
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
if !strings.Contains(string(content), resourceFileName) {
t.Errorf("expected resource name in kustomization")
}
}
func TestAddResourceAlreadyThere(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(resourceFileName, []byte(resourceFileContent))
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdAddResource(buf, os.Stderr, fakeFS)
args := []string{resourceFileName}
err := cmd.RunE(cmd, args)
if err != nil {
t.Fatalf("unexpected cmd error: %v", err)
}
// adding an existing resource should return an error
err = cmd.RunE(cmd, args)
if err == nil {
t.Errorf("expected already there problem")
}
if err.Error() != "resource "+resourceFileName+" already in kustomization file" {
t.Errorf("unexpected error %v", err)
}
}
func TestAddResourceNoArgs(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
cmd := newCmdAddResource(buf, os.Stderr, fakeFS)
err := cmd.Execute()
if err == nil {
t.Errorf("expected error: %v", err)
}
if err.Error() != "must specify a resource file" {
t.Errorf("incorrect error: %v", err.Error())
}
}

110
pkg/commands/build.go Normal file
View File

@@ -0,0 +1,110 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"fmt"
"io"
"os"
"path/filepath"
"github.com/spf13/cobra"
"errors"
"k8s.io/kubectl/pkg/kustomize/app"
"k8s.io/kubectl/pkg/kustomize/constants"
kutil "k8s.io/kubectl/pkg/kustomize/util"
"k8s.io/kubectl/pkg/kustomize/util/fs"
"k8s.io/kubectl/pkg/loader"
)
type buildOptions struct {
kustomizationPath string
}
// newCmdBuild creates a new build command.
func newCmdBuild(out, errOut io.Writer, fs fs.FileSystem) *cobra.Command {
var o buildOptions
cmd := &cobra.Command{
Use: "build [path]",
Short: "Print current configuration per contents of " + constants.KustomizationFileName,
Example: "Use the file somedir/" + constants.KustomizationFileName +
" to generate a set of api resources:\nbuild somedir/",
Run: func(cmd *cobra.Command, args []string) {
err := o.Validate(args)
if err != nil {
fmt.Fprintf(errOut, "error: %v\n", err)
os.Exit(1)
}
err = o.RunBuild(out, errOut, fs)
if err != nil {
fmt.Fprintf(errOut, "error: %v\n", err)
os.Exit(1)
}
},
}
return cmd
}
// Validate validates build command.
func (o *buildOptions) Validate(args []string) error {
if len(args) > 1 {
return errors.New("specify one path to " + constants.KustomizationFileName)
}
if len(args) == 0 {
o.kustomizationPath = "./"
return nil
}
o.kustomizationPath = args[0]
return nil
}
// RunBuild runs build command.
func (o *buildOptions) RunBuild(out, errOut io.Writer, fs fs.FileSystem) error {
l := loader.Init([]loader.SchemeLoader{loader.NewFileLoader(fs)})
absPath, err := filepath.Abs(o.kustomizationPath)
if err != nil {
return err
}
rootLoader, err := l.New(absPath)
if err != nil {
return err
}
application, err := app.New(rootLoader)
if err != nil {
return err
}
allResources, err := application.Resources()
if err != nil {
return err
}
// Output the objects.
res, err := kutil.Encode(allResources)
if err != nil {
return err
}
_, err = out.Write(res)
return err
}

152
pkg/commands/build_test.go Normal file
View File

@@ -0,0 +1,152 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/ghodss/yaml"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubectl/pkg/kustomize/constants"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
type buildTestCase struct {
Description string `yaml:"description"`
Args []string `yaml:"args"`
Filename string `yaml:"filename"`
// path to the file that contains the expected output
ExpectedStdout string `yaml:"expectedStdout"`
ExpectedError string `yaml:"expectedError"`
}
func TestBuildValidate(t *testing.T) {
var cases = []struct {
name string
args []string
path string
erMsg string
}{
{"noargs", []string{}, "./", ""},
{"file", []string{"beans"}, "beans", ""},
{"path", []string{"a/b/c"}, "a/b/c", ""},
{"path", []string{"too", "many"},
"", "specify one path to " + constants.KustomizationFileName},
}
for _, mycase := range cases {
opts := buildOptions{}
e := opts.Validate(mycase.args)
if len(mycase.erMsg) > 0 {
if e == nil {
t.Errorf("%s: Expected an error %v", mycase.name, mycase.erMsg)
}
if e.Error() != mycase.erMsg {
t.Errorf("%s: Expected error %s, but got %v", mycase.name, mycase.erMsg, e)
}
continue
}
if e != nil {
t.Errorf("%s: unknown error %v", mycase.name, e)
continue
}
if opts.kustomizationPath != mycase.path {
t.Errorf("%s: expected path '%s', got '%s'", mycase.name, mycase.path, opts.kustomizationPath)
}
}
}
func TestBuild(t *testing.T) {
const updateEnvVar = "UPDATE_KUSTOMIZE_EXPECTED_DATA"
updateKustomizeExpected := os.Getenv(updateEnvVar) == "true"
fs := fs.MakeRealFS()
testcases := sets.NewString()
filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == "testdata" {
return nil
}
name := filepath.Base(path)
if info.IsDir() {
if strings.HasPrefix(name, "testcase-") {
testcases.Insert(strings.TrimPrefix(name, "testcase-"))
}
return filepath.SkipDir
}
return nil
})
// sanity check that we found the right folder
if !testcases.Has("simple") {
t.Fatalf("Error locating testcases")
}
for _, testcaseName := range testcases.List() {
t.Run(testcaseName, func(t *testing.T) {
name := testcaseName
testcase := buildTestCase{}
testcaseDir := filepath.Join("testdata", "testcase-"+name)
testcaseData, err := ioutil.ReadFile(filepath.Join(testcaseDir, "test.yaml"))
if err != nil {
t.Fatalf("%s: %v", name, err)
}
if err := yaml.Unmarshal(testcaseData, &testcase); err != nil {
t.Fatalf("%s: %v", name, err)
}
ops := &buildOptions{
kustomizationPath: testcase.Filename,
}
buf := bytes.NewBuffer([]byte{})
err = ops.RunBuild(buf, os.Stderr, fs)
switch {
case err != nil && len(testcase.ExpectedError) == 0:
t.Errorf("unexpected error: %v", err)
case err != nil && len(testcase.ExpectedError) != 0:
if !strings.Contains(err.Error(), testcase.ExpectedError) {
t.Errorf("expected error to contain %q but got: %v", testcase.ExpectedError, err)
}
return
case err == nil && len(testcase.ExpectedError) != 0:
t.Errorf("unexpected no error")
}
actualBytes := buf.Bytes()
if !updateKustomizeExpected {
expectedBytes, err := ioutil.ReadFile(testcase.ExpectedStdout)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(actualBytes, expectedBytes) {
t.Errorf("%s\ndoesn't equal expected:\n%s\n", actualBytes, expectedBytes)
}
} else {
ioutil.WriteFile(testcase.ExpectedStdout, actualBytes, 0644)
}
})
}
}

126
pkg/commands/commands.go Normal file
View File

@@ -0,0 +1,126 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"flag"
"io"
"os"
"github.com/spf13/cobra"
"k8s.io/kubectl/cmd/kustomize/version"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
// NewDefaultCommand returns the default (aka root) command for kustomize command.
func NewDefaultCommand() *cobra.Command {
fsys := fs.MakeRealFS()
stdOut, stdErr := os.Stdout, os.Stderr
c := &cobra.Command{
Use: "kustomize",
Short: "kustomize manages declarative configuration of Kubernetes",
Long: `
kustomize manages declarative configuration of Kubernetes.
More info at https://github.com/kubernetes/kubectl/tree/master/cmd/kustomize
`,
}
c.AddCommand(
newCmdBuild(stdOut, stdErr, fsys),
newCmdDiff(stdOut, stdErr, fsys),
newCmdEdit(stdOut, stdErr, fsys),
version.NewCmdVersion(stdOut),
)
c.PersistentFlags().AddGoFlagSet(flag.CommandLine)
// Workaround for this issue:
// https://github.com/kubernetes/kubernetes/issues/17162
flag.CommandLine.Parse([]string{})
return c
}
// newCmdEdit returns an instance of 'edit' subcommand.
func newCmdEdit(stdOut, stdErr io.Writer, fsys fs.FileSystem) *cobra.Command {
c := &cobra.Command{
Use: "edit",
Short: "Edits a kustomization file",
Long: "",
Example: `
# Adds a configmap to the kustomization file
kustomize edit add configmap NAME --from-literal=k=v
# Sets the nameprefix field
kustomize edit set nameprefix <prefix-value>
`,
Args: cobra.MinimumNArgs(1),
}
c.AddCommand(
newCmdAdd(stdOut, stdErr, fsys),
newCmdSet(stdOut, stdErr, fsys),
)
return c
}
// newAddCommand returns an instance of 'add' subcommand.
func newCmdAdd(stdOut, stdErr io.Writer, fsys fs.FileSystem) *cobra.Command {
c := &cobra.Command{
Use: "add",
Short: "Adds configmap/resource/secret to the kustomization file.",
Long: "",
Example: `
# Adds a configmap to the kustomization file
kustomize edit add configmap NAME --from-literal=k=v
# Adds a secret to the kustomization file
kustomize edit add secret NAME --from-literal=k=v
# Adds a resource to the kustomization
kustomize edit add resource <filepath>
# Adds a patch to the kustomization
kustomize edit add patch <filepath>
`,
Args: cobra.MinimumNArgs(1),
}
c.AddCommand(
newCmdAddResource(stdOut, stdErr, fsys),
newCmdAddPatch(stdOut, stdErr, fsys),
newCmdAddConfigMap(stdErr, fsys),
)
return c
}
// newSetCommand returns an instance of 'set' subcommand.
func newCmdSet(stdOut, stdErr io.Writer, fsys fs.FileSystem) *cobra.Command {
c := &cobra.Command{
Use: "set",
Short: "Sets the value of different fields in kustomization file.",
Long: "",
Example: `
# Sets the nameprefix field
kustomize edit set nameprefix <prefix-value>
`,
Args: cobra.MinimumNArgs(1),
}
c.AddCommand(
newCmdSetNamePrefix(stdOut, stdErr, fsys),
)
return c
}

135
pkg/commands/configmap.go Normal file
View File

@@ -0,0 +1,135 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/kustomize/configmapandsecret"
"k8s.io/kubectl/pkg/kustomize/constants"
"k8s.io/kubectl/pkg/kustomize/types"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
func newCmdAddConfigMap(errOut io.Writer, fsys fs.FileSystem) *cobra.Command {
var config dataConfig
cmd := &cobra.Command{
Use: "configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1]",
Short: "Adds a configmap to the kustomization file.",
Long: "",
Example: `
# Adds a configmap to the kustomization file (with a specified key)
kustomize edit add configmap my-configmap --from-file=my-key=file/path --from-literal=my-literal=12345
# Adds a configmap to the kustomization file (key is the filename)
kustomize edit add configmap my-configmap --from-file=file/path
# Adds a configmap from env-file
kustomize edit add configmap my-configmap --from-env-file=env/path.env
`,
RunE: func(_ *cobra.Command, args []string) error {
err := config.Validate(args)
if err != nil {
return err
}
// Load in the kustomization file.
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
return err
}
m, err := mf.read()
if err != nil {
return err
}
// Add the config map to the kustomization file.
err = addConfigMap(m, config)
if err != nil {
return err
}
// Write out the kustomization file with added configmap.
return mf.write(m)
},
}
cmd.Flags().StringSliceVar(
&config.FileSources,
"from-file",
[]string{},
"Key file can be specified using its file path, in which case file basename will be used as configmap key, or optionally with a key and file path, in which case the given key will be used. Specifying a directory will iterate each named file in the directory whose basename is a valid configmap key.")
cmd.Flags().StringArrayVar(
&config.LiteralSources,
"from-literal",
[]string{},
"Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
cmd.Flags().StringVar(
&config.EnvFileSource,
"from-env-file",
"",
"Specify the path to a file to read lines of key=val pairs to create a configmap (i.e. a Docker .env file).")
return cmd
}
// addConfigMap updates a configmap within a kustomization file, using the data in config.
// Note: error may leave kustomization file in an undefined state. Suggest passing a copy
// of kustomization file.
func addConfigMap(m *types.Kustomization, config dataConfig) error {
cm := getOrCreateConfigMap(m, config.Name)
err := mergeData(&cm.DataSources, config)
if err != nil {
return err
}
// Validate by trying to create corev1.configmap.
_, _, err = configmapandsecret.MakeConfigmapAndGenerateName(*cm)
if err != nil {
return err
}
return nil
}
func getOrCreateConfigMap(m *types.Kustomization, name string) *types.ConfigMapArgs {
for i, v := range m.ConfigMapGenerator {
if name == v.Name {
return &m.ConfigMapGenerator[i]
}
}
// config map not found, create new one and add it to the kustomization file.
cm := &types.ConfigMapArgs{Name: name}
m.ConfigMapGenerator = append(m.ConfigMapGenerator, *cm)
return &m.ConfigMapGenerator[len(m.ConfigMapGenerator)-1]
}
func mergeData(src *types.DataSources, config dataConfig) error {
src.LiteralSources = append(src.LiteralSources, config.LiteralSources...)
src.FileSources = append(src.FileSources, config.FileSources...)
if src.EnvSource != "" && src.EnvSource != config.EnvFileSource {
return fmt.Errorf("updating existing env source '%s' not allowed.", src.EnvSource)
}
src.EnvSource = config.EnvFileSource
return nil
}

View File

@@ -0,0 +1,129 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"testing"
"k8s.io/kubectl/pkg/kustomize/types"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
func TestNewAddConfigMapIsNotNil(t *testing.T) {
if newCmdAddConfigMap(nil, fs.MakeFakeFS()) == nil {
t.Fatal("newCmdAddConfigMap shouldn't be nil")
}
}
func TestGetOrCreateConfigMap(t *testing.T) {
cmName := "test-config-name"
kustomization := &types.Kustomization{
NamePrefix: "test-name-prefix",
}
if len(kustomization.ConfigMapGenerator) != 0 {
t.Fatal("Initial kustomization should not have any configmaps")
}
cm := getOrCreateConfigMap(kustomization, cmName)
if cm == nil {
t.Fatalf("ConfigMap should always be non-nil")
}
if len(kustomization.ConfigMapGenerator) != 1 {
t.Fatalf("Kustomization should have newly created configmap")
}
if &kustomization.ConfigMapGenerator[len(kustomization.ConfigMapGenerator)-1] != cm {
t.Fatalf("Pointer address for newly inserted configmap should be same")
}
existingCM := getOrCreateConfigMap(kustomization, cmName)
if existingCM != cm {
t.Fatalf("should have returned an existing cm with name: %v", cmName)
}
if len(kustomization.ConfigMapGenerator) != 1 {
t.Fatalf("Should not insert configmap for an existing name: %v", cmName)
}
}
func TestMergeData_LiteralSources(t *testing.T) {
ds := &types.DataSources{}
err := mergeData(ds, dataConfig{LiteralSources: []string{"k1=v1"}})
if err != nil {
t.Fatalf("Merge initial literal source should not return error")
}
if len(ds.LiteralSources) != 1 {
t.Fatalf("Initial literal source should have been added")
}
err = mergeData(ds, dataConfig{LiteralSources: []string{"k2=v2"}})
if err != nil {
t.Fatalf("Merge second literal source should not return error")
}
if len(ds.LiteralSources) != 2 {
t.Fatalf("Second literal source should have been added")
}
}
func TestMergeData_FileSources(t *testing.T) {
ds := &types.DataSources{}
err := mergeData(ds, dataConfig{FileSources: []string{"file1"}})
if err != nil {
t.Fatalf("Merge initial file source should not return error")
}
if len(ds.FileSources) != 1 {
t.Fatalf("Initial file source should have been added")
}
err = mergeData(ds, dataConfig{FileSources: []string{"file2"}})
if err != nil {
t.Fatalf("Merge second file source should not return error")
}
if len(ds.FileSources) != 2 {
t.Fatalf("Second file source should have been added")
}
}
func TestMergeData_EnvSource(t *testing.T) {
envFileName := "env1"
envFileName2 := "env2"
ds := &types.DataSources{}
err := mergeData(ds, dataConfig{EnvFileSource: envFileName})
if err != nil {
t.Fatalf("Merge initial env source should not return error")
}
if ds.EnvSource != envFileName {
t.Fatalf("Initial env source filename should have been added")
}
err = mergeData(ds, dataConfig{EnvFileSource: envFileName2})
if err == nil {
t.Fatalf("Updating env source should return an error")
}
}

View File

@@ -0,0 +1,50 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"fmt"
)
// dataConfig encapsulates the options for add configmap/Secret commands.
type dataConfig struct {
// Name of configMap/Secret (required)
Name string
// FileSources to derive the configMap/Secret from (optional)
FileSources []string
// LiteralSources to derive the configMap/Secret from (optional)
LiteralSources []string
// EnvFileSource to derive the configMap/Secret from (optional)
// TODO: Rationalize this name with Generic.EnvSource
EnvFileSource string
}
// Validate validates required fields are set to support structured generation.
func (a *dataConfig) Validate(args []string) error {
if len(args) != 1 {
return fmt.Errorf("name must be specified once")
}
a.Name = args[0]
if len(a.EnvFileSource) == 0 && len(a.FileSources) == 0 && len(a.LiteralSources) == 0 {
return fmt.Errorf("at least from-env-file, or from-file or from-literal must be set")
}
if len(a.EnvFileSource) > 0 && (len(a.FileSources) > 0 || len(a.LiteralSources) > 0) {
return fmt.Errorf("from-env-file cannot be combined with from-file or from-literal")
}
// TODO: Should we check if the path exists? if it's valid, if it's within the same (sub-)directory?
return nil
}

View File

@@ -0,0 +1,83 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"testing"
)
func TestDataConfigValidation_NoName(t *testing.T) {
config := dataConfig{}
if config.Validate([]string{}) == nil {
t.Fatal("Validation should fail if no name is specified")
}
}
func TestDataConfigValidation_MoreThanOneName(t *testing.T) {
config := dataConfig{}
if config.Validate([]string{"name", "othername"}) == nil {
t.Fatal("Validation should fail if more than one name is specified")
}
}
func TestDataConfigValidation_Flags(t *testing.T) {
tests := []struct {
name string
config dataConfig
shouldFail bool
}{
{
name: "env-file-source and literal are both set",
config: dataConfig{
LiteralSources: []string{"one", "two"},
EnvFileSource: "three",
},
shouldFail: true,
},
{
name: "env-file-source and from-file are both set",
config: dataConfig{
FileSources: []string{"one", "two"},
EnvFileSource: "three",
},
shouldFail: true,
},
{
name: "we don't have any option set",
config: dataConfig{},
shouldFail: true,
},
{
name: "we have from-file and literal ",
config: dataConfig{
LiteralSources: []string{"one", "two"},
FileSources: []string{"three", "four"},
},
shouldFail: false,
},
}
for _, test := range tests {
if test.config.Validate([]string{"name"}) == nil && test.shouldFail {
t.Fatalf("Validation should fail if %s", test.name)
} else if test.config.Validate([]string{"name"}) != nil && !test.shouldFail {
t.Fatalf("Validation should succeed if %s", test.name)
}
}
}

130
pkg/commands/diff.go Normal file
View File

@@ -0,0 +1,130 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"errors"
"io"
"path/filepath"
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/kustomize/app"
"k8s.io/kubectl/pkg/kustomize/constants"
"k8s.io/kubectl/pkg/kustomize/util"
"k8s.io/kubectl/pkg/kustomize/util/fs"
"k8s.io/kubectl/pkg/loader"
"k8s.io/utils/exec"
)
type diffOptions struct {
kustomizationPath string
}
// newCmdDiff makes the diff command.
func newCmdDiff(out, errOut io.Writer, fs fs.FileSystem) *cobra.Command {
var o diffOptions
cmd := &cobra.Command{
Use: "diff",
Short: "diff between transformed resources and untransformed resources",
Long: "diff between transformed resources and untransformed resources and the subpackages are all transformed.",
Example: `diff -f .`,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(cmd, args)
if err != nil {
return err
}
err = o.Complete(cmd, args)
if err != nil {
return err
}
return o.RunDiff(out, errOut, fs)
},
}
cmd.Flags().StringVarP(
&o.kustomizationPath,
"filename",
"f",
"",
"Specify a directory containing "+constants.KustomizationFileName)
cmd.MarkFlagRequired("filename")
return cmd
}
// Validate validates diff command.
func (o *diffOptions) Validate(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
return errors.New("The diff command takes no arguments.")
}
return nil
}
// Complete completes diff command.
func (o *diffOptions) Complete(cmd *cobra.Command, args []string) error {
return nil
}
// RunDiff gets the differences between Application.Resources() and Application.RawResources().
func (o *diffOptions) RunDiff(out, errOut io.Writer, fs fs.FileSystem) error {
printer := util.Printer{}
diff := util.DiffProgram{
Exec: exec.New(),
Stdout: out,
Stderr: errOut,
}
l := loader.Init([]loader.SchemeLoader{loader.NewFileLoader(fs)})
absPath, err := filepath.Abs(o.kustomizationPath)
if err != nil {
return err
}
rootLoader, err := l.New(absPath)
if err != nil {
return err
}
application, err := app.New(rootLoader)
if err != nil {
return err
}
resources, err := application.Resources()
if err != nil {
return err
}
rawResources, err := application.RawResources()
if err != nil {
return err
}
transformedDir, err := util.WriteToDir(resources, "transformed", printer)
if err != nil {
return err
}
defer transformedDir.Delete()
noopDir, err := util.WriteToDir(rawResources, "noop", printer)
if err != nil {
return err
}
defer noopDir.Delete()
return diff.Run(noopDir.Name, transformedDir.Name)
}

124
pkg/commands/diff_test.go Normal file
View File

@@ -0,0 +1,124 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"
"testing"
"github.com/ghodss/yaml"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
type DiffTestCase struct {
Description string `yaml:"description"`
Args []string `yaml:"args"`
Filename string `yaml:"filename"`
// path to the file that contains the expected output
ExpectedDiff string `yaml:"expectedDiff"`
ExpectedError string `yaml:"expectedError"`
}
func TestDiff(t *testing.T) {
const updateEnvVar = "UPDATE_KUSTOMIZE_EXPECTED_DATA"
updateKustomizeExpected := os.Getenv(updateEnvVar) == "true"
noopDir, _ := regexp.Compile(`/tmp/noop-[0-9]*/`)
transformedDir, _ := regexp.Compile(`/tmp/transformed-[0-9]*/`)
timestamp, _ := regexp.Compile(`[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) (2[0-3]|[01][0-9]):[0-5][0-9]:[0-5][0-9].[0-9]* [+-]{1}[0-9]{4}`)
fs := fs.MakeRealFS()
testcases := sets.NewString()
filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == "testdata" {
return nil
}
name := filepath.Base(path)
if info.IsDir() {
if strings.HasPrefix(name, "testcase-") {
testcases.Insert(strings.TrimPrefix(name, "testcase-"))
}
return filepath.SkipDir
}
return nil
})
// sanity check that we found the right folder
if !testcases.Has("simple") {
t.Fatalf("Error locating testcases")
}
for _, testcaseName := range testcases.List() {
t.Run(testcaseName, func(t *testing.T) {
name := testcaseName
testcase := DiffTestCase{}
testcaseDir := filepath.Join("testdata", "testcase-"+name)
testcaseData, err := ioutil.ReadFile(filepath.Join(testcaseDir, "test.yaml"))
if err != nil {
t.Fatalf("%s: %v", name, err)
}
if err := yaml.Unmarshal(testcaseData, &testcase); err != nil {
t.Fatalf("%s: %v", name, err)
}
diffOps := &diffOptions{
kustomizationPath: testcase.Filename,
}
buf := bytes.NewBuffer([]byte{})
err = diffOps.RunDiff(buf, os.Stderr, fs)
switch {
case err != nil && len(testcase.ExpectedError) == 0:
t.Errorf("unexpected error: %v", err)
case err != nil && len(testcase.ExpectedError) != 0:
if !strings.Contains(err.Error(), testcase.ExpectedError) {
t.Errorf("expected error to contain %q but got: %v", testcase.ExpectedError, err)
}
return
case err == nil && len(testcase.ExpectedError) != 0:
t.Errorf("unexpected no error")
}
actualString := string(buf.Bytes())
actualString = noopDir.ReplaceAllString(actualString, "/tmp/noop/")
actualString = transformedDir.ReplaceAllString(actualString, "/tmp/transformed/")
actualString = timestamp.ReplaceAllString(actualString, "YYYY-MM-DD HH:MM:SS")
actualBytes := []byte(actualString)
if !updateKustomizeExpected {
expectedBytes, err := ioutil.ReadFile(testcase.ExpectedDiff)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(actualBytes, expectedBytes) {
t.Errorf("%s\ndoesn't equal expected:\n%s\n", actualBytes, expectedBytes)
}
} else {
ioutil.WriteFile(testcase.ExpectedDiff, actualBytes, 0644)
}
})
}
}

96
pkg/commands/init.go Normal file
View File

@@ -0,0 +1,96 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"fmt"
"io"
"errors"
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/kustomize/constants"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
const kustomizationTemplate = `
namePrefix: some-prefix
# Labels to add to all objects and selectors.
# These labels would also be used to form the selector for apply --prune
# Named differently than “labels” to avoid confusion with metadata for this object
commonLabels:
app: helloworld
commonAnnotations:
note: This is an example annotation
resources: []
#- service.yaml
#- ../some-dir/
# There could also be configmaps in Base, which would make these overlays
configMapGenerator: []
# There could be secrets in Base, if just using a fork/rebase workflow
secretGenerator: []
`
type initOptions struct {
}
// NewCmdInit makes the init command.
func newCmdInit(out, errOut io.Writer, fs fs.FileSystem) *cobra.Command {
var o initOptions
cmd := &cobra.Command{
Use: "init",
Short: "Creates a file called \"" + constants.KustomizationFileName + "\" in the current directory",
Long: "Creates a file called \"" +
constants.KustomizationFileName + "\" in the current directory with example values.",
Example: `init`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(cmd, args)
if err != nil {
return err
}
err = o.Complete(cmd, args)
if err != nil {
return err
}
return o.RunInit(out, errOut, fs)
},
}
return cmd
}
// Validate validates init command.
func (o *initOptions) Validate(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
return errors.New("The init command takes no arguments.")
}
return nil
}
// Complete completes init command.
func (o *initOptions) Complete(cmd *cobra.Command, args []string) error {
return nil
}
// RunInit writes a kustomization file.
func (o *initOptions) RunInit(out, errOut io.Writer, fs fs.FileSystem) error {
if _, err := fs.Stat(constants.KustomizationFileName); err == nil {
return fmt.Errorf("%q already exists", constants.KustomizationFileName)
}
return fs.WriteFile(constants.KustomizationFileName, []byte(kustomizationTemplate))
}

View File

@@ -0,0 +1,88 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"errors"
"io"
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/kustomize/constants"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
type setNamePrefixOptions struct {
prefix string
}
// newCmdSetNamePrefix sets the value of the namePrefix field in the kustomization.
func newCmdSetNamePrefix(out, errOut io.Writer, fsys fs.FileSystem) *cobra.Command {
var o setNamePrefixOptions
cmd := &cobra.Command{
Use: "nameprefix",
Short: "Sets the value of the namePrefix field in the kustomization file.",
Example: `
The command
set nameprefix acme-
will add the field "namePrefix: acme-" to the kustomization file if it doesn't exist,
and overwrite the value with "acme-" if the field does exist.
`,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args)
if err != nil {
return err
}
err = o.Complete(cmd, args)
if err != nil {
return err
}
return o.RunSetNamePrefix(out, errOut, fsys)
},
}
return cmd
}
// Validate validates setNamePrefix command.
func (o *setNamePrefixOptions) Validate(args []string) error {
if len(args) != 1 {
return errors.New("must specify exactly one prefix value")
}
// TODO: add further validation on the value.
o.prefix = args[0]
return nil
}
// Complete completes setNamePrefix command.
func (o *setNamePrefixOptions) Complete(cmd *cobra.Command, args []string) error {
return nil
}
// RunSetNamePrefix runs setNamePrefix command (does real work).
func (o *setNamePrefixOptions) RunSetNamePrefix(out, errOut io.Writer, fsys fs.FileSystem) error {
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
return err
}
m, err := mf.read()
if err != nil {
return err
}
m.NamePrefix = o.prefix
return mf.write(m)
}

View File

@@ -0,0 +1,66 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"bytes"
"os"
"testing"
"strings"
"k8s.io/kubectl/pkg/kustomize/constants"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
const (
goodPrefixValue = "acme-"
)
func TestSetNamePrefixHappyPath(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdSetNamePrefix(buf, os.Stderr, fakeFS)
args := []string{goodPrefixValue}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadFile(constants.KustomizationFileName)
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
if !strings.Contains(string(content), goodPrefixValue) {
t.Errorf("expected prefix value in kustomization file")
}
}
func TestSetNamePrefixNoArgs(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
cmd := newCmdSetNamePrefix(buf, os.Stderr, fakeFS)
err := cmd.Execute()
if err == nil {
t.Errorf("expected error: %v", err)
}
if err.Error() != "must specify exactly one prefix value" {
t.Errorf("incorrect error: %v", err.Error())
}
}

View File

@@ -0,0 +1,58 @@
diff -u -N /tmp/noop/apps_v1beta2_Deployment_nginx.yaml /tmp/transformed/apps_v1beta2_Deployment_nginx.yaml
--- /tmp/noop/apps_v1beta2_Deployment_nginx.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/apps_v1beta2_Deployment_nginx.yaml YYYY-MM-DD HH:MM:SS
@@ -1,14 +1,27 @@
apiVersion: apps/v1beta2
kind: Deployment
metadata:
+ annotations:
+ note: This is a test annotation
labels:
- app: nginx
- name: nginx
+ app: mynginx
+ org: example.com
+ team: foo
+ name: team-foo-nginx
spec:
+ selector:
+ matchLabels:
+ app: mynginx
+ org: example.com
+ team: foo
template:
metadata:
+ annotations:
+ note: This is a test annotation
labels:
- app: nginx
+ app: mynginx
+ org: example.com
+ team: foo
spec:
containers:
- image: nginx
diff -u -N /tmp/noop/v1_Service_nginx.yaml /tmp/transformed/v1_Service_nginx.yaml
--- /tmp/noop/v1_Service_nginx.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_Service_nginx.yaml YYYY-MM-DD HH:MM:SS
@@ -1,11 +1,17 @@
apiVersion: v1
kind: Service
metadata:
+ annotations:
+ note: This is a test annotation
labels:
- app: nginx
- name: nginx
+ app: mynginx
+ org: example.com
+ team: foo
+ name: team-foo-nginx
spec:
ports:
- port: 80
selector:
- app: nginx
+ app: mynginx
+ org: example.com
+ team: foo

View File

@@ -0,0 +1,46 @@
apiVersion: v1
kind: Service
metadata:
annotations:
note: This is a test annotation
labels:
app: mynginx
org: example.com
team: foo
name: team-foo-nginx
spec:
ports:
- port: 80
selector:
app: mynginx
org: example.com
team: foo
---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
annotations:
note: This is a test annotation
labels:
app: mynginx
org: example.com
team: foo
name: team-foo-nginx
spec:
selector:
matchLabels:
app: mynginx
org: example.com
team: foo
template:
metadata:
annotations:
note: This is a test annotation
labels:
app: mynginx
org: example.com
team: foo
spec:
containers:
- image: nginx
name: nginx

View File

@@ -0,0 +1,15 @@
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx

View File

@@ -0,0 +1,10 @@
namePrefix: team-foo-
commonLabels:
app: mynginx
org: example.com
team: foo
commonAnnotations:
note: This is a test annotation
resources:
- deployment.yaml
- service.yaml

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
selector:
app: nginx

View File

@@ -0,0 +1,5 @@
description: base only
args: []
filename: testdata/testcase-base-only/in
expectedStdout: testdata/testcase-base-only/expected.yaml
expectedDiff: testdata/testcase-base-only/expected.diff

View File

@@ -0,0 +1,20 @@
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: nginx
spec:
template:
spec:
containers:
- name: nginx
env:
- name: ENABLE_FEATURE_FOO
value: TRUE
volumes:
- name: nginx-persistent-storage
emptyDir: null
gcePersistentDisk:
pdName: nginx-persistent-storage
- configMap:
name: configmap-in-overlay
name: configmap-in-overlay

View File

@@ -0,0 +1,12 @@
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: nginx
spec:
template:
spec:
containers:
- name: nginx
env:
- name: ENABLE_FEATURE_FOO
value: FALSE

View File

@@ -0,0 +1,12 @@
namePrefix: staging-
commonLabels:
env: staging
patches:
- deployment-patch2.yaml
- deployment-patch1.yaml
bases:
- ../package/
configMapGenerator:
- name: configmap-in-overlay
literals:
- hello=world

View File

@@ -0,0 +1,24 @@
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: nginx-persistent-storage
mountPath: /tmp/ps
volumes:
- name: nginx-persistent-storage
emptyDir: {}
- configMap:
name: configmap-in-base
name: configmap-in-base

View File

@@ -0,0 +1,14 @@
namePrefix: team-foo-
commonLabels:
app: mynginx
org: example.com
team: foo
commonAnnotations:
note: This is a test annotation
resources:
- deployment.yaml
- service.yaml
configMapGenerator:
- name: configmap-in-base
literals:
- foo=bar

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
selector:
app: nginx

View File

@@ -0,0 +1,4 @@
description: conflict between multiple patches
args: []
filename: testdata/testcase-multiple-patches-conflict/in/overlay/
expectedError: conflict

View File

@@ -0,0 +1,99 @@
diff -u -N /tmp/noop/apps_v1beta2_Deployment_nginx.yaml /tmp/transformed/apps_v1beta2_Deployment_nginx.yaml
--- /tmp/noop/apps_v1beta2_Deployment_nginx.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/apps_v1beta2_Deployment_nginx.yaml YYYY-MM-DD HH:MM:SS
@@ -5,13 +5,15 @@
note: This is a test annotation
labels:
app: mynginx
+ env: staging
org: example.com
team: foo
- name: team-foo-nginx
+ name: staging-team-foo-nginx
spec:
selector:
matchLabels:
app: mynginx
+ env: staging
org: example.com
team: foo
template:
@@ -20,18 +22,30 @@
note: This is a test annotation
labels:
app: mynginx
+ env: staging
org: example.com
team: foo
spec:
containers:
- - image: nginx
+ - env:
+ - name: ANOTHERENV
+ value: FOO
+ - name: ENVKEY
+ value: ENVVALUE
+ image: nginx:latest
name: nginx
volumeMounts:
- mountPath: /tmp/ps
name: nginx-persistent-storage
+ - image: sidecar
+ name: sidecar
volumes:
- - emptyDir: {}
+ - gcePersistentDisk:
+ pdName: nginx-persistent-storage
name: nginx-persistent-storage
- configMap:
- name: team-foo-configmap-in-base-bbdmdh7m8t
+ name: staging-configmap-in-overlay-k7cbc75tg8
+ name: configmap-in-overlay
+ - configMap:
+ name: staging-team-foo-configmap-in-base-g7k6gt2889
name: configmap-in-base
diff -u -N /tmp/noop/v1_ConfigMap_configmap-in-base.yaml /tmp/transformed/v1_ConfigMap_configmap-in-base.yaml
--- /tmp/noop/v1_ConfigMap_configmap-in-base.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_ConfigMap_configmap-in-base.yaml YYYY-MM-DD HH:MM:SS
@@ -8,6 +8,7 @@
creationTimestamp: null
labels:
app: mynginx
+ env: staging
org: example.com
team: foo
- name: team-foo-configmap-in-base-bbdmdh7m8t
+ name: staging-team-foo-configmap-in-base-g7k6gt2889
diff -u -N /tmp/noop/v1_ConfigMap_configmap-in-overlay.yaml /tmp/transformed/v1_ConfigMap_configmap-in-overlay.yaml
--- /tmp/noop/v1_ConfigMap_configmap-in-overlay.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_ConfigMap_configmap-in-overlay.yaml YYYY-MM-DD HH:MM:SS
@@ -0,0 +1,9 @@
+apiVersion: v1
+data:
+ hello: world
+kind: ConfigMap
+metadata:
+ creationTimestamp: null
+ labels:
+ env: staging
+ name: staging-configmap-in-overlay-k7cbc75tg8
diff -u -N /tmp/noop/v1_Service_nginx.yaml /tmp/transformed/v1_Service_nginx.yaml
--- /tmp/noop/v1_Service_nginx.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_Service_nginx.yaml YYYY-MM-DD HH:MM:SS
@@ -5,13 +5,15 @@
note: This is a test annotation
labels:
app: mynginx
+ env: staging
org: example.com
team: foo
- name: team-foo-nginx
+ name: staging-team-foo-nginx
spec:
ports:
- port: 80
selector:
app: mynginx
+ env: staging
org: example.com
team: foo

View File

@@ -0,0 +1,96 @@
apiVersion: v1
data:
foo: bar
kind: ConfigMap
metadata:
annotations:
note: This is a test annotation
creationTimestamp: null
labels:
app: mynginx
env: staging
org: example.com
team: foo
name: staging-team-foo-configmap-in-base-g7k6gt2889
---
apiVersion: v1
data:
hello: world
kind: ConfigMap
metadata:
creationTimestamp: null
labels:
env: staging
name: staging-configmap-in-overlay-k7cbc75tg8
---
apiVersion: v1
kind: Service
metadata:
annotations:
note: This is a test annotation
labels:
app: mynginx
env: staging
org: example.com
team: foo
name: staging-team-foo-nginx
spec:
ports:
- port: 80
selector:
app: mynginx
env: staging
org: example.com
team: foo
---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
annotations:
note: This is a test annotation
labels:
app: mynginx
env: staging
org: example.com
team: foo
name: staging-team-foo-nginx
spec:
selector:
matchLabels:
app: mynginx
env: staging
org: example.com
team: foo
template:
metadata:
annotations:
note: This is a test annotation
labels:
app: mynginx
env: staging
org: example.com
team: foo
spec:
containers:
- env:
- name: ANOTHERENV
value: FOO
- name: ENVKEY
value: ENVVALUE
image: nginx:latest
name: nginx
volumeMounts:
- mountPath: /tmp/ps
name: nginx-persistent-storage
- image: sidecar
name: sidecar
volumes:
- gcePersistentDisk:
pdName: nginx-persistent-storage
name: nginx-persistent-storage
- configMap:
name: staging-configmap-in-overlay-k7cbc75tg8
name: configmap-in-overlay
- configMap:
name: staging-team-foo-configmap-in-base-g7k6gt2889
name: configmap-in-base

View File

@@ -0,0 +1,21 @@
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: nginx
spec:
template:
spec:
containers:
- name: nginx
image: nginx:latest
env:
- name: ENVKEY
value: ENVVALUE
volumes:
- name: nginx-persistent-storage
emptyDir: null
gcePersistentDisk:
pdName: nginx-persistent-storage
- configMap:
name: configmap-in-overlay
name: configmap-in-overlay

View File

@@ -0,0 +1,16 @@
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: nginx
spec:
template:
spec:
containers:
- name: nginx
env:
- name: ANOTHERENV
value: FOO
- name: sidecar
image: sidecar
volumes:
- name: nginx-persistent-storage

View File

@@ -0,0 +1,12 @@
namePrefix: staging-
commonLabels:
env: staging
patches:
- deployment-patch1.yaml
- deployment-patch2.yaml
bases:
- ../package/
configMapGenerator:
- name: configmap-in-overlay
literals:
- hello=world

View File

@@ -0,0 +1,24 @@
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: nginx-persistent-storage
mountPath: /tmp/ps
volumes:
- name: nginx-persistent-storage
emptyDir: {}
- configMap:
name: configmap-in-base
name: configmap-in-base

View File

@@ -0,0 +1,14 @@
namePrefix: team-foo-
commonLabels:
app: mynginx
org: example.com
team: foo
commonAnnotations:
note: This is a test annotation
resources:
- deployment.yaml
- service.yaml
configMapGenerator:
- name: configmap-in-base
literals:
- foo=bar

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
selector:
app: nginx

View File

@@ -0,0 +1,5 @@
description: multiple patches no conflict
args: []
filename: testdata/testcase-multiple-patches-noconflict/in/overlay/
expectedStdout: testdata/testcase-multiple-patches-noconflict/expected.yaml
expectedDiff: testdata/testcase-multiple-patches-noconflict/expected.diff

View File

@@ -0,0 +1,154 @@
diff -u -N /tmp/noop/extensions_v1beta1_Deployment_mungebot.yaml /tmp/transformed/extensions_v1beta1_Deployment_mungebot.yaml
--- /tmp/noop/extensions_v1beta1_Deployment_mungebot.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/extensions_v1beta1_Deployment_mungebot.yaml YYYY-MM-DD HH:MM:SS
@@ -3,28 +3,68 @@
metadata:
annotations:
baseAnno: This is an base annotation
+ note: This is a test annotation
labels:
app: mungebot
foo: bar
- name: baseprefix-mungebot
+ org: kubernetes
+ repo: test-infra
+ name: test-infra-baseprefix-mungebot
spec:
- replicas: 1
+ replicas: 2
selector:
matchLabels:
+ app: mungebot
foo: bar
+ org: kubernetes
+ repo: test-infra
template:
metadata:
annotations:
baseAnno: This is an base annotation
+ note: This is a test annotation
labels:
app: mungebot
foo: bar
+ org: kubernetes
+ repo: test-infra
spec:
containers:
- env:
+ - name: FOO
+ valueFrom:
+ configMapKeyRef:
+ key: somekey
+ name: test-infra-app-env-bh449c299k
+ - name: BAR
+ valueFrom:
+ secretKeyRef:
+ key: somekey
+ name: test-infra-app-tls-6hkmhf2224
- name: foo
value: bar
- image: nginx
+ image: nginx:1.7.9
name: nginx
ports:
- containerPort: 80
+ - envFrom:
+ - configMapRef:
+ name: someConfigMap
+ - configMapRef:
+ name: test-infra-app-env-bh449c299k
+ - secretRef:
+ name: test-infra-app-tls-6hkmhf2224
+ image: busybox
+ name: busybox
+ volumeMounts:
+ - mountPath: /tmp/env
+ name: app-env
+ - mountPath: /tmp/tls
+ name: app-tls
+ volumes:
+ - configMap:
+ name: test-infra-app-env-bh449c299k
+ name: app-env
+ - name: app-tls
+ secret:
+ secretName: test-infra-app-tls-6hkmhf2224
diff -u -N /tmp/noop/v1_ConfigMap_app-config.yaml /tmp/transformed/v1_ConfigMap_app-config.yaml
--- /tmp/noop/v1_ConfigMap_app-config.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_ConfigMap_app-config.yaml YYYY-MM-DD HH:MM:SS
@@ -0,0 +1,15 @@
+apiVersion: v1
+data:
+ app-init.ini: |
+ FOO=bar
+ BAR=baz
+kind: ConfigMap
+metadata:
+ annotations:
+ note: This is a test annotation
+ creationTimestamp: null
+ labels:
+ app: mungebot
+ org: kubernetes
+ repo: test-infra
+ name: test-infra-app-config-hf5424hg8g
diff -u -N /tmp/noop/v1_ConfigMap_app-env.yaml /tmp/transformed/v1_ConfigMap_app-env.yaml
--- /tmp/noop/v1_ConfigMap_app-env.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_ConfigMap_app-env.yaml YYYY-MM-DD HH:MM:SS
@@ -0,0 +1,14 @@
+apiVersion: v1
+data:
+ DB_PASSWORD: somepw
+ DB_USERNAME: admin
+kind: ConfigMap
+metadata:
+ annotations:
+ note: This is a test annotation
+ creationTimestamp: null
+ labels:
+ app: mungebot
+ org: kubernetes
+ repo: test-infra
+ name: test-infra-app-env-bh449c299k
diff -u -N /tmp/noop/v1_Secret_app-tls.yaml /tmp/transformed/v1_Secret_app-tls.yaml
--- /tmp/noop/v1_Secret_app-tls.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_Secret_app-tls.yaml YYYY-MM-DD HH:MM:SS
@@ -0,0 +1,15 @@
+apiVersion: v1
+data:
+ tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUIwekNDQVgyZ0F3SUJBZ0lKQUkvTTdCWWp3Qit1TUEwR0NTcUdTSWIzRFFFQkJRVUFNRVV4Q3pBSkJnTlYKQkFZVEFrRlZNUk13RVFZRFZRUUlEQXBUYjIxbExWTjBZWFJsTVNFd0h3WURWUVFLREJoSmJuUmxjbTVsZENCWAphV1JuYVhSeklGQjBlU0JNZEdRd0hoY05NVEl3T1RFeU1qRTFNakF5V2hjTk1UVXdPVEV5TWpFMU1qQXlXakJGCk1Rc3dDUVlEVlFRR0V3SkJWVEVUTUJFR0ExVUVDQXdLVTI5dFpTMVRkR0YwWlRFaE1COEdBMVVFQ2d3WVNXNTAKWlhKdVpYUWdWMmxrWjJsMGN5QlFkSGtnVEhSa01Gd3dEUVlKS29aSWh2Y05BUUVCQlFBRFN3QXdTQUpCQU5MSgpoUEhoSVRxUWJQa2xHM2liQ1Z4d0dNUmZwL3Y0WHFoZmRRSGRjVmZIYXA2TlE1V29rLzR4SUErdWkzNS9NbU5hCnJ0TnVDK0JkWjF0TXVWQ1BGWmNDQXdFQUFhTlFNRTR3SFFZRFZSME9CQllFRkp2S3M4UmZKYVhUSDA4VytTR3YKelF5S24wSDhNQjhHQTFVZEl3UVlNQmFBRkp2S3M4UmZKYVhUSDA4VytTR3Z6UXlLbjBIOE1Bd0dBMVVkRXdRRgpNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUZCUUFEUVFCSmxmZkpIeWJqREd4Uk1xYVJtRGhYMCs2djAyVFVLWnNXCnI1UXVWYnBRaEg2dSswVWdjVzBqcDlRd3B4b1BUTFRXR1hFV0JCQnVyeEZ3aUNCaGtRK1YKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
+ tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT3dJQkFBSkJBTkxKaFBIaElUcVFiUGtsRzNpYkNWeHdHTVJmcC92NFhxaGZkUUhkY1ZmSGFwNk5RNVdvCmsvNHhJQSt1aTM1L01tTmFydE51QytCZFoxdE11VkNQRlpjQ0F3RUFBUUpBRUoyTit6c1IwWG44L1E2dHdhNEcKNk9CMU0xV08rayt6dG5YLzFTdk5lV3U4RDZHSW10dXBMVFlnalpjSHVmeWtqMDlqaUhtakh4OHU4WlpCL28xTgpNUUloQVBXK2V5Wm83YXkzbE16MVYwMVdWak5LSzlRU24xTUpsYjA2aC9MdVl2OUZBaUVBMjVXUGVkS2dWeUNXClNtVXdiUHc4Zm5UY3BxRFdFM3lUTzN2S2NlYnFNU3NDSUJGM1VtVnVlOFlVM2p5YkMzTnh1WHEzd05tMzRSOFQKeFZMSHdEWGgvNk5KQWlFQWwyb0hHR0x6NjRCdUFmaktycXd6N3FNWXI5SENMSWUvWXNvV3Evb2x6U2NDSVFEaQpEMmxXdXNvZTIvbkVxZkRWVldHV2x5Sjd5T21xYVZtL2lOVU45QjJOMmc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
+kind: Secret
+metadata:
+ annotations:
+ note: This is a test annotation
+ creationTimestamp: null
+ labels:
+ app: mungebot
+ org: kubernetes
+ repo: test-infra
+ name: test-infra-app-tls-6hkmhf2224
+type: kubernetes.io/tls
diff -u -N /tmp/noop/v1_Service_mungebot-service.yaml /tmp/transformed/v1_Service_mungebot-service.yaml
--- /tmp/noop/v1_Service_mungebot-service.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_Service_mungebot-service.yaml YYYY-MM-DD HH:MM:SS
@@ -3,13 +3,18 @@
metadata:
annotations:
baseAnno: This is an base annotation
+ note: This is a test annotation
labels:
app: mungebot
foo: bar
- name: baseprefix-mungebot-service
+ org: kubernetes
+ repo: test-infra
+ name: test-infra-baseprefix-mungebot-service
spec:
ports:
- port: 7002
selector:
app: mungebot
foo: bar
+ org: kubernetes
+ repo: test-infra

View File

@@ -0,0 +1,138 @@
apiVersion: v1
data:
app-init.ini: |
FOO=bar
BAR=baz
kind: ConfigMap
metadata:
annotations:
note: This is a test annotation
creationTimestamp: null
labels:
app: mungebot
org: kubernetes
repo: test-infra
name: test-infra-app-config-hf5424hg8g
---
apiVersion: v1
data:
DB_PASSWORD: somepw
DB_USERNAME: admin
kind: ConfigMap
metadata:
annotations:
note: This is a test annotation
creationTimestamp: null
labels:
app: mungebot
org: kubernetes
repo: test-infra
name: test-infra-app-env-bh449c299k
---
apiVersion: v1
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUIwekNDQVgyZ0F3SUJBZ0lKQUkvTTdCWWp3Qit1TUEwR0NTcUdTSWIzRFFFQkJRVUFNRVV4Q3pBSkJnTlYKQkFZVEFrRlZNUk13RVFZRFZRUUlEQXBUYjIxbExWTjBZWFJsTVNFd0h3WURWUVFLREJoSmJuUmxjbTVsZENCWAphV1JuYVhSeklGQjBlU0JNZEdRd0hoY05NVEl3T1RFeU1qRTFNakF5V2hjTk1UVXdPVEV5TWpFMU1qQXlXakJGCk1Rc3dDUVlEVlFRR0V3SkJWVEVUTUJFR0ExVUVDQXdLVTI5dFpTMVRkR0YwWlRFaE1COEdBMVVFQ2d3WVNXNTAKWlhKdVpYUWdWMmxrWjJsMGN5QlFkSGtnVEhSa01Gd3dEUVlKS29aSWh2Y05BUUVCQlFBRFN3QXdTQUpCQU5MSgpoUEhoSVRxUWJQa2xHM2liQ1Z4d0dNUmZwL3Y0WHFoZmRRSGRjVmZIYXA2TlE1V29rLzR4SUErdWkzNS9NbU5hCnJ0TnVDK0JkWjF0TXVWQ1BGWmNDQXdFQUFhTlFNRTR3SFFZRFZSME9CQllFRkp2S3M4UmZKYVhUSDA4VytTR3YKelF5S24wSDhNQjhHQTFVZEl3UVlNQmFBRkp2S3M4UmZKYVhUSDA4VytTR3Z6UXlLbjBIOE1Bd0dBMVVkRXdRRgpNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUZCUUFEUVFCSmxmZkpIeWJqREd4Uk1xYVJtRGhYMCs2djAyVFVLWnNXCnI1UXVWYnBRaEg2dSswVWdjVzBqcDlRd3B4b1BUTFRXR1hFV0JCQnVyeEZ3aUNCaGtRK1YKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT3dJQkFBSkJBTkxKaFBIaElUcVFiUGtsRzNpYkNWeHdHTVJmcC92NFhxaGZkUUhkY1ZmSGFwNk5RNVdvCmsvNHhJQSt1aTM1L01tTmFydE51QytCZFoxdE11VkNQRlpjQ0F3RUFBUUpBRUoyTit6c1IwWG44L1E2dHdhNEcKNk9CMU0xV08rayt6dG5YLzFTdk5lV3U4RDZHSW10dXBMVFlnalpjSHVmeWtqMDlqaUhtakh4OHU4WlpCL28xTgpNUUloQVBXK2V5Wm83YXkzbE16MVYwMVdWak5LSzlRU24xTUpsYjA2aC9MdVl2OUZBaUVBMjVXUGVkS2dWeUNXClNtVXdiUHc4Zm5UY3BxRFdFM3lUTzN2S2NlYnFNU3NDSUJGM1VtVnVlOFlVM2p5YkMzTnh1WHEzd05tMzRSOFQKeFZMSHdEWGgvNk5KQWlFQWwyb0hHR0x6NjRCdUFmaktycXd6N3FNWXI5SENMSWUvWXNvV3Evb2x6U2NDSVFEaQpEMmxXdXNvZTIvbkVxZkRWVldHV2x5Sjd5T21xYVZtL2lOVU45QjJOMmc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
kind: Secret
metadata:
annotations:
note: This is a test annotation
creationTimestamp: null
labels:
app: mungebot
org: kubernetes
repo: test-infra
name: test-infra-app-tls-6hkmhf2224
type: kubernetes.io/tls
---
apiVersion: v1
kind: Service
metadata:
annotations:
baseAnno: This is an base annotation
note: This is a test annotation
labels:
app: mungebot
foo: bar
org: kubernetes
repo: test-infra
name: test-infra-baseprefix-mungebot-service
spec:
ports:
- port: 7002
selector:
app: mungebot
foo: bar
org: kubernetes
repo: test-infra
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
annotations:
baseAnno: This is an base annotation
note: This is a test annotation
labels:
app: mungebot
foo: bar
org: kubernetes
repo: test-infra
name: test-infra-baseprefix-mungebot
spec:
replicas: 2
selector:
matchLabels:
app: mungebot
foo: bar
org: kubernetes
repo: test-infra
template:
metadata:
annotations:
baseAnno: This is an base annotation
note: This is a test annotation
labels:
app: mungebot
foo: bar
org: kubernetes
repo: test-infra
spec:
containers:
- env:
- name: FOO
valueFrom:
configMapKeyRef:
key: somekey
name: test-infra-app-env-bh449c299k
- name: BAR
valueFrom:
secretKeyRef:
key: somekey
name: test-infra-app-tls-6hkmhf2224
- name: foo
value: bar
image: nginx:1.7.9
name: nginx
ports:
- containerPort: 80
- envFrom:
- configMapRef:
name: someConfigMap
- configMapRef:
name: test-infra-app-env-bh449c299k
- secretRef:
name: test-infra-app-tls-6hkmhf2224
image: busybox
name: busybox
volumeMounts:
- mountPath: /tmp/env
name: app-env
- mountPath: /tmp/tls
name: app-tls
volumes:
- configMap:
name: test-infra-app-env-bh449c299k
name: app-env
- name: app-tls
secret:
secretName: test-infra-app-tls-6hkmhf2224

View File

@@ -0,0 +1,5 @@
description: simple
args: []
filename: ../examples/simple/instances/exampleinstance/
expectedStdout: testdata/testcase-simple/expected.yaml
expectedDiff: testdata/testcase-simple/expected.diff

View File

@@ -0,0 +1,128 @@
diff -u -N /tmp/noop/apps_v1beta2_Deployment_nginx.yaml /tmp/transformed/apps_v1beta2_Deployment_nginx.yaml
--- /tmp/noop/apps_v1beta2_Deployment_nginx.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/apps_v1beta2_Deployment_nginx.yaml YYYY-MM-DD HH:MM:SS
@@ -5,23 +5,26 @@
note: This is a test annotation
labels:
app: mynginx
+ env: staging
org: example.com
- team: foo
- name: team-foo-nginx
+ team: override-foo
+ name: staging-team-foo-nginx
spec:
selector:
matchLabels:
app: mynginx
+ env: staging
org: example.com
- team: foo
+ team: override-foo
template:
metadata:
annotations:
note: This is a test annotation
labels:
app: mynginx
+ env: staging
org: example.com
- team: foo
+ team: override-foo
spec:
containers:
- image: nginx
@@ -30,8 +33,12 @@
- mountPath: /tmp/ps
name: nginx-persistent-storage
volumes:
- - emptyDir: {}
+ - gcePersistentDisk:
+ pdName: nginx-persistent-storage
name: nginx-persistent-storage
- configMap:
- name: team-foo-configmap-in-base-bbdmdh7m8t
+ name: staging-configmap-in-overlay-k7cbc75tg8
+ name: configmap-in-overlay
+ - configMap:
+ name: staging-team-foo-configmap-in-base-gh9d7t85gb
name: configmap-in-base
diff -u -N /tmp/noop/v1_ConfigMap_configmap-in-base.yaml /tmp/transformed/v1_ConfigMap_configmap-in-base.yaml
--- /tmp/noop/v1_ConfigMap_configmap-in-base.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_ConfigMap_configmap-in-base.yaml YYYY-MM-DD HH:MM:SS
@@ -1,6 +1,6 @@
apiVersion: v1
data:
- foo: bar
+ foo: override-bar
kind: ConfigMap
metadata:
annotations:
@@ -8,6 +8,7 @@
creationTimestamp: null
labels:
app: mynginx
+ env: staging
org: example.com
- team: foo
- name: team-foo-configmap-in-base-bbdmdh7m8t
+ team: override-foo
+ name: staging-team-foo-configmap-in-base-gh9d7t85gb
diff -u -N /tmp/noop/v1_ConfigMap_configmap-in-overlay.yaml /tmp/transformed/v1_ConfigMap_configmap-in-overlay.yaml
--- /tmp/noop/v1_ConfigMap_configmap-in-overlay.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_ConfigMap_configmap-in-overlay.yaml YYYY-MM-DD HH:MM:SS
@@ -0,0 +1,10 @@
+apiVersion: v1
+data:
+ hello: world
+kind: ConfigMap
+metadata:
+ creationTimestamp: null
+ labels:
+ env: staging
+ team: override-foo
+ name: staging-configmap-in-overlay-k7cbc75tg8
diff -u -N /tmp/noop/v1_Secret_secret-in-base.yaml /tmp/transformed/v1_Secret_secret-in-base.yaml
--- /tmp/noop/v1_Secret_secret-in-base.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_Secret_secret-in-base.yaml YYYY-MM-DD HH:MM:SS
@@ -1,6 +1,7 @@
apiVersion: v1
data:
password: c29tZXB3
+ proxy: aGFwcm94eQ==
username: YWRtaW4=
kind: Secret
metadata:
@@ -9,7 +10,8 @@
creationTimestamp: null
labels:
app: mynginx
+ env: staging
org: example.com
- team: foo
- name: team-foo-secret-in-base-tkm7hhtf8d
+ team: override-foo
+ name: staging-team-foo-secret-in-base-c8db7gk2m2
type: Opaque
diff -u -N /tmp/noop/v1_Service_nginx.yaml /tmp/transformed/v1_Service_nginx.yaml
--- /tmp/noop/v1_Service_nginx.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_Service_nginx.yaml YYYY-MM-DD HH:MM:SS
@@ -5,13 +5,15 @@
note: This is a test annotation
labels:
app: mynginx
+ env: staging
org: example.com
- team: foo
- name: team-foo-nginx
+ team: override-foo
+ name: staging-team-foo-nginx
spec:
ports:
- port: 80
selector:
app: mynginx
+ env: staging
org: example.com
- team: foo
+ team: override-foo

View File

@@ -0,0 +1,108 @@
apiVersion: v1
data:
foo: override-bar
kind: ConfigMap
metadata:
annotations:
note: This is a test annotation
creationTimestamp: null
labels:
app: mynginx
env: staging
org: example.com
team: override-foo
name: staging-team-foo-configmap-in-base-gh9d7t85gb
---
apiVersion: v1
data:
hello: world
kind: ConfigMap
metadata:
creationTimestamp: null
labels:
env: staging
team: override-foo
name: staging-configmap-in-overlay-k7cbc75tg8
---
apiVersion: v1
data:
password: c29tZXB3
proxy: aGFwcm94eQ==
username: YWRtaW4=
kind: Secret
metadata:
annotations:
note: This is a test annotation
creationTimestamp: null
labels:
app: mynginx
env: staging
org: example.com
team: override-foo
name: staging-team-foo-secret-in-base-c8db7gk2m2
type: Opaque
---
apiVersion: v1
kind: Service
metadata:
annotations:
note: This is a test annotation
labels:
app: mynginx
env: staging
org: example.com
team: override-foo
name: staging-team-foo-nginx
spec:
ports:
- port: 80
selector:
app: mynginx
env: staging
org: example.com
team: override-foo
---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
annotations:
note: This is a test annotation
labels:
app: mynginx
env: staging
org: example.com
team: override-foo
name: staging-team-foo-nginx
spec:
selector:
matchLabels:
app: mynginx
env: staging
org: example.com
team: override-foo
template:
metadata:
annotations:
note: This is a test annotation
labels:
app: mynginx
env: staging
org: example.com
team: override-foo
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- mountPath: /tmp/ps
name: nginx-persistent-storage
volumes:
- gcePersistentDisk:
pdName: nginx-persistent-storage
name: nginx-persistent-storage
- configMap:
name: staging-configmap-in-overlay-k7cbc75tg8
name: configmap-in-overlay
- configMap:
name: staging-team-foo-configmap-in-base-gh9d7t85gb
name: configmap-in-base

View File

@@ -0,0 +1,15 @@
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: nginx
spec:
template:
spec:
volumes:
- name: nginx-persistent-storage
emptyDir: null
gcePersistentDisk:
pdName: nginx-persistent-storage
- configMap:
name: configmap-in-overlay
name: configmap-in-overlay

View File

@@ -0,0 +1,21 @@
namePrefix: staging-
commonLabels:
env: staging
team: override-foo
patches:
- deployment.yaml
bases:
- ../package/
configMapGenerator:
- name: configmap-in-overlay
literals:
- hello=world
- name: configmap-in-base
behavior: replace
literals:
- foo=override-bar
secretGenerator:
- name: secret-in-base
behavior: merge
commands:
proxy: "printf haproxy"

View File

@@ -0,0 +1,24 @@
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: nginx-persistent-storage
mountPath: /tmp/ps
volumes:
- name: nginx-persistent-storage
emptyDir: {}
- configMap:
name: configmap-in-base
name: configmap-in-base

View File

@@ -0,0 +1,19 @@
namePrefix: team-foo-
commonLabels:
app: mynginx
org: example.com
team: foo
commonAnnotations:
note: This is a test annotation
resources:
- deployment.yaml
- service.yaml
configMapGenerator:
- name: configmap-in-base
literals:
- foo=bar
secretGenerator:
- name: secret-in-base
commands:
username: "printf admin"
password: "printf somepw"

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
selector:
app: nginx

View File

@@ -0,0 +1,5 @@
description: single overlay
args: []
filename: testdata/testcase-single-overlay/in/overlay/
expectedStdout: testdata/testcase-single-overlay/expected.yaml
expectedDiff: testdata/testcase-single-overlay/expected.diff

104
pkg/commands/util.go Normal file
View File

@@ -0,0 +1,104 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"errors"
"fmt"
"path"
"strings"
"github.com/ghodss/yaml"
"k8s.io/kubectl/pkg/kustomize/constants"
interror "k8s.io/kubectl/pkg/kustomize/internal/error"
"k8s.io/kubectl/pkg/kustomize/types"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
type kustomizationFile struct {
path string
fsys fs.FileSystem
}
func newKustomizationFile(mPath string, fsys fs.FileSystem) (*kustomizationFile, error) {
mf := &kustomizationFile{path: mPath, fsys: fsys}
err := mf.validate()
if err != nil {
return nil, err
}
return mf, nil
}
func (mf *kustomizationFile) validate() error {
f, err := mf.fsys.Stat(mf.path)
if err != nil {
errorMsg := fmt.Sprintf("Missing kustomization file '%s'.\n", mf.path)
merr := interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
return merr
}
if f.IsDir() {
mf.path = path.Join(mf.path, constants.KustomizationFileName)
_, err = mf.fsys.Stat(mf.path)
if err != nil {
errorMsg := fmt.Sprintf("Missing kustomization file '%s'.\n", mf.path)
merr := interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
return merr
}
} else {
if !strings.HasSuffix(mf.path, constants.KustomizationFileName) {
errorMsg := fmt.Sprintf("Kustomization file path (%s) should have %s suffix\n", mf.path, constants.KustomizationSuffix)
merr := interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
return merr
}
}
return nil
}
func (mf *kustomizationFile) read() (*types.Kustomization, error) {
bytes, err := mf.fsys.ReadFile(mf.path)
if err != nil {
return nil, err
}
var kustomization types.Kustomization
err = yaml.Unmarshal(bytes, &kustomization)
if err != nil {
return nil, err
}
return &kustomization, err
}
func (mf *kustomizationFile) write(kustomization *types.Kustomization) error {
if kustomization == nil {
return errors.New("util: kustomization file arg is nil.")
}
bytes, err := yaml.Marshal(kustomization)
if err != nil {
return err
}
return mf.fsys.WriteFile(mf.path, bytes)
}
func stringInSlice(str string, list []string) bool {
for _, v := range list {
if v == str {
return true
}
}
return false
}

90
pkg/commands/util_test.go Normal file
View File

@@ -0,0 +1,90 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"reflect"
"strings"
"testing"
"k8s.io/kubectl/pkg/kustomize/constants"
"k8s.io/kubectl/pkg/kustomize/types"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
func TestWriteAndRead(t *testing.T) {
kustomization := &types.Kustomization{
NamePrefix: "prefix",
}
fsys := fs.MakeFakeFS()
fsys.Create(constants.KustomizationFileName)
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
if err := mf.write(kustomization); err != nil {
t.Fatalf("Couldn't write kustomization file: %v\n", err)
}
content, err := mf.read()
if err != nil {
t.Fatalf("Couldn't read kustomization file: %v\n", err)
}
if !reflect.DeepEqual(kustomization, content) {
t.Fatal("Read kustomization is different from written kustomization")
}
}
func TestEmptyFile(t *testing.T) {
fsys := fs.MakeFakeFS()
_, err := newKustomizationFile("", fsys)
if err == nil {
t.Fatalf("Create kustomizationFile from empty filename should fail")
}
}
func TestNewNotExist(t *testing.T) {
badSuffix := "foo.bar"
fakeFS := fs.MakeFakeFS()
fakeFS.Mkdir(".", 0644)
fakeFS.Create(badSuffix)
_, err := newKustomizationFile(constants.KustomizationFileName, fakeFS)
if err == nil {
t.Fatalf("expect an error")
}
contained := "Missing kustomization file"
if !strings.Contains(err.Error(), contained) {
t.Fatalf("expect an error contains %q, but got %v", contained, err)
}
_, err = newKustomizationFile(constants.KustomizationFileName, fakeFS)
if err == nil {
t.Fatalf("expect an error")
}
if !strings.Contains(err.Error(), contained) {
t.Fatalf("expect an error contains %q, but got %v", contained, err)
}
_, err = newKustomizationFile(badSuffix, fakeFS)
if err == nil {
t.Fatalf("expect an error")
}
contained = "should have .yaml suffix"
if !strings.Contains(err.Error(), contained) {
t.Fatalf("expect an error contains %q, but got %v", contained, err)
}
}