change kinflate to kustomize

This commit is contained in:
Jingfang Liu
2018-04-10 14:32:02 -07:00
committed by Sunil Arora
commit 696ec9b171
125 changed files with 10447 additions and 0 deletions

106
commands/addresource.go Normal file
View File

@@ -0,0 +1,106 @@
/*
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 manifest.
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 manifest.",
Long: "Add the name of a file containing a resource to the manifest.",
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
}
func stringInSlice(str string, list []string) bool {
for _, v := range list {
if v == str {
return true
}
}
return false
}
// 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 := newManifestFile(constants.KustomizeFileName, 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 manifest", o.resourceFilePath)
}
m.Resources = append(m.Resources, o.resourceFilePath)
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 (
resourceFileName = "myWonderfulResource.yaml"
resourceFileContent = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
`
)
func TestAddResourceHappyPath(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(resourceFileName, []byte(resourceFileContent))
fakeFS.WriteFile(constants.KustomizeFileName, []byte(manifestTemplate))
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.KustomizeFileName)
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
if !strings.Contains(string(content), resourceFileName) {
t.Errorf("expected resource name in manifest")
}
}
func TestAddResourceAlreadyThere(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(resourceFileName, []byte(resourceFileContent))
fakeFS.WriteFile(constants.KustomizeFileName, []byte(manifestTemplate))
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 manifest" {
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())
}
}

111
commands/build.go Normal file
View File

@@ -0,0 +1,111 @@
/*
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 {
manifestPath 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.KustomizeFileName,
Example: `
# Use the kustomize.yaml file under somedir/ to generate a set of api resources.
build 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 manifest")
}
if len(args) == 0 {
o.manifestPath = "./"
return nil
}
o.manifestPath = 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.manifestPath)
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
}

150
commands/build_test.go Normal file
View File

@@ -0,0 +1,150 @@
/*
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/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 manifest"},
}
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.manifestPath != mycase.path {
t.Errorf("%s: expected path '%s', got '%s'", mycase.name, mycase.path, opts.manifestPath)
}
}
}
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{
manifestPath: 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)
}
})
}
}

123
commands/commands.go Normal file
View File

@@ -0,0 +1,123 @@
/*
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),
newCmdInit(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 manifest file",
Long: "",
Example: `
# Adds a configmap to the manifest
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 manifest.",
Long: "",
Example: `
# Adds a configmap to the manifest
kustomize edit add configmap NAME --from-literal=k=v
# Adds a secret to the manifest
kustomize edit add secret NAME --from-literal=k=v
# Adds a resource to the manifest
kustomize edit add resource <filepath>
`,
Args: cobra.MinimumNArgs(1),
}
c.AddCommand(
newCmdAddResource(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 manifest.",
Long: "",
Example: `
# Sets the nameprefix field
kustomize edit set nameprefix <prefix-value>
`,
Args: cobra.MinimumNArgs(1),
}
c.AddCommand(
newCmdSetNamePrefix(stdOut, stdErr, fsys),
)
return c
}

123
commands/configmap.go Normal file
View File

@@ -0,0 +1,123 @@
/*
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"
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
"k8s.io/kubectl/pkg/kustomize/configmapandsecret"
"k8s.io/kubectl/pkg/kustomize/constants"
"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 manifest.",
Long: "",
Example: `
# Adds a configmap to the Manifest (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 Manifest (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 manifest file.
mf, err := newManifestFile(constants.KustomizeFileName, fsys)
if err != nil {
return err
}
m, err := mf.read()
if err != nil {
return err
}
// Add the config map to the manifest.
err = addConfigMap(m, config)
if err != nil {
return err
}
// Write out the manifest 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 manifest, using the data in config.
// Note: error may leave manifest in an undefined state. Suggest passing a copy
// of manifest.
func addConfigMap(m *manifest.Manifest, config dataConfig) error {
cm := getOrCreateConfigMap(m, config.Name)
err := mergeData(&cm.DataSources, config)
if err != nil {
return err
}
// Validate manifest's configmap by trying to create corev1.configmap.
_, _, err = configmapandsecret.MakeConfigmapAndGenerateName(*cm)
if err != nil {
return err
}
return nil
}
func getOrCreateConfigMap(m *manifest.Manifest, name string) *manifest.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 manifest.
cm := &manifest.ConfigMapArgs{Name: name}
m.ConfigMapGenerator = append(m.ConfigMapGenerator, *cm)
return &m.ConfigMapGenerator[len(m.ConfigMapGenerator)-1]
}
func mergeData(src *manifest.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
}

129
commands/configmap_test.go Normal file
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"
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
"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"
manifest := &manifest.Manifest{
NamePrefix: "test-name-prefix",
}
if len(manifest.ConfigMapGenerator) != 0 {
t.Fatal("Initial manifest should not have any configmaps")
}
cm := getOrCreateConfigMap(manifest, cmName)
if cm == nil {
t.Fatalf("ConfigMap should always be non-nil")
}
if len(manifest.ConfigMapGenerator) != 1 {
t.Fatalf("Manifest should have newly created configmap")
}
if &manifest.ConfigMapGenerator[len(manifest.ConfigMapGenerator)-1] != cm {
t.Fatalf("Pointer address for newly inserted configmap should be same")
}
existingCM := getOrCreateConfigMap(manifest, cmName)
if existingCM != cm {
t.Fatalf("should have returned an existing cm with name: %v", cmName)
}
if len(manifest.ConfigMapGenerator) != 1 {
t.Fatalf("Should not insert configmap for an existing name: %v", cmName)
}
}
func TestMergeData_LiteralSources(t *testing.T) {
ds := &manifest.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 := &manifest.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 := &manifest.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")
}
}

50
commands/data_config.go Normal file
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)
}
}
}

124
commands/diff.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 (
"errors"
"io"
"path/filepath"
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/kustomize/app"
"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 {
manifestPath 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.manifestPath, "filename", "f", "", "Pass in a kustomize.yaml file or a directory that contains the file.")
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.manifestPath)
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
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{
manifestPath: 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)
}
})
}
}

100
commands/init.go Normal file
View File

@@ -0,0 +1,100 @@
/*
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 manifestTemplate = `apiVersion: manifest.k8s.io/v1alpha1
kind: Manifest
metadata:
name: helloworld
description: helloworld does useful stuff.
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
objectLabels:
app: helloworld
objectAnnotations:
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.KustomizeFileName + "\" in the current directory",
Long: "Creates a file called \"" +
constants.KustomizeFileName + "\" 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 manifest file.
func (o *initOptions) RunInit(out, errOut io.Writer, fs fs.FileSystem) error {
if _, err := fs.Stat(constants.KustomizeFileName); err == nil {
return fmt.Errorf("%q already exists", constants.KustomizeFileName)
}
return fs.WriteFile(constants.KustomizeFileName, []byte(manifestTemplate))
}

62
commands/init_test.go Normal file
View File

@@ -0,0 +1,62 @@
/*
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"
"k8s.io/kubectl/pkg/kustomize/constants"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
func TestInitHappyPath(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
cmd := newCmdInit(buf, os.Stderr, fakeFS)
err := cmd.Execute()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
f, err := fakeFS.Open(constants.KustomizeFileName)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
file := f.(*fs.FakeFile)
if !file.ContentMatches([]byte(manifestTemplate)) {
t.Fatalf("actual: %v doesn't match expected: %v",
string(file.GetContent()), manifestTemplate)
}
}
func TestInitFileAlreadyExist(t *testing.T) {
content := "hey there"
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(constants.KustomizeFileName, []byte(content))
buf := bytes.NewBuffer([]byte{})
cmd := newCmdInit(buf, os.Stderr, fakeFS)
err := cmd.Execute()
if err == nil {
t.Fatalf("expected error")
}
if err.Error() != `"`+constants.KustomizeFileName+`" already exists` {
t.Fatalf("unexpected error: %v", err)
}
}

View File

@@ -0,0 +1,90 @@
/*
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 manifest.
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 manifest.",
Long: "Sets the value of the namePrefix field in the manifest.",
//
Example: `
The command
set nameprefix acme-
will add the field "namePrefix: acme-" to the manifest 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 := newManifestFile(constants.KustomizeFileName, 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.KustomizeFileName, []byte(manifestTemplate))
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.KustomizeFileName)
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
if !strings.Contains(string(content), goodPrefixValue) {
t.Errorf("expected prefix value in manifest")
}
}
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,14 @@
apiVersion: manifest.k8s.io/v1alpha1
kind: Manifest
metadata:
name: nginx-app
namePrefix: team-foo-
objectLabels:
app: mynginx
org: example.com
team: foo
objectAnnotations:
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,16 @@
apiVersion: manifest.k8s.io/v1alpha1
kind: Manifest
metadata:
name: nginx-app
namePrefix: staging-
objectLabels:
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,18 @@
apiVersion: manifest.k8s.io/v1alpha1
kind: Manifest
metadata:
name: nginx-app
namePrefix: team-foo-
objectLabels:
app: mynginx
org: example.com
team: foo
objectAnnotations:
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,16 @@
apiVersion: manifest.k8s.io/v1alpha1
kind: Manifest
metadata:
name: nginx-app
namePrefix: staging-
objectLabels:
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,18 @@
apiVersion: manifest.k8s.io/v1alpha1
kind: Manifest
metadata:
name: nginx-app
namePrefix: team-foo-
objectLabels:
app: mynginx
org: example.com
team: foo
objectAnnotations:
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,25 @@
apiVersion: manifest.k8s.io/v1alpha1
kind: Manifest
metadata:
name: nginx-app
namePrefix: staging-
objectLabels:
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,23 @@
apiVersion: manifest.k8s.io/v1alpha1
kind: Manifest
metadata:
name: nginx-app
namePrefix: team-foo-
objectLabels:
app: mynginx
org: example.com
team: foo
objectAnnotations:
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

95
commands/util.go Normal file
View File

@@ -0,0 +1,95 @@
/*
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"
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
"k8s.io/kubectl/pkg/kustomize/constants"
interror "k8s.io/kubectl/pkg/kustomize/internal/error"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
type manifestFile struct {
mPath string
fsys fs.FileSystem
}
func newManifestFile(mPath string, fsys fs.FileSystem) (*manifestFile, error) {
mf := &manifestFile{mPath: mPath, fsys: fsys}
err := mf.validate()
if err != nil {
return nil, err
}
return mf, nil
}
func (mf *manifestFile) validate() error {
f, err := mf.fsys.Stat(mf.mPath)
if err != nil {
errorMsg := fmt.Sprintf("Manifest (%s) missing\nRun `kustomize init` first", mf.mPath)
merr := interror.ManifestError{ManifestFilepath: mf.mPath, ErrorMsg: errorMsg}
return merr
}
if f.IsDir() {
mf.mPath = path.Join(mf.mPath, constants.KustomizeFileName)
_, err = mf.fsys.Stat(mf.mPath)
if err != nil {
errorMsg := fmt.Sprintf("Manifest (%s) missing\nRun `kustomize init` first", mf.mPath)
merr := interror.ManifestError{ManifestFilepath: mf.mPath, ErrorMsg: errorMsg}
return merr
}
} else {
if !strings.HasSuffix(mf.mPath, constants.KustomizeFileName) {
errorMsg := fmt.Sprintf("Manifest file (%s) should have %s suffix\n", mf.mPath, constants.KustomizeSuffix)
merr := interror.ManifestError{ManifestFilepath: mf.mPath, ErrorMsg: errorMsg}
return merr
}
}
return nil
}
func (mf *manifestFile) read() (*manifest.Manifest, error) {
bytes, err := mf.fsys.ReadFile(mf.mPath)
if err != nil {
return nil, err
}
var manifest manifest.Manifest
err = yaml.Unmarshal(bytes, &manifest)
if err != nil {
return nil, err
}
return &manifest, err
}
func (mf *manifestFile) write(manifest *manifest.Manifest) error {
if manifest == nil {
return errors.New("util: failed to write passed-in nil manifest")
}
bytes, err := yaml.Marshal(manifest)
if err != nil {
return err
}
return mf.fsys.WriteFile(mf.mPath, bytes)
}

87
commands/util_test.go Normal file
View File

@@ -0,0 +1,87 @@
/*
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"
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
func TestWriteAndRead(t *testing.T) {
manifest := &manifest.Manifest{
NamePrefix: "prefix",
}
fsys := fs.MakeFakeFS()
fsys.Create("kustomize.yaml")
mf, err := newManifestFile("kustomize.yaml", fsys)
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
if err := mf.write(manifest); err != nil {
t.Fatalf("Couldn't write manifest file: %v\n", err)
}
readManifest, err := mf.read()
if err != nil {
t.Fatalf("Couldn't read manifest file: %v\n", err)
}
if !reflect.DeepEqual(manifest, readManifest) {
t.Fatal("Read manifest is different from written manifest")
}
}
func TestEmptyFile(t *testing.T) {
fsys := fs.MakeFakeFS()
_, err := newManifestFile("", fsys)
if err == nil {
t.Fatalf("Creat manifestFile from empty filename should fail")
}
}
func TestNewNotExist(t *testing.T) {
badSuffix := "foo.bar"
fakeFS := fs.MakeFakeFS()
fakeFS.Mkdir(".", 0644)
fakeFS.Create(badSuffix)
_, err := newManifestFile("kustomize.yaml", fakeFS)
if err == nil {
t.Fatalf("expect an error")
}
if !strings.Contains(err.Error(), "Run `kustomize init` first") {
t.Fatalf("expect an error contains %q, but got %v", "does not exist", err)
}
_, err = newManifestFile("kustomize.yaml", fakeFS)
if err == nil {
t.Fatalf("expect an error")
}
if !strings.Contains(err.Error(), "Run `kustomize init` first") {
t.Fatalf("expect an error contains %q, but got %v", "does not exist", err)
}
_, err = newManifestFile(badSuffix, fakeFS)
if err == nil {
t.Fatalf("expect an error")
}
if !strings.Contains(err.Error(), "should have .yaml suffix") {
t.Fatalf("expect an error contains %q, but got %v", "does not exist", err)
}
}