Move the kustomize binary to its own module.

This commit is contained in:
Jeffrey Regan
2019-09-25 11:01:03 -07:00
parent 2d0c22d6a4
commit b82a8fd316
55 changed files with 334 additions and 87 deletions

View File

@@ -1,238 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package build
import (
"io"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/ifc"
"sigs.k8s.io/kustomize/v3/pkg/loader"
"sigs.k8s.io/kustomize/v3/pkg/pgmconfig"
"sigs.k8s.io/kustomize/v3/pkg/plugins"
"sigs.k8s.io/kustomize/v3/pkg/resmap"
"sigs.k8s.io/kustomize/v3/pkg/resource"
"sigs.k8s.io/kustomize/v3/pkg/target"
"sigs.k8s.io/kustomize/v3/plugin/builtin"
"sigs.k8s.io/yaml"
)
// Options contain the options for running a build
type Options struct {
kustomizationPath string
outputPath string
loadRestrictor loader.LoadRestrictorFunc
outOrder reorderOutput
}
// NewOptions creates a Options object
func NewOptions(p, o string) *Options {
return &Options{
kustomizationPath: p,
outputPath: o,
loadRestrictor: loader.RestrictionRootOnly,
}
}
var examples = `
To generate the resources specified in 'someDir/kustomization.yaml', run
kustomize build someDir
The default argument to 'build' is '.' (the current working directory).
The argument can be a URL resolving to a directory
with a kustomization.yaml file, e.g.
kustomize build \
github.com/kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6
The URL should be formulated as described at
https://github.com/hashicorp/go-getter#url-format
`
// NewCmdBuild creates a new build command.
func NewCmdBuild(
out io.Writer, fSys fs.FileSystem,
v ifc.Validator, rf *resmap.Factory,
ptf resmap.PatchFactory) *cobra.Command {
var o Options
pluginConfig := plugins.DefaultPluginConfig()
pl := plugins.NewLoader(pluginConfig, rf)
cmd := &cobra.Command{
Use: "build {path}",
Short: "Print configuration per contents of " + pgmconfig.KustomizationFileNames[0],
Example: examples,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args)
if err != nil {
return err
}
return o.RunBuild(out, v, fSys, rf, ptf, pl)
},
}
cmd.Flags().StringVarP(
&o.outputPath,
"output", "o", "",
"If specified, write the build output to this path.")
loader.AddFlagLoadRestrictor(cmd.Flags())
plugins.AddFlagEnablePlugins(
cmd.Flags(), &pluginConfig.Enabled)
addFlagReorderOutput(cmd.Flags())
cmd.AddCommand(NewCmdBuildPrune(out, v, fSys, rf, ptf, pl))
return cmd
}
// Validate validates build command.
func (o *Options) Validate(args []string) (err error) {
if len(args) > 1 {
return errors.New(
"specify one path to " + pgmconfig.KustomizationFileNames[0])
}
if len(args) == 0 {
o.kustomizationPath = loader.CWD
} else {
o.kustomizationPath = args[0]
}
o.loadRestrictor, err = loader.ValidateFlagLoadRestrictor()
if err != nil {
return err
}
o.outOrder, err = validateFlagReorderOutput()
return
}
// RunBuild runs build command.
func (o *Options) RunBuild(
out io.Writer, v ifc.Validator, fSys fs.FileSystem,
rf *resmap.Factory, ptf resmap.PatchFactory,
pl *plugins.Loader) error {
ldr, err := loader.NewLoader(
o.loadRestrictor, v, o.kustomizationPath, fSys)
if err != nil {
return err
}
defer ldr.Cleanup()
kt, err := target.NewKustTarget(ldr, rf, ptf, pl)
if err != nil {
return err
}
m, err := kt.MakeCustomizedResMap()
if err != nil {
return err
}
return o.emitResources(out, fSys, m)
}
func (o *Options) RunBuildPrune(
out io.Writer, v ifc.Validator, fSys fs.FileSystem,
rf *resmap.Factory, ptf resmap.PatchFactory,
pl *plugins.Loader) error {
ldr, err := loader.NewLoader(
o.loadRestrictor, v, o.kustomizationPath, fSys)
if err != nil {
return err
}
defer ldr.Cleanup()
kt, err := target.NewKustTarget(ldr, rf, ptf, pl)
if err != nil {
return err
}
m, err := kt.MakePruneConfigMap()
if err != nil {
return err
}
return o.emitResources(out, fSys, m)
}
func (o *Options) emitResources(
out io.Writer, fSys fs.FileSystem, m resmap.ResMap) error {
if o.outputPath != "" && fSys.IsDir(o.outputPath) {
return writeIndividualFiles(fSys, o.outputPath, m)
}
if o.outOrder == legacy {
// Done this way just to show how overall sorting
// can be performed by a plugin. This particular
// plugin doesn't require configuration; just make
// it and call transform.
builtin.NewLegacyOrderTransformerPlugin().Transform(m)
}
res, err := m.AsYaml()
if err != nil {
return err
}
if o.outputPath != "" {
return fSys.WriteFile(o.outputPath, res)
}
_, err = out.Write(res)
return err
}
func NewCmdBuildPrune(
out io.Writer, v ifc.Validator, fSys fs.FileSystem,
rf *resmap.Factory, ptf resmap.PatchFactory,
pl *plugins.Loader) *cobra.Command {
var o Options
cmd := &cobra.Command{
Use: "alpha-inventory [path]",
Short: "Print the inventory object which contains a list of all other objects",
Example: examples,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args)
if err != nil {
return err
}
return o.RunBuildPrune(out, v, fSys, rf, ptf, pl)
},
}
return cmd
}
func writeIndividualFiles(
fSys fs.FileSystem, folderPath string, m resmap.ResMap) error {
byNamespace := m.GroupedByCurrentNamespace()
for namespace, resList := range byNamespace {
for _, res := range resList {
fName := fileName(res)
if len(byNamespace) > 1 {
fName = strings.ToLower(namespace) + "_" + fName
}
err := writeFile(fSys, folderPath, fName, res)
if err != nil {
return err
}
}
}
for _, res := range m.NonNamespaceable() {
err := writeFile(fSys, folderPath, fileName(res), res)
if err != nil {
return err
}
}
return nil
}
func fileName(res *resource.Resource) string {
return strings.ToLower(res.GetGvk().String()) +
"_" + strings.ToLower(res.GetName()) + ".yaml"
}
func writeFile(
fSys fs.FileSystem, path, fName string, res *resource.Resource) error {
out, err := yaml.Marshal(res.Map())
if err != nil {
return err
}
return fSys.WriteFile(filepath.Join(path, fName), out)
}

View File

@@ -1,51 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package build
import (
"testing"
"sigs.k8s.io/kustomize/v3/pkg/pgmconfig"
)
func TestNewOptionsToSilenceCodeInspectionError(t *testing.T) {
if NewOptions("foo", "bar") == nil {
t.Fatal("could not make new options")
}
}
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 " + pgmconfig.KustomizationFileNames[0]},
}
for _, mycase := range cases {
opts := Options{}
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)
}
}
}

View File

@@ -1,50 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package build
import (
"fmt"
"github.com/spf13/pflag"
)
//go:generate stringer -type=reorderOutput
type reorderOutput int
const (
unspecified reorderOutput = iota
none
legacy
)
const (
flagReorderOutputName = "reorder"
)
var (
flagReorderOutputValue = legacy.String()
flagReorderOutputHelp = "Reorder the resources just before output. " +
"Use '" + legacy.String() + "' to apply a legacy reordering (Namespaces first, Webhooks last, etc). " +
"Use '" + none.String() + "' to suppress a final reordering."
)
func addFlagReorderOutput(set *pflag.FlagSet) {
set.StringVar(
&flagReorderOutputValue, flagReorderOutputName,
legacy.String(), flagReorderOutputHelp)
}
func validateFlagReorderOutput() (reorderOutput, error) {
switch flagReorderOutputValue {
case none.String():
return none, nil
case legacy.String():
return legacy, nil
default:
return unspecified, fmt.Errorf(
"illegal flag value --%s %s; legal values: %v",
flagReorderOutputName, flagReorderOutputValue,
[]string{legacy.String(), none.String()})
}
}

View File

@@ -1,25 +0,0 @@
// Code generated by "stringer -type=reorderOutput"; DO NOT EDIT.
package build
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[unspecified-0]
_ = x[none-1]
_ = x[legacy-2]
}
const _reorderOutput_name = "unspecifiednonelegacy"
var _reorderOutput_index = [...]uint8{0, 11, 15, 21}
func (i reorderOutput) String() string {
if i < 0 || i >= reorderOutput(len(_reorderOutput_index)-1) {
return "reorderOutput(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _reorderOutput_name[_reorderOutput_index[i]:_reorderOutput_index[i+1]]
}

View File

@@ -1,58 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package commands holds the CLI glue mapping textual commands/args to method calls.
package commands
import (
"flag"
"os"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/v3/k8sdeps/transformer"
"sigs.k8s.io/kustomize/v3/k8sdeps/validator"
"sigs.k8s.io/kustomize/v3/pkg/commands/build"
"sigs.k8s.io/kustomize/v3/pkg/commands/create"
"sigs.k8s.io/kustomize/v3/pkg/commands/edit"
"sigs.k8s.io/kustomize/v3/pkg/commands/misc"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/pgmconfig"
"sigs.k8s.io/kustomize/v3/pkg/resmap"
"sigs.k8s.io/kustomize/v3/pkg/resource"
)
// NewDefaultCommand returns the default (aka root) command for kustomize command.
func NewDefaultCommand() *cobra.Command {
fSys := fs.MakeRealFS()
stdOut := os.Stdout
c := &cobra.Command{
Use: pgmconfig.ProgramName,
Short: "Manages declarative configuration of Kubernetes",
Long: `
Manages declarative configuration of Kubernetes.
See https://sigs.k8s.io/kustomize
`,
}
uf := kunstruct.NewKunstructuredFactoryImpl()
pf := transformer.NewFactoryImpl()
rf := resmap.NewFactory(resource.NewFactory(uf), pf)
v := validator.NewKustValidator()
c.AddCommand(
build.NewCmdBuild(
stdOut, fSys, v,
rf, pf),
edit.NewCmdEdit(fSys, v, uf),
create.NewCmdCreate(fSys, uf),
misc.NewCmdConfig(fSys),
misc.NewCmdVersion(stdOut),
)
c.PersistentFlags().AddGoFlagSet(flag.CommandLine)
// Workaround for this issue:
// https://github.com/kubernetes/kubernetes/issues/17162
flag.CommandLine.Parse([]string{})
return c
}

View File

@@ -1,185 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package create
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/commands/util"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/ifc"
"sigs.k8s.io/kustomize/v3/pkg/pgmconfig"
)
type createFlags struct {
resources string
namespace string
annotations string
labels string
prefix string
suffix string
detectResources bool
detectRecursive bool
path string
}
// NewCmdCreate returns an instance of 'create' subcommand.
func NewCmdCreate(fSys fs.FileSystem, uf ifc.KunstructuredFactory) *cobra.Command {
opts := createFlags{path: "."}
c := &cobra.Command{
Use: "create",
Short: "Create a new kustomization in the current directory",
Long: "",
Example: `
# Create a new overlay from the base '../base".
kustomize create --resources ../base
# Create a new kustomization detecting resources in the current directory.
kustomize create --autodetect
# Create a new kustomization with multiple resources and fields set.
kustomize create --resources deployment.yaml,service.yaml,../base --namespace staging --nameprefix acme-
`,
RunE: func(cmd *cobra.Command, args []string) error {
return runCreate(opts, fSys, uf)
},
}
c.Flags().StringVar(
&opts.resources,
"resources",
"",
"Name of a file containing a file to add to the kustomization file.")
c.Flags().StringVar(
&opts.namespace,
"namespace",
"",
"Set the value of the namespace field in the customization file.")
c.Flags().StringVar(
&opts.annotations,
"annotations",
"",
"Add one or more common annotations.")
c.Flags().StringVar(
&opts.labels,
"labels",
"",
"Add one or more common labels.")
c.Flags().StringVar(
&opts.prefix,
"nameprefix",
"",
"Sets the value of the namePrefix field in the kustomization file.")
c.Flags().StringVar(
&opts.suffix,
"namesuffix",
"",
"Sets the value of the nameSuffix field in the kustomization file.")
c.Flags().BoolVar(
&opts.detectResources,
"autodetect",
false,
"Search for kubernetes resources in the current directory to be added to the kustomization file.")
c.Flags().BoolVar(
&opts.detectRecursive,
"recursive",
false,
"Enable recursive directory searching for resource auto-detection.")
return c
}
func runCreate(opts createFlags, fSys fs.FileSystem, uf ifc.KunstructuredFactory) error {
var resources []string
var err error
if opts.resources != "" {
resources, err = util.GlobPatterns(fSys, strings.Split(opts.resources, ","))
if err != nil {
return err
}
}
if _, err = kustfile.NewKustomizationFile(fSys); err == nil {
return fmt.Errorf("kustomization file already exists")
}
if opts.detectResources {
detected, err := detectResources(fSys, uf, opts.path, opts.detectRecursive)
if err != nil {
return err
}
for _, resource := range detected {
if kustfile.StringInSlice(resource, resources) {
continue
}
resources = append(resources, resource)
}
}
f, err := fSys.Create("kustomization.yaml")
if err != nil {
return err
}
f.Close()
mf, err := kustfile.NewKustomizationFile(fSys)
if err != nil {
return err
}
m, err := mf.Read()
if err != nil {
return err
}
m.Resources = resources
m.Namespace = opts.namespace
m.NamePrefix = opts.prefix
m.NameSuffix = opts.suffix
annotations, err := util.ConvertToMap(opts.annotations, "annotation")
if err != nil {
return err
}
m.CommonAnnotations = annotations
labels, err := util.ConvertToMap(opts.labels, "label")
if err != nil {
return err
}
m.CommonLabels = labels
return mf.Write(m)
}
func detectResources(fSys fs.FileSystem, uf ifc.KunstructuredFactory, base string, recursive bool) ([]string, error) {
var paths []string
err := fSys.Walk(base, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == base {
return nil
}
if info.IsDir() {
if !recursive {
return filepath.SkipDir
}
// If a sub-directory contains an existing kustomization file add the
// directory as a resource and do not decend into it.
for _, kfilename := range pgmconfig.KustomizationFileNames {
if fSys.Exists(filepath.Join(path, kfilename)) {
paths = append(paths, path)
return filepath.SkipDir
}
}
return nil
}
fContents, err := fSys.ReadFile(path)
if err != nil {
return err
}
if _, err := uf.SliceFromBytes(fContents); err != nil {
return nil
}
paths = append(paths, path)
return nil
})
return paths, err
}

View File

@@ -1,195 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package create
import (
"reflect"
"testing"
"sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/types"
)
var factory = kunstruct.NewKunstructuredFactoryImpl()
func readKustomizationFS(t *testing.T, fakeFS fs.FileSystem) *types.Kustomization {
kf, err := kustfile.NewKustomizationFile(fakeFS)
if err != nil {
t.Errorf("unexpected new error %v", err)
}
m, err := kf.Read()
if err != nil {
t.Errorf("unexpected read error %v", err)
}
return m
}
func TestCreateNoArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
cmd := NewCmdCreate(fakeFS, factory)
err := cmd.RunE(cmd, []string{})
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
readKustomizationFS(t, fakeFS)
}
func TestCreateWithResources(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile("foo.yaml", []byte(""))
fakeFS.WriteFile("bar.yaml", []byte(""))
opts := createFlags{resources: "foo.yaml,bar.yaml"}
err := runCreate(opts, fakeFS, factory)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
m := readKustomizationFS(t, fakeFS)
expected := []string{"foo.yaml", "bar.yaml"}
if !reflect.DeepEqual(m.Resources, expected) {
t.Fatalf("expected %+v but got %+v", expected, m.Resources)
}
}
func TestCreateWithNamespace(t *testing.T) {
fakeFS := fs.MakeFakeFS()
want := "foo"
opts := createFlags{namespace: want}
err := runCreate(opts, fakeFS, factory)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
m := readKustomizationFS(t, fakeFS)
got := m.Namespace
if got != want {
t.Errorf("want: %s, got: %s", want, got)
}
}
func TestCreateWithLabels(t *testing.T) {
fakeFS := fs.MakeFakeFS()
opts := createFlags{labels: "foo:bar"}
err := runCreate(opts, fakeFS, factory)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
m := readKustomizationFS(t, fakeFS)
expected := map[string]string{"foo": "bar"}
if !reflect.DeepEqual(m.CommonLabels, expected) {
t.Fatalf("expected %+v but got %+v", expected, m.CommonLabels)
}
}
func TestCreateWithAnnotations(t *testing.T) {
fakeFS := fs.MakeFakeFS()
opts := createFlags{annotations: "foo:bar"}
err := runCreate(opts, fakeFS, factory)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
m := readKustomizationFS(t, fakeFS)
expected := map[string]string{"foo": "bar"}
if !reflect.DeepEqual(m.CommonAnnotations, expected) {
t.Fatalf("expected %+v but got %+v", expected, m.CommonAnnotations)
}
}
func TestCreateWithNamePrefix(t *testing.T) {
fakeFS := fs.MakeFakeFS()
want := "foo-"
opts := createFlags{prefix: want}
err := runCreate(opts, fakeFS, factory)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
m := readKustomizationFS(t, fakeFS)
got := m.NamePrefix
if got != want {
t.Errorf("want: %s, got: %s", want, got)
}
}
func TestCreateWithNameSuffix(t *testing.T) {
fakeFS := fs.MakeFakeFS()
opts := createFlags{suffix: "-foo"}
err := runCreate(opts, fakeFS, factory)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
m := readKustomizationFS(t, fakeFS)
if m.NameSuffix != "-foo" {
t.Errorf("want: -foo, got: %s", m.NameSuffix)
}
}
func writeDetectContent(fakeFS fs.FileSystem) {
fakeFS.WriteFile("/test.yaml", []byte(`
apiVersion: v1
kind: Service
metadata:
name: test`))
fakeFS.WriteFile("/README.md", []byte(`
# Not a k8s resource
This file is not a valid kubernetes object.`))
fakeFS.WriteFile("/non-k8s.yaml", []byte(`
# Not a k8s resource
other: yaml
foo:
- bar
- baz`))
fakeFS.Mkdir("/sub")
fakeFS.WriteFile("/sub/test.yaml", []byte(`
apiVersion: v1
kind: Service
metadata:
name: test2`))
fakeFS.WriteFile("/sub/README.md", []byte(`
# Not a k8s resource
This file in a subdirectory is not a valid kubernetes object.`))
fakeFS.WriteFile("/sub/non-k8s.yaml", []byte(`
# Not a k8s resource
other: yaml
foo:
- bar
- baz`))
fakeFS.Mkdir("/overlay")
fakeFS.WriteFile("/overlay/test.yaml", []byte(`
apiVersion: v1
kind: Service
metadata:
name: test3`))
fakeFS.WriteFile("/overlay/kustomization.yaml", []byte(`
resources:
- test.yaml`))
}
func TestCreateWithDetect(t *testing.T) {
fakeFS := fs.MakeFakeFS()
writeDetectContent(fakeFS)
opts := createFlags{path: "/", detectResources: true}
err := runCreate(opts, fakeFS, factory)
if err != nil {
t.Fatalf("unexpected cmd error: %v", err)
}
m := readKustomizationFS(t, fakeFS)
expected := []string{"/test.yaml"}
if !reflect.DeepEqual(m.Resources, expected) {
t.Fatalf("expected %+v but got %+v", expected, m.Resources)
}
}
func TestCreateWithDetectRecursive(t *testing.T) {
fakeFS := fs.MakeFakeFS()
writeDetectContent(fakeFS)
opts := createFlags{path: "/", detectResources: true, detectRecursive: true}
err := runCreate(opts, fakeFS, factory)
if err != nil {
t.Fatalf("unexpected cmd error: %v", err)
}
m := readKustomizationFS(t, fakeFS)
expected := []string{"/overlay", "/sub/test.yaml", "/test.yaml"}
if !reflect.DeepEqual(m.Resources, expected) {
t.Fatalf("expected %+v but got %+v", expected, m.Resources)
}
}

View File

@@ -1,84 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package add
import (
"errors"
"fmt"
"strings"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/fs"
)
type addBaseOptions struct {
baseDirectoryPaths string
}
// newCmdAddBase adds the file path of the kustomize base to the kustomization file.
func newCmdAddBase(fsys fs.FileSystem) *cobra.Command {
var o addBaseOptions
cmd := &cobra.Command{
Use: "base",
Short: "Adds one or more bases to the kustomization.yaml in current directory",
Example: `
add base {filepath1},{filepath2}`,
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.RunAddBase(fsys)
},
}
return cmd
}
// Validate validates addBase command.
func (o *addBaseOptions) Validate(args []string) error {
if len(args) != 1 {
return errors.New("must specify a base directory")
}
o.baseDirectoryPaths = args[0]
return nil
}
// Complete completes addBase command.
func (o *addBaseOptions) Complete(cmd *cobra.Command, args []string) error {
return nil
}
// RunAddBase runs addBase command (do real work).
func (o *addBaseOptions) RunAddBase(fSys fs.FileSystem) error {
mf, err := kustfile.NewKustomizationFile(fSys)
if err != nil {
return err
}
m, err := mf.Read()
if err != nil {
return err
}
// split directory paths
paths := strings.Split(o.baseDirectoryPaths, ",")
for _, path := range paths {
if !fSys.Exists(path) {
return errors.New(path + " does not exist")
}
if kustfile.StringInSlice(path, m.Resources) {
return fmt.Errorf("base %s already in kustomization file", path)
}
m.Resources = append(m.Resources, path)
}
return mf.Write(m)
}

View File

@@ -1,86 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package add
import (
"strings"
"testing"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/fs"
)
const (
baseDirectoryPaths = "my/path/to/wonderful/base,other/path/to/even/more/wonderful/base"
)
func TestAddBaseHappyPath(t *testing.T) {
fakeFS := fs.MakeFakeFS()
bases := strings.Split(baseDirectoryPaths, ",")
for _, base := range bases {
fakeFS.Mkdir(base)
}
fakeFS.WriteTestKustomization()
cmd := newCmdAddBase(fakeFS)
args := []string{baseDirectoryPaths}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadTestKustomization()
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
for _, base := range bases {
if !strings.Contains(string(content), base) {
t.Errorf("expected base name in kustomization")
}
}
}
func TestAddBaseAlreadyThere(t *testing.T) {
fakeFS := fs.MakeFakeFS()
// Create fake directories
bases := strings.Split(baseDirectoryPaths, ",")
for _, base := range bases {
fakeFS.Mkdir(base)
}
fakeFS.WriteTestKustomization()
cmd := newCmdAddBase(fakeFS)
args := []string{baseDirectoryPaths}
err := cmd.RunE(cmd, args)
if err != nil {
t.Fatalf("unexpected cmd error: %v", err)
}
// adding an existing base should return an error
err = cmd.RunE(cmd, args)
if err == nil {
t.Errorf("expected already there problem")
}
var expectedErrors []string
for _, base := range bases {
msg := "base " + base + " already in kustomization file"
expectedErrors = append(expectedErrors, msg)
if !kustfile.StringInSlice(msg, expectedErrors) {
t.Errorf("unexpected error %v", err)
}
}
}
func TestAddBaseNoArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
cmd := newCmdAddBase(fakeFS)
err := cmd.Execute()
if err == nil {
t.Errorf("expected error: %v", err)
}
if err.Error() != "must specify a base directory" {
t.Errorf("incorrect error: %v", err.Error())
}
}

View File

@@ -1,145 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package add
import (
"fmt"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/commands/util"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/pgmconfig"
"sigs.k8s.io/kustomize/v3/pkg/types"
)
// kindOfAdd is the kind of metadata being added: label or annotation
type kindOfAdd int
const (
annotation kindOfAdd = iota
label
)
func (k kindOfAdd) String() string {
kinds := [...]string{
"annotation",
"label",
}
if k < 0 || k > 1 {
return "Unknown metadatakind"
}
return kinds[k]
}
type addMetadataOptions struct {
force bool
metadata map[string]string
mapValidator func(map[string]string) error
kind kindOfAdd
}
// newCmdAddAnnotation adds one or more commonAnnotations to the kustomization file.
func newCmdAddAnnotation(fSys fs.FileSystem, v func(map[string]string) error) *cobra.Command {
var o addMetadataOptions
o.kind = annotation
o.mapValidator = v
cmd := &cobra.Command{
Use: "annotation",
Short: "Adds one or more commonAnnotations to " + pgmconfig.KustomizationFileNames[0],
Example: `
add annotation {annotationKey1:annotationValue1},{annotationKey2:annotationValue2}`,
RunE: func(cmd *cobra.Command, args []string) error {
return o.runE(args, fSys, o.addAnnotations)
},
}
cmd.Flags().BoolVarP(&o.force, "force", "f", false,
"overwrite commonAnnotation if it already exists",
)
return cmd
}
// newCmdAddLabel adds one or more commonLabels to the kustomization file.
func newCmdAddLabel(fSys fs.FileSystem, v func(map[string]string) error) *cobra.Command {
var o addMetadataOptions
o.kind = label
o.mapValidator = v
cmd := &cobra.Command{
Use: "label",
Short: "Adds one or more commonLabels to " + pgmconfig.KustomizationFileNames[0],
Example: `
add label {labelKey1:labelValue1},{labelKey2:labelValue2}`,
RunE: func(cmd *cobra.Command, args []string) error {
return o.runE(args, fSys, o.addLabels)
},
}
cmd.Flags().BoolVarP(&o.force, "force", "f", false,
"overwrite commonLabel if it already exists",
)
return cmd
}
func (o *addMetadataOptions) runE(
args []string, fSys fs.FileSystem, adder func(*types.Kustomization) error) error {
err := o.validateAndParse(args)
if err != nil {
return err
}
kf, err := kustfile.NewKustomizationFile(fSys)
if err != nil {
return err
}
m, err := kf.Read()
if err != nil {
return err
}
err = adder(m)
if err != nil {
return err
}
return kf.Write(m)
}
// validateAndParse validates `add` commands and parses them into o.metadata
func (o *addMetadataOptions) validateAndParse(args []string) error {
if len(args) < 1 {
return fmt.Errorf("must specify %s", o.kind)
}
if len(args) > 1 {
return fmt.Errorf("%ss must be comma-separated, with no spaces", o.kind)
}
m, err := util.ConvertToMap(args[0], o.kind.String())
if err != nil {
return err
}
if err = o.mapValidator(m); err != nil {
return err
}
o.metadata = m
return nil
}
func (o *addMetadataOptions) addAnnotations(m *types.Kustomization) error {
if m.CommonAnnotations == nil {
m.CommonAnnotations = make(map[string]string)
}
return o.writeToMap(m.CommonAnnotations, annotation)
}
func (o *addMetadataOptions) addLabels(m *types.Kustomization) error {
if m.CommonLabels == nil {
m.CommonLabels = make(map[string]string)
}
return o.writeToMap(m.CommonLabels, label)
}
func (o *addMetadataOptions) writeToMap(m map[string]string, kind kindOfAdd) error {
for k, v := range o.metadata {
if _, ok := m[k]; ok && !o.force {
return fmt.Errorf("%s %s already in kustomization file", kind, k)
}
m[k] = v
}
return nil
}

View File

@@ -1,350 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package add
import (
"testing"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/types"
"sigs.k8s.io/kustomize/v3/pkg/validators"
)
func makeKustomization(t *testing.T) *types.Kustomization {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
kf, err := kustfile.NewKustomizationFile(fakeFS)
if err != nil {
t.Errorf("unexpected new error %v", err)
}
m, err := kf.Read()
if err != nil {
t.Errorf("unexpected read error %v", err)
}
return m
}
func TestRunAddAnnotation(t *testing.T) {
var o addMetadataOptions
o.metadata = map[string]string{"owls": "cute", "otters": "adorable"}
m := makeKustomization(t)
err := o.addAnnotations(m)
if err != nil {
t.Errorf("unexpected error: could not write to kustomization file")
}
// adding the same test input should not work
err = o.addAnnotations(m)
if err == nil {
t.Errorf("expected already in kustomization file error")
}
// adding new annotations should work
o.metadata = map[string]string{"new": "annotation"}
err = o.addAnnotations(m)
if err != nil {
t.Errorf("unexpected error: could not write to kustomization file")
}
}
func TestAddAnnotationNoArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdAddAnnotation(fakeFS, v.Validator)
err := cmd.Execute()
v.VerifyNoCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "must specify annotation" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestAddAnnotationInvalidFormat(t *testing.T) {
fakeFS := fs.MakeFakeFS()
v := validators.MakeSadMapValidator(t)
cmd := newCmdAddAnnotation(fakeFS, v.Validator)
args := []string{"whatever:whatever"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != validators.SAD {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestAddAnnotationManyArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdAddAnnotation(fakeFS, v.Validator)
args := []string{"k1:v1,k2:v2,k3:v3,k4:v5"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error: %v", err.Error())
}
}
func TestAddAnnotationValueQuoted(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdAddAnnotation(fakeFS, v.Validator)
args := []string{"k1:\"v1\""}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error: %v", err.Error())
}
}
func TestAddAnnotationValueWithColon(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdAddAnnotation(fakeFS, v.Validator)
args := []string{"k1:\"v1:v2\""}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error: %v", err.Error())
}
}
func TestAddAnnotationNoKey(t *testing.T) {
fakeFS := fs.MakeFakeFS()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdAddAnnotation(fakeFS, v.Validator)
args := []string{":nokey"}
err := cmd.RunE(cmd, args)
v.VerifyNoCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "invalid annotation: ':nokey' (need k:v pair where v may be quoted)" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestAddAnnotationTooManyColons(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdAddAnnotation(fakeFS, v.Validator)
args := []string{"key:v1:v2"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error: %v", err.Error())
}
}
func TestAddAnnotationNoValue(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdAddAnnotation(fakeFS, v.Validator)
args := []string{"no:,value"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error: %v", err.Error())
}
}
func TestAddAnnotationMultipleArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdAddAnnotation(fakeFS, v.Validator)
args := []string{"this:annotation", "has:spaces"}
err := cmd.RunE(cmd, args)
v.VerifyNoCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "annotations must be comma-separated, with no spaces" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestAddAnnotationForce(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdAddAnnotation(fakeFS, v.Validator)
args := []string{"key:foo"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error: %v", err.Error())
}
// trying to add the same annotation again should not work
args = []string{"key:bar"}
v = validators.MakeHappyMapValidator(t)
cmd = newCmdAddAnnotation(fakeFS, v.Validator)
err = cmd.RunE(cmd, args)
v.VerifyCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "annotation key already in kustomization file" {
t.Errorf("expected an error")
}
// but trying to add it with --force should
v = validators.MakeHappyMapValidator(t)
cmd = newCmdAddAnnotation(fakeFS, v.Validator)
cmd.Flag("force").Value.Set("true")
err = cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error: %v", err.Error())
}
}
func TestRunAddLabel(t *testing.T) {
var o addMetadataOptions
o.metadata = map[string]string{"owls": "cute", "otters": "adorable"}
m := makeKustomization(t)
err := o.addLabels(m)
if err != nil {
t.Errorf("unexpected error: could not write to kustomization file")
}
// adding the same test input should not work
err = o.addLabels(m)
if err == nil {
t.Errorf("expected already in kustomization file error")
}
// adding new labels should work
o.metadata = map[string]string{"new": "label"}
err = o.addLabels(m)
if err != nil {
t.Errorf("unexpected error: could not write to kustomization file")
}
}
func TestAddLabelNoArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdAddLabel(fakeFS, v.Validator)
err := cmd.Execute()
v.VerifyNoCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "must specify label" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestAddLabelInvalidFormat(t *testing.T) {
fakeFS := fs.MakeFakeFS()
v := validators.MakeSadMapValidator(t)
cmd := newCmdAddLabel(fakeFS, v.Validator)
args := []string{"exclamation!:point"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != validators.SAD {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestAddLabelNoKey(t *testing.T) {
fakeFS := fs.MakeFakeFS()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdAddLabel(fakeFS, v.Validator)
args := []string{":nokey"}
err := cmd.RunE(cmd, args)
v.VerifyNoCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "invalid label: ':nokey' (need k:v pair where v may be quoted)" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestAddLabelTooManyColons(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdAddLabel(fakeFS, v.Validator)
args := []string{"key:v1:v2"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error: %v", err.Error())
}
}
func TestAddLabelNoValue(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdAddLabel(fakeFS, v.Validator)
args := []string{"no,value:"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error: %v", err.Error())
}
}
func TestAddLabelMultipleArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdAddLabel(fakeFS, v.Validator)
args := []string{"this:input", "has:spaces"}
err := cmd.RunE(cmd, args)
v.VerifyNoCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "labels must be comma-separated, with no spaces" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestAddLabelForce(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdAddLabel(fakeFS, v.Validator)
args := []string{"key:foo"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error: %v", err.Error())
}
// trying to add the same label again should not work
args = []string{"key:bar"}
v = validators.MakeHappyMapValidator(t)
cmd = newCmdAddLabel(fakeFS, v.Validator)
err = cmd.RunE(cmd, args)
v.VerifyCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "label key already in kustomization file" {
t.Errorf("expected an error")
}
// but trying to add it with --force should
v = validators.MakeHappyMapValidator(t)
cmd = newCmdAddLabel(fakeFS, v.Validator)
cmd.Flag("force").Value.Set("true")
err = cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error: %v", err.Error())
}
}

View File

@@ -1,88 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package add
import (
"errors"
"log"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/commands/util"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/patch"
)
type addPatchOptions struct {
patchFilePaths []string
}
// newCmdAddPatch adds the name of a file containing a patch to the kustomization file.
func newCmdAddPatch(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(fsys)
},
}
return cmd
}
// Validate validates addPatch command.
func (o *addPatchOptions) Validate(args []string) error {
if len(args) == 0 {
return errors.New("must specify a patch file")
}
o.patchFilePaths = args
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(fSys fs.FileSystem) error {
patches, err := util.GlobPatterns(fSys, o.patchFilePaths)
if err != nil {
return err
}
if len(patches) == 0 {
return nil
}
mf, err := kustfile.NewKustomizationFile(fSys)
if err != nil {
return err
}
m, err := mf.Read()
if err != nil {
return err
}
for _, p := range patches {
if patch.Exist(m.PatchesStrategicMerge, p) {
log.Printf("patch %s already in kustomization file", p)
continue
}
m.PatchesStrategicMerge = patch.Append(m.PatchesStrategicMerge, p)
}
return mf.Write(m)
}

View File

@@ -1,76 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package add
import (
"testing"
"strings"
"sigs.k8s.io/kustomize/v3/pkg/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) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(patchFileName, []byte(patchFileContent))
fakeFS.WriteFile(patchFileName+"another", []byte(patchFileContent))
fakeFS.WriteTestKustomization()
cmd := newCmdAddPatch(fakeFS)
args := []string{patchFileName + "*"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadTestKustomization()
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
if !strings.Contains(string(content), patchFileName) {
t.Errorf("expected patch name in kustomization")
}
if !strings.Contains(string(content), patchFileName+"another") {
t.Errorf("expected patch name in kustomization")
}
}
func TestAddPatchAlreadyThere(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(patchFileName, []byte(patchFileContent))
fakeFS.WriteTestKustomization()
cmd := newCmdAddPatch(fakeFS)
args := []string{patchFileName}
err := cmd.RunE(cmd, args)
if err != nil {
t.Fatalf("unexpected cmd error: %v", err)
}
// adding an existing patch shouldn't return an error
err = cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
}
func TestAddPatchNoArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
cmd := newCmdAddPatch(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

@@ -1,87 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package add
import (
"errors"
"log"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/commands/util"
"sigs.k8s.io/kustomize/v3/pkg/fs"
)
type addResourceOptions struct {
resourceFilePaths []string
}
// newCmdAddResource adds the name of a file containing a resource to the kustomization file.
func newCmdAddResource(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(fsys)
},
}
return cmd
}
// Validate validates addResource command.
func (o *addResourceOptions) Validate(args []string) error {
if len(args) == 0 {
return errors.New("must specify a resource file")
}
o.resourceFilePaths = args
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(fSys fs.FileSystem) error {
resources, err := util.GlobPatterns(fSys, o.resourceFilePaths)
if err != nil {
return err
}
if len(resources) == 0 {
return nil
}
mf, err := kustfile.NewKustomizationFile(fSys)
if err != nil {
return err
}
m, err := mf.Read()
if err != nil {
return err
}
for _, resource := range resources {
if kustfile.StringInSlice(resource, m.Resources) {
log.Printf("resource %s already in kustomization file", resource)
continue
}
m.Resources = append(m.Resources, resource)
}
return mf.Write(m)
}

View File

@@ -1,75 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package add
import (
"strings"
"testing"
"sigs.k8s.io/kustomize/v3/pkg/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) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(resourceFileName, []byte(resourceFileContent))
fakeFS.WriteFile(resourceFileName+"another", []byte(resourceFileContent))
fakeFS.WriteTestKustomization()
cmd := newCmdAddResource(fakeFS)
args := []string{resourceFileName + "*"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadTestKustomization()
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
if !strings.Contains(string(content), resourceFileName) {
t.Errorf("expected resource name in kustomization")
}
if !strings.Contains(string(content), resourceFileName+"another") {
t.Errorf("expected resource name in kustomization")
}
}
func TestAddResourceAlreadyThere(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(resourceFileName, []byte(resourceFileContent))
fakeFS.WriteTestKustomization()
cmd := newCmdAddResource(fakeFS)
args := []string{resourceFileName}
err := cmd.RunE(cmd, args)
if err != nil {
t.Fatalf("unexpected cmd error: %v", err)
}
// adding an existing resource doesn't return an error
err = cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error :%v", err)
}
}
func TestAddResourceNoArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
cmd := newCmdAddResource(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())
}
}

View File

@@ -1,56 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package add
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/ifc"
)
// NewCmdAdd returns an instance of 'add' subcommand.
func NewCmdAdd(
fSys fs.FileSystem,
ldr ifc.Loader,
kf ifc.KunstructuredFactory) *cobra.Command {
c := &cobra.Command{
Use: "add",
Short: "Adds an item to the kustomization file.",
Long: "",
Example: `
# Adds a secret to the kustomization file
kustomize edit add secret NAME --from-literal=k=v
# Adds a configmap to the kustomization file
kustomize edit add configmap 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>
# Adds one or more base directories to the kustomization
kustomize edit add base <filepath>
kustomize edit add base <filepath1>,<filepath2>,<filepath3>
# Adds one or more commonLabels to the kustomization
kustomize edit add label {labelKey1:labelValue1},{labelKey2:labelValue2}
# Adds one or more commonAnnotations to the kustomization
kustomize edit add annotation {annotationKey1:annotationValue1},{annotationKey2:annotationValue2}
`,
Args: cobra.MinimumNArgs(1),
}
c.AddCommand(
newCmdAddResource(fSys),
newCmdAddPatch(fSys),
newCmdAddSecret(fSys, ldr, kf),
newCmdAddConfigMap(fSys, ldr, kf),
newCmdAddBase(fSys),
newCmdAddLabel(fSys, ldr.Validator().MakeLabelValidator()),
newCmdAddAnnotation(fSys, ldr.Validator().MakeAnnotationValidator()),
)
return c
}

View File

@@ -1,130 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package add
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/ifc"
"sigs.k8s.io/kustomize/v3/pkg/types"
)
// newCmdAddConfigMap returns a new command.
func newCmdAddConfigMap(
fSys fs.FileSystem,
ldr ifc.Loader,
kf ifc.KunstructuredFactory) *cobra.Command {
var flags flagsAndArgs
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 := flags.ExpandFileSource(fSys)
if err != nil {
return err
}
err = flags.Validate(args)
if err != nil {
return err
}
// Load the kustomization file.
mf, err := kustfile.NewKustomizationFile(fSys)
if err != nil {
return err
}
kustomization, err := mf.Read()
if err != nil {
return err
}
// Add the flagsAndArgs map to the kustomization file.
err = addConfigMap(ldr, kustomization, flags, kf)
if err != nil {
return err
}
// Write out the kustomization file with added configmap.
return mf.Write(kustomization)
},
}
cmd.Flags().StringSliceVar(
&flags.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(
&flags.LiteralSources,
"from-literal",
[]string{},
"Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
cmd.Flags().StringVar(
&flags.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 adds a configmap to a kustomization file.
// Note: error may leave kustomization file in an undefined state.
// Suggest passing a copy of kustomization file.
func addConfigMap(
ldr ifc.Loader,
k *types.Kustomization,
flags flagsAndArgs, kf ifc.KunstructuredFactory) error {
args := findOrMakeConfigMapArgs(k, flags.Name)
mergeFlagsIntoCmArgs(args, flags)
// Validate by trying to create corev1.configmap.
_, err := kf.MakeConfigMap(ldr, k.GeneratorOptions, args)
if err != nil {
return err
}
return nil
}
func findOrMakeConfigMapArgs(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{GeneratorArgs: types.GeneratorArgs{Name: name}}
m.ConfigMapGenerator = append(m.ConfigMapGenerator, *cm)
return &m.ConfigMapGenerator[len(m.ConfigMapGenerator)-1]
}
func mergeFlagsIntoCmArgs(args *types.ConfigMapArgs, flags flagsAndArgs) {
if len(flags.LiteralSources) > 0 {
args.LiteralSources = append(
args.LiteralSources, flags.LiteralSources...)
}
if len(flags.FileSources) > 0 {
args.FileSources = append(
args.FileSources, flags.FileSources...)
}
if flags.EnvFileSource != "" {
args.EnvSources = append(
args.EnvSources, flags.EnvFileSource)
}
}

View File

@@ -1,107 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package add
import (
"testing"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/loader"
"sigs.k8s.io/kustomize/v3/pkg/types"
"sigs.k8s.io/kustomize/v3/pkg/validators"
)
func TestNewAddConfigMapIsNotNil(t *testing.T) {
fSys := fs.MakeFakeFS()
ldr := loader.NewFileLoaderAtCwd(validators.MakeFakeValidator(), fSys)
if newCmdAddConfigMap(fSys, ldr, nil) == nil {
t.Fatal("newCmdAddConfigMap shouldn't be nil")
}
}
func TestMakeConfigMapArgs(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")
}
args := findOrMakeConfigMapArgs(kustomization, cmName)
if args == nil {
t.Fatalf("args 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] != args {
t.Fatalf("Pointer address for newly inserted configmap generator should be same")
}
args2 := findOrMakeConfigMapArgs(kustomization, cmName)
if args2 != args {
t.Fatalf("should have returned an existing args with name: %v", cmName)
}
if len(kustomization.ConfigMapGenerator) != 1 {
t.Fatalf("Should not insert configmap for an existing name: %v", cmName)
}
}
func TestMergeFlagsIntoConfigMapArgs_LiteralSources(t *testing.T) {
k := &types.Kustomization{}
args := findOrMakeConfigMapArgs(k, "foo")
mergeFlagsIntoGeneratorArgs(
&args.GeneratorArgs,
flagsAndArgs{LiteralSources: []string{"k1=v1"}})
mergeFlagsIntoGeneratorArgs(
&args.GeneratorArgs,
flagsAndArgs{LiteralSources: []string{"k2=v2"}})
if k.ConfigMapGenerator[0].LiteralSources[0] != "k1=v1" {
t.Fatalf("expected v1")
}
if k.ConfigMapGenerator[0].LiteralSources[1] != "k2=v2" {
t.Fatalf("expected v2")
}
}
func TestMergeFlagsIntoConfigMapArgs_FileSources(t *testing.T) {
k := &types.Kustomization{}
args := findOrMakeConfigMapArgs(k, "foo")
mergeFlagsIntoGeneratorArgs(
&args.GeneratorArgs,
flagsAndArgs{FileSources: []string{"file1"}})
mergeFlagsIntoGeneratorArgs(
&args.GeneratorArgs,
flagsAndArgs{FileSources: []string{"file2"}})
if k.ConfigMapGenerator[0].FileSources[0] != "file1" {
t.Fatalf("expected file1")
}
if k.ConfigMapGenerator[0].FileSources[1] != "file2" {
t.Fatalf("expected file2")
}
}
func TestMergeFlagsIntoConfigMapArgs_EnvSource(t *testing.T) {
k := &types.Kustomization{}
args := findOrMakeConfigMapArgs(k, "foo")
mergeFlagsIntoGeneratorArgs(
&args.GeneratorArgs,
flagsAndArgs{EnvFileSource: "env1"})
mergeFlagsIntoGeneratorArgs(
&args.GeneratorArgs,
flagsAndArgs{EnvFileSource: "env2"})
if k.ConfigMapGenerator[0].EnvSources[0] != "env1" {
t.Fatalf("expected env1")
}
if k.ConfigMapGenerator[0].EnvSources[1] != "env2" {
t.Fatalf("expected env2")
}
}

View File

@@ -1,95 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package add
import (
"fmt"
"strings"
"sigs.k8s.io/kustomize/v3/pkg/commands/util"
"sigs.k8s.io/kustomize/v3/pkg/fs"
)
// flagsAndArgs encapsulates the options for add secret/configmap commands.
type flagsAndArgs 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
// Type of secret to create
Type string
}
// Validate validates required fields are set to support structured generation.
func (a *flagsAndArgs) 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
}
// ExpandFileSource normalizes a string list, possibly
// containing globs, into a validated, globless list.
// For example, this list:
// some/path
// some/dir/a*
// bfile=some/dir/b*
// becomes:
// some/path
// some/dir/airplane
// some/dir/ant
// some/dir/apple
// bfile=some/dir/banana
// i.e. everything is converted to a key=value pair,
// where the value is always a relative file path,
// and the key, if missing, is the same as the value.
// In the case where the key is explicitly declared,
// the globbing, if present, must have exactly one match.
func (a *flagsAndArgs) ExpandFileSource(fSys fs.FileSystem) error {
var results []string
for _, pattern := range a.FileSources {
var patterns []string
key := ""
// check if the pattern is in `--from-file=[key=]source` format
// and if so split it to send only the file-pattern to glob function
s := strings.Split(pattern, "=")
if len(s) == 2 {
patterns = append(patterns, s[1])
key = s[0]
} else {
patterns = append(patterns, s[0])
}
result, err := util.GlobPatterns(fSys, patterns)
if err != nil {
return err
}
// if the format is `--from-file=[key=]source` accept only one result
// and extend it with the `key=` prefix
if key != "" {
if len(result) != 1 {
return fmt.Errorf(
"'pattern '%s' catches files %v, should catch only one", pattern, result)
}
fileSource := fmt.Sprintf("%s=%s", key, result[0])
results = append(results, fileSource)
} else {
results = append(results, result...)
}
}
a.FileSources = results
return nil
}

View File

@@ -1,125 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package add
import (
"reflect"
"testing"
"sigs.k8s.io/kustomize/v3/pkg/fs"
)
func TestDataValidation_NoName(t *testing.T) {
fa := flagsAndArgs{}
if fa.Validate([]string{}) == nil {
t.Fatal("Validation should fail if no name is specified")
}
}
func TestDataValidation_MoreThanOneName(t *testing.T) {
fa := flagsAndArgs{}
if fa.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
fa flagsAndArgs
shouldFail bool
}{
{
name: "env-file-source and literal are both set",
fa: flagsAndArgs{
LiteralSources: []string{"one", "two"},
EnvFileSource: "three",
},
shouldFail: true,
},
{
name: "env-file-source and from-file are both set",
fa: flagsAndArgs{
FileSources: []string{"one", "two"},
EnvFileSource: "three",
},
shouldFail: true,
},
{
name: "we don't have any option set",
fa: flagsAndArgs{},
shouldFail: true,
},
{
name: "we have from-file and literal ",
fa: flagsAndArgs{
LiteralSources: []string{"one", "two"},
FileSources: []string{"three", "four"},
},
shouldFail: false,
},
}
for _, test := range tests {
if test.fa.Validate([]string{"name"}) == nil && test.shouldFail {
t.Fatalf("Validation should fail if %s", test.name)
} else if test.fa.Validate([]string{"name"}) != nil && !test.shouldFail {
t.Fatalf("Validation should succeed if %s", test.name)
}
}
}
func TestExpandFileSource(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.Create("dir/fa1")
fakeFS.Create("dir/fa2")
fakeFS.Create("dir/readme")
fa := flagsAndArgs{
FileSources: []string{"dir/fa*"},
}
fa.ExpandFileSource(fakeFS)
expected := []string{
"dir/fa1",
"dir/fa2",
}
if !reflect.DeepEqual(fa.FileSources, expected) {
t.Fatalf("FileSources is not correctly expanded: %v", fa.FileSources)
}
}
func TestExpandFileSourceWithKey(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.Create("dir/faaaaaaaaaabbbbbbbbbccccccccccccccccc")
fakeFS.Create("dir/foobar")
fakeFS.Create("dir/simplebar")
fakeFS.Create("dir/readme")
fa := flagsAndArgs{
FileSources: []string{"foo-key=dir/fa*", "bar-key=dir/foobar", "dir/simplebar"},
}
fa.ExpandFileSource(fakeFS)
expected := []string{
"foo-key=dir/faaaaaaaaaabbbbbbbbbccccccccccccccccc",
"bar-key=dir/foobar",
"dir/simplebar",
}
if !reflect.DeepEqual(fa.FileSources, expected) {
t.Fatalf("FileSources is not correctly expanded: %v", fa.FileSources)
}
}
func TestExpandFileSourceWithKeyAndError(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.Create("dir/fa1")
fakeFS.Create("dir/fa2")
fakeFS.Create("dir/readme")
fa := flagsAndArgs{
FileSources: []string{"foo-key=dir/fa*"},
}
err := fa.ExpandFileSource(fakeFS)
if err == nil {
t.Fatalf("FileSources should not be correctly expanded: %v", fa.FileSources)
}
}

View File

@@ -1,137 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package add
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/ifc"
"sigs.k8s.io/kustomize/v3/pkg/types"
)
// newCmdAddSecret returns a new command.
func newCmdAddSecret(
fSys fs.FileSystem,
ldr ifc.Loader,
kf ifc.KunstructuredFactory) *cobra.Command {
var flags flagsAndArgs
cmd := &cobra.Command{
Use: "secret NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--type=Opaque|kubernetes.io/tls]",
Short: "Adds a secret to the kustomization file.",
Long: "",
Example: `
# Adds a secret to the kustomization file (with a specified key)
kustomize edit add secret my-secret --from-file=my-key=file/path --from-literal=my-literal=12345
# Adds a secret to the kustomization file (key is the filename)
kustomize edit add secret my-secret --from-file=file/path
# Adds a secret from env-file
kustomize edit add secret my-secret --from-env-file=env/path.env
`,
RunE: func(_ *cobra.Command, args []string) error {
err := flags.ExpandFileSource(fSys)
if err != nil {
return err
}
err = flags.Validate(args)
if err != nil {
return err
}
// Load the kustomization file.
mf, err := kustfile.NewKustomizationFile(fSys)
if err != nil {
return err
}
kustomization, err := mf.Read()
if err != nil {
return err
}
// Add the flagsAndArgs map to the kustomization file.
err = addSecret(ldr, kustomization, flags, kf)
if err != nil {
return err
}
// Write out the kustomization file with added secret.
return mf.Write(kustomization)
},
}
cmd.Flags().StringSliceVar(
&flags.FileSources,
"from-file",
[]string{},
"Key file can be specified using its file path, in which case file basename will be used as secret "+
"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 secret key.")
cmd.Flags().StringArrayVar(
&flags.LiteralSources,
"from-literal",
[]string{},
"Specify a key and literal value to insert in secret (i.e. mykey=somevalue)")
cmd.Flags().StringVar(
&flags.EnvFileSource,
"from-env-file",
"",
"Specify the path to a file to read lines of key=val pairs to create a secret (i.e. a Docker .env file).")
cmd.Flags().StringVar(
&flags.Type,
"type",
"Opaque",
"Specify the secret type this can be 'Opaque' (default), or 'kubernetes.io/tls'")
return cmd
}
// addSecret adds a secret to a kustomization file.
// Note: error may leave kustomization file in an undefined state.
// Suggest passing a copy of kustomization file.
func addSecret(
ldr ifc.Loader,
k *types.Kustomization,
flags flagsAndArgs, kf ifc.KunstructuredFactory) error {
args := findOrMakeSecretArgs(k, flags.Name, flags.Type)
mergeFlagsIntoGeneratorArgs(&args.GeneratorArgs, flags)
// Validate by trying to create corev1.secret.
_, err := kf.MakeSecret(ldr, k.GeneratorOptions, args)
if err != nil {
return err
}
return nil
}
func findOrMakeSecretArgs(m *types.Kustomization, name, secretType string) *types.SecretArgs {
for i, v := range m.SecretGenerator {
if name == v.Name {
return &m.SecretGenerator[i]
}
}
// secret not found, create new one and add it to the kustomization file.
secret := &types.SecretArgs{
GeneratorArgs: types.GeneratorArgs{Name: name},
Type: secretType}
m.SecretGenerator = append(m.SecretGenerator, *secret)
return &m.SecretGenerator[len(m.SecretGenerator)-1]
}
func mergeFlagsIntoGeneratorArgs(args *types.GeneratorArgs, flags flagsAndArgs) {
if len(flags.LiteralSources) > 0 {
args.LiteralSources = append(
args.LiteralSources, flags.LiteralSources...)
}
if len(flags.FileSources) > 0 {
args.FileSources = append(
args.FileSources, flags.FileSources...)
}
if flags.EnvFileSource != "" {
args.EnvSources = append(
args.EnvSources, flags.EnvFileSource)
}
}

View File

@@ -1,109 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package add
import (
"testing"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/loader"
"sigs.k8s.io/kustomize/v3/pkg/types"
"sigs.k8s.io/kustomize/v3/pkg/validators"
)
func TestNewCmdAddSecretIsNotNil(t *testing.T) {
fSys := fs.MakeFakeFS()
ldr := loader.NewFileLoaderAtCwd(validators.MakeFakeValidator(), fSys)
if newCmdAddSecret(fSys, ldr, nil) == nil {
t.Fatal("newCmdAddSecret shouldn't be nil")
}
}
func TestMakeSecretArgs(t *testing.T) {
secretName := "test-secret-name"
kustomization := &types.Kustomization{
NamePrefix: "test-name-prefix",
}
secretType := "Opaque"
if len(kustomization.SecretGenerator) != 0 {
t.Fatal("Initial kustomization should not have any secrets")
}
args := findOrMakeSecretArgs(kustomization, secretName, secretType)
if args == nil {
t.Fatalf("args should always be non-nil")
}
if len(kustomization.SecretGenerator) != 1 {
t.Fatalf("Kustomization should have newly created secret")
}
if &kustomization.SecretGenerator[len(kustomization.SecretGenerator)-1] != args {
t.Fatalf("Pointer address for newly inserted secret generator should be same")
}
args2 := findOrMakeSecretArgs(kustomization, secretName, secretType)
if args2 != args {
t.Fatalf("should have returned an existing args with name: %v", secretName)
}
if len(kustomization.SecretGenerator) != 1 {
t.Fatalf("Should not insert secret for an existing name: %v", secretName)
}
}
func TestMergeFlagsIntoSecretArgs_LiteralSources(t *testing.T) {
k := &types.Kustomization{}
args := findOrMakeSecretArgs(k, "foo", "forbidden")
mergeFlagsIntoGeneratorArgs(
&args.GeneratorArgs,
flagsAndArgs{LiteralSources: []string{"k1=v1"}})
mergeFlagsIntoGeneratorArgs(
&args.GeneratorArgs,
flagsAndArgs{LiteralSources: []string{"k2=v2"}})
if k.SecretGenerator[0].LiteralSources[0] != "k1=v1" {
t.Fatalf("expected v1")
}
if k.SecretGenerator[0].LiteralSources[1] != "k2=v2" {
t.Fatalf("expected v2")
}
}
func TestMergeFlagsIntoSecretArgs_FileSources(t *testing.T) {
k := &types.Kustomization{}
args := findOrMakeSecretArgs(k, "foo", "forbidden")
mergeFlagsIntoGeneratorArgs(
&args.GeneratorArgs,
flagsAndArgs{FileSources: []string{"file1"}})
mergeFlagsIntoGeneratorArgs(
&args.GeneratorArgs,
flagsAndArgs{FileSources: []string{"file2"}})
if k.SecretGenerator[0].FileSources[0] != "file1" {
t.Fatalf("expected file1")
}
if k.SecretGenerator[0].FileSources[1] != "file2" {
t.Fatalf("expected file2")
}
}
func TestMergeFlagsIntoSecretArgs_EnvSource(t *testing.T) {
k := &types.Kustomization{}
args := findOrMakeSecretArgs(k, "foo", "forbidden")
mergeFlagsIntoGeneratorArgs(
&args.GeneratorArgs,
flagsAndArgs{EnvFileSource: "env1"})
mergeFlagsIntoGeneratorArgs(
&args.GeneratorArgs,
flagsAndArgs{EnvFileSource: "env2"})
if k.SecretGenerator[0].EnvSources[0] != "env1" {
t.Fatalf("expected env1")
}
if k.SecretGenerator[0].EnvSources[1] != "env2" {
t.Fatalf("expected env2")
}
}

View File

@@ -1,44 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package edit
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/commands/edit/add"
"sigs.k8s.io/kustomize/v3/pkg/commands/edit/fix"
"sigs.k8s.io/kustomize/v3/pkg/commands/edit/remove"
"sigs.k8s.io/kustomize/v3/pkg/commands/edit/set"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/ifc"
"sigs.k8s.io/kustomize/v3/pkg/loader"
)
// NewCmdEdit returns an instance of 'edit' subcommand.
func NewCmdEdit(
fSys fs.FileSystem, v ifc.Validator, kf ifc.KunstructuredFactory) *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>
# Sets the namesuffix field
kustomize edit set namesuffix <suffix-value>
`,
Args: cobra.MinimumNArgs(1),
}
c.AddCommand(
add.NewCmdAdd(fSys, loader.NewFileLoaderAtCwd(v, fSys), kf),
set.NewCmdSet(fSys, v),
fix.NewCmdFix(fSys),
remove.NewCmdRemove(fSys, loader.NewFileLoaderAtCwd(v, fSys)),
)
return c
}

View File

@@ -1,43 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package fix
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/fs"
)
// NewCmdFix returns an instance of 'fix' subcommand.
func NewCmdFix(fSys fs.FileSystem) *cobra.Command {
cmd := &cobra.Command{
Use: "fix",
Short: "Fix the missing fields in kustomization file",
Long: "",
Example: `
# Fix the missing and deprecated fields in kustomization file
kustomize edit fix
`,
RunE: func(cmd *cobra.Command, args []string) error {
return RunFix(fSys)
},
}
return cmd
}
// RunFix runs `fix` command
func RunFix(fSys fs.FileSystem) error {
mf, err := kustfile.NewKustomizationFile(fSys)
if err != nil {
return err
}
m, err := mf.Read()
if err != nil {
return err
}
return mf.Write(m)
}

View File

@@ -1,32 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package fix
import (
"strings"
"testing"
"sigs.k8s.io/kustomize/v3/pkg/fs"
)
func TestFix(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomizationWith([]byte(`nameprefix: some-prefix-`))
cmd := NewCmdFix(fakeFS)
err := cmd.RunE(cmd, nil)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadTestKustomization()
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
if !strings.Contains(string(content), "apiVersion: ") {
t.Errorf("expected apiVersion in kustomization")
}
if !strings.Contains(string(content), "kind: Kustomization") {
t.Errorf("expected kind in kustomization")
}
}

View File

@@ -1,43 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package remove
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/ifc"
)
// NewCmdRemove returns an instance of 'remove' subcommand.
func NewCmdRemove(
fsys fs.FileSystem,
ldr ifc.Loader) *cobra.Command {
c := &cobra.Command{
Use: "remove",
Short: "Removes items from the kustomization file.",
Long: "",
Example: `
# Removes resources from the kustomization file
kustomize edit remove resource {filepath} {filepath}
kustomize edit remove resource {pattern}
# Removes one or more patches from the kustomization file
kustomize edit remove patch <filepath>
# Removes one or more commonLabels from the kustomization file
kustomize edit remove label {labelKey1},{labelKey2}
# Removes one or more commonAnnotations from the kustomization file
kustomize edit remove annotation {annotationKey1},{annotationKey2}
`,
Args: cobra.MinimumNArgs(1),
}
c.AddCommand(
newCmdRemoveResource(fsys),
newCmdRemoveLabel(fsys, ldr.Validator().MakeLabelNameValidator()),
newCmdRemoveAnnotation(fsys, ldr.Validator().MakeAnnotationNameValidator()),
newCmdRemovePatch(fsys),
)
return c
}

View File

@@ -1,161 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package remove
import (
"fmt"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/pgmconfig"
"sigs.k8s.io/kustomize/v3/pkg/types"
"strings"
)
// kindOfAdd is the kind of metadata being added: label or annotation
type kindOfAdd int
const (
annotation kindOfAdd = iota
label
)
func (k kindOfAdd) String() string {
kinds := [...]string{
"annotation",
"label",
}
if k < 0 || k > 1 {
return "Unknown metadatakind"
}
return kinds[k]
}
type removeMetadataOptions struct {
ignore bool
metadata []string
arrayValidator func([]string) error
kind kindOfAdd
}
// newCmdRemoveLabel removes one or more commonAnnotations from the kustomization file.
func newCmdRemoveAnnotation(fSys fs.FileSystem, v func([]string) error) *cobra.Command {
var o removeMetadataOptions
o.kind = label
o.arrayValidator = v
cmd := &cobra.Command{
Use: "annotation",
Short: "Removes one or more commonAnnotations from " + pgmconfig.KustomizationFileNames[0],
Example: `
remove annotation {annotationKey1},{annotationKey2}`,
RunE: func(cmd *cobra.Command, args []string) error {
return o.runE(args, fSys, o.removeAnnotations)
},
}
cmd.Flags().BoolVarP(&o.ignore, "ignore-non-existence", "i", false,
"ignore error if the given label doesn't exist",
)
return cmd
}
// newCmdRemoveLabel removes one or more commonLabels from the kustomization file.
func newCmdRemoveLabel(fSys fs.FileSystem, v func([]string) error) *cobra.Command {
var o removeMetadataOptions
o.kind = label
o.arrayValidator = v
cmd := &cobra.Command{
Use: "label",
Short: "Removes one or more commonLabels from " + pgmconfig.KustomizationFileNames[0],
Example: `
remove label {labelKey1},{labelKey2}`,
RunE: func(cmd *cobra.Command, args []string) error {
return o.runE(args, fSys, o.removeLabels)
},
}
cmd.Flags().BoolVarP(&o.ignore, "ignore-non-existence", "i", false,
"ignore error if the given label doesn't exist",
)
return cmd
}
func (o *removeMetadataOptions) runE(
args []string, fSys fs.FileSystem, remover func(*types.Kustomization) error) error {
err := o.validateAndParse(args)
if err != nil {
return err
}
kf, err := kustfile.NewKustomizationFile(fSys)
if err != nil {
return err
}
m, err := kf.Read()
if err != nil {
return err
}
err = remover(m)
if err != nil {
return err
}
return kf.Write(m)
}
// validateAndParse validates `remove` commands and parses them into o.metadata
func (o *removeMetadataOptions) validateAndParse(args []string) error {
if len(args) < 1 {
return fmt.Errorf("must specify %s", o.kind)
}
if len(args) > 1 {
return fmt.Errorf("%ss must be comma-separated, with no spaces", o.kind)
}
m, err := o.convertToArray(args[0])
if err != nil {
return err
}
if err = o.arrayValidator(m); err != nil {
return err
}
o.metadata = m
return nil
}
func (o *removeMetadataOptions) convertToArray(arg string) ([]string, error) {
inputs := strings.Split(arg, ",")
result := make([]string, 0, len(inputs))
for _, input := range inputs {
if len(input) == 0 {
return nil, o.makeError(input, "name is empty")
}
result = append(result, input)
}
return result, nil
}
func (o *removeMetadataOptions) removeAnnotations(m *types.Kustomization) error {
if m.CommonAnnotations == nil && !o.ignore {
return fmt.Errorf("commonAnnotations is not defined in kustomization file")
}
return o.removeFromMap(m.CommonAnnotations, annotation)
}
func (o *removeMetadataOptions) removeLabels(m *types.Kustomization) error {
if m.CommonLabels == nil && !o.ignore {
return fmt.Errorf("commonLabels is not defined in kustomization file")
}
return o.removeFromMap(m.CommonLabels, label)
}
func (o *removeMetadataOptions) removeFromMap(m map[string]string, kind kindOfAdd) error {
for _, k := range o.metadata {
if _, ok := m[k]; !ok && !o.ignore {
return fmt.Errorf("%s %s is not defined in kustomization file", kind, k)
}
delete(m, k)
}
return nil
}
func (o *removeMetadataOptions) makeError(input string, message string) error {
return fmt.Errorf("invalid %s: '%s' (%s)", o.kind, input, message)
}

View File

@@ -1,338 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package remove
import (
"fmt"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/types"
"sigs.k8s.io/kustomize/v3/pkg/validators"
"strings"
"testing"
)
func makeKustomizationFS() fs.FileSystem {
fakeFS := fs.MakeFakeFS()
commonLabels := []string{"label1: val1", "label2: val2"}
commonAnnotations := []string{"annotation1: val1", "annotation2: val2"}
fakeFS.WriteTestKustomizationWith([]byte(
fmt.Sprintf("commonLabels:\n %s\ncommonAnnotations:\n %s",
strings.Join(commonLabels, "\n "), strings.Join(commonAnnotations, "\n "))))
return fakeFS
}
func readKustomizationFS(t *testing.T, fakeFS fs.FileSystem) *types.Kustomization {
kf, err := kustfile.NewKustomizationFile(fakeFS)
if err != nil {
t.Errorf("unexpected new error %v", err)
}
m, err := kf.Read()
if err != nil {
t.Errorf("unexpected read error %v", err)
}
return m
}
func makeKustomization(t *testing.T) *types.Kustomization {
fakeFS := makeKustomizationFS()
return readKustomizationFS(t, fakeFS)
}
func TestRemoveAnnotation(t *testing.T) {
var o removeMetadataOptions
o.metadata = []string{"annotation1"}
m := makeKustomization(t)
err := o.removeAnnotations(m)
if err != nil {
t.Errorf("unexpected error: could not write to kustomization file")
}
// adding the same test input should not work
err = o.removeAnnotations(m)
if err == nil {
t.Errorf("expected not exist in kustomization file error")
}
_, exists := m.CommonAnnotations["annotation1"]
if exists {
t.Errorf("annotation1 must be deleted")
}
_, exists = m.CommonAnnotations["annotation2"]
if !exists {
t.Errorf("annotation2 must exist")
}
}
func TestRemoveAnnotationIgnore(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveAnnotation(fakeFS, v.ValidatorArray)
cmd.Flag("ignore-non-existence").Value.Set("true")
args := []string{"annotation3"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error %v", err)
}
}
func TestRemoveAnnotationNoDefinition(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomizationWith([]byte(""))
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveAnnotation(fakeFS, v.ValidatorArray)
args := []string{"annotation1,annotation2"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "commonAnnotations is not defined in kustomization file" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestRemoveAnnotationNoDefinitionIgnore(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomizationWith([]byte(""))
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveLabel(fakeFS, v.ValidatorArray)
cmd.Flag("ignore-non-existence").Value.Set("true")
args := []string{"annotation1,annotation2"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error %v", err)
}
}
func TestRemoveAnnotationNoArgs(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveAnnotation(fakeFS, v.ValidatorArray)
err := cmd.Execute()
v.VerifyNoCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "must specify label" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestRemoveAnnotationInvalidFormat(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeSadMapValidator(t)
cmd := newCmdRemoveAnnotation(fakeFS, v.ValidatorArray)
args := []string{"nospecialchars%^=@"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != validators.SAD {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestRemoveAnnotationMultipleArgs(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveAnnotation(fakeFS, v.ValidatorArray)
args := []string{"annotation1,annotation2"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error %v", err)
}
m := readKustomizationFS(t, fakeFS)
splitArgs := strings.Split(args[0], ",")
for _, k := range splitArgs {
if _, exist := m.CommonAnnotations[k]; exist {
t.Errorf("%s must be deleted", k)
}
}
}
func TestRemoveAnnotationMultipleArgsInvalidFormat(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeSadMapValidator(t)
cmd := newCmdRemoveAnnotation(fakeFS, v.ValidatorArray)
args := []string{"annotation1", "annotation2"}
err := cmd.RunE(cmd, args)
v.VerifyNoCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "labels must be comma-separated, with no spaces" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestRemoveLabel(t *testing.T) {
var o removeMetadataOptions
o.metadata = []string{"label1"}
m := makeKustomization(t)
err := o.removeLabels(m)
if err != nil {
t.Errorf("unexpected error: could not write to kustomization file")
}
// adding the same test input should not work
err = o.removeLabels(m)
if err == nil {
t.Errorf("expected not exist in kustomization file error")
}
_, exists := m.CommonLabels["label1"]
if exists {
t.Errorf("label1 must be deleted")
}
_, exists = m.CommonLabels["label2"]
if !exists {
t.Errorf("label2 must exist")
}
}
func TestRemoveLabelIgnore(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveLabel(fakeFS, v.ValidatorArray)
cmd.Flag("ignore-non-existence").Value.Set("true")
args := []string{"label3"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error %v", err)
}
}
func TestRemoveLabelNoDefinition(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomizationWith([]byte(""))
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveLabel(fakeFS, v.ValidatorArray)
args := []string{"label1,label2"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "commonLabels is not defined in kustomization file" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestRemoveLabelNoDefinitionIgnore(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomizationWith([]byte(""))
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveLabel(fakeFS, v.ValidatorArray)
cmd.Flag("ignore-non-existence").Value.Set("true")
args := []string{"label1,label2"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error %v", err)
}
}
func TestRemoveLabelNoArgs(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveLabel(fakeFS, v.ValidatorArray)
err := cmd.Execute()
v.VerifyNoCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "must specify label" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestRemoveLabelInvalidFormat(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeSadMapValidator(t)
cmd := newCmdRemoveLabel(fakeFS, v.ValidatorArray)
args := []string{"exclamation!"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != validators.SAD {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestRemoveLabelMultipleArgs(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveLabel(fakeFS, v.ValidatorArray)
args := []string{"label1,label2"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error %v", err)
}
m := readKustomizationFS(t, fakeFS)
splitArgs := strings.Split(args[0], ",")
for _, k := range splitArgs {
if _, exist := m.CommonLabels[k]; exist {
t.Errorf("%s must be deleted", k)
}
}
}
func TestRemoveLabelMultipleArgsInvalidFormat(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeSadMapValidator(t)
cmd := newCmdRemoveLabel(fakeFS, v.ValidatorArray)
args := []string{"label1", "label2"}
err := cmd.RunE(cmd, args)
v.VerifyNoCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "labels must be comma-separated, with no spaces" {
t.Errorf("incorrect error: %v", err.Error())
}
}

View File

@@ -1,91 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package remove
import (
"log"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/commands/util"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/patch"
"sigs.k8s.io/kustomize/v3/pkg/pgmconfig"
)
type removePatchOptions struct {
patchFilePaths []string
}
// newCmdRemovePatch removes the name of a file containing a patch from the kustomization file.
func newCmdRemovePatch(fsys fs.FileSystem) *cobra.Command {
var o removePatchOptions
cmd := &cobra.Command{
Use: "patch",
Short: "Removes one or more patches from " + pgmconfig.KustomizationFileNames[0],
Example: `
remove 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.RunRemovePatch(fsys)
},
}
return cmd
}
// Validate validates removePatch command.
func (o *removePatchOptions) Validate(args []string) error {
if len(args) == 0 {
return errors.New("must specify a patch file")
}
o.patchFilePaths = args
return nil
}
// Complete completes removePatch command.
func (o *removePatchOptions) Complete(cmd *cobra.Command, args []string) error {
return nil
}
// RunRemovePatch runs removePatch command (do real work).
func (o *removePatchOptions) RunRemovePatch(fSys fs.FileSystem) error {
patches, err := util.GlobPatterns(fSys, o.patchFilePaths)
if err != nil {
return err
}
if len(patches) == 0 {
return nil
}
mf, err := kustfile.NewKustomizationFile(fSys)
if err != nil {
return err
}
m, err := mf.Read()
if err != nil {
return err
}
var removePatches []string
for _, p := range patches {
if !patch.Exist(m.PatchesStrategicMerge, p) {
log.Printf("patch %s doesn't exist in kustomization file", p)
continue
}
removePatches = append(removePatches, p)
}
m.PatchesStrategicMerge = patch.Delete(m.PatchesStrategicMerge, removePatches...)
return mf.Write(m)
}

View File

@@ -1,136 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package remove
import (
"fmt"
"strings"
"testing"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/patch"
)
const (
patchFileContent = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
`
)
func makeKustomizationPatchFS() fs.FileSystem {
fakeFS := fs.MakeFakeFS()
patches := []string{"patch1.yaml", "patch2.yaml"}
fakeFS.WriteTestKustomizationWith([]byte(
fmt.Sprintf("patchesStrategicMerge:\n - %s",
strings.Join(patches, "\n - "))))
for _, p := range patches {
fakeFS.WriteFile(p, []byte(patchFileContent))
}
fakeFS.WriteFile("patch3.yaml", []byte(patchFileContent))
return fakeFS
}
func TestRemovePatch(t *testing.T) {
fakeFS := makeKustomizationPatchFS()
cmd := newCmdRemovePatch(fakeFS)
args := []string{"patch1.yaml"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected error %v", err)
}
m := readKustomizationFS(t, fakeFS)
for _, k := range args {
if patch.Exist(m.PatchesStrategicMerge, k) {
t.Errorf("%s must be deleted", k)
}
}
}
func TestRemovePatchMultipleArgs(t *testing.T) {
fakeFS := makeKustomizationPatchFS()
cmd := newCmdRemovePatch(fakeFS)
args := []string{"patch1.yaml", "patch2.yaml"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected error %v", err)
}
m := readKustomizationFS(t, fakeFS)
for _, k := range args {
if patch.Exist(m.PatchesStrategicMerge, k) {
t.Errorf("%s must be deleted", k)
}
}
}
func TestRemovePatchGlob(t *testing.T) {
fakeFS := makeKustomizationPatchFS()
cmd := newCmdRemovePatch(fakeFS)
args := []string{"patch*.yaml"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected error %v", err)
}
m := readKustomizationFS(t, fakeFS)
if len(m.PatchesStrategicMerge) != 0 {
t.Errorf("all patch must be deleted")
}
}
func TestRemovePatchNotDefinedInKustomization(t *testing.T) {
fakeFS := makeKustomizationPatchFS()
cmd := newCmdRemovePatch(fakeFS)
args := []string{"patch3.yaml"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected error %v", err)
}
m := readKustomizationFS(t, fakeFS)
for _, k := range []string{"patch1.yaml", "patch2.yaml"} {
if !patch.Exist(m.PatchesStrategicMerge, k) {
t.Errorf("%s must exist", k)
}
}
}
func TestRemovePatchNotExist(t *testing.T) {
fakeFS := makeKustomizationPatchFS()
cmd := newCmdRemovePatch(fakeFS)
args := []string{"patch4.yaml"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected error %v", err)
}
m := readKustomizationFS(t, fakeFS)
for _, k := range []string{"patch1.yaml", "patch2.yaml"} {
if !patch.Exist(m.PatchesStrategicMerge, k) {
t.Errorf("%s must exist", k)
}
}
}
func TestRemovePatchNoArgs(t *testing.T) {
fakeFS := makeKustomizationPatchFS()
cmd := newCmdRemovePatch(fakeFS)
err := cmd.RunE(cmd, nil)
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "must specify a patch file" {
t.Errorf("incorrect error: %v", err.Error())
}
}

View File

@@ -1,110 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package remove
import (
"errors"
"path/filepath"
"sigs.k8s.io/kustomize/v3/pkg/pgmconfig"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/fs"
)
type removeResourceOptions struct {
resourceFilePaths []string
}
// newCmdRemoveResource remove the name of a file containing a resource to the kustomization file.
func newCmdRemoveResource(fsys fs.FileSystem) *cobra.Command {
var o removeResourceOptions
cmd := &cobra.Command{
Use: "resource",
Short: "Removes one or more resource file paths from " + pgmconfig.KustomizationFileNames[0],
Example: `
remove resource my-resource.yml
remove resource resource1.yml resource2.yml resource3.yml
remove resource resources/*.yml
`,
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.RunRemoveResource(fsys)
},
}
return cmd
}
// Validate validates removeResource command.
func (o *removeResourceOptions) Validate(args []string) error {
if len(args) == 0 {
return errors.New("must specify a resource file")
}
o.resourceFilePaths = args
return nil
}
// Complete completes removeResource command.
func (o *removeResourceOptions) Complete(cmd *cobra.Command, args []string) error {
return nil
}
// RunRemoveResource runs Resource command (do real work).
func (o *removeResourceOptions) RunRemoveResource(fSys fs.FileSystem) error {
mf, err := kustfile.NewKustomizationFile(fSys)
if err != nil {
return err
}
m, err := mf.Read()
if err != nil {
return err
}
resources, err := globPatterns(m.Resources, o.resourceFilePaths)
if err != nil {
return err
}
if len(resources) == 0 {
return nil
}
newResources := make([]string, 0, len(m.Resources))
for _, resource := range m.Resources {
if kustfile.StringInSlice(resource, resources) {
continue
}
newResources = append(newResources, resource)
}
m.Resources = newResources
return mf.Write(m)
}
func globPatterns(resources []string, patterns []string) ([]string, error) {
var result []string
for _, pattern := range patterns {
for _, resource := range resources {
match, err := filepath.Match(pattern, resource)
if err != nil {
return nil, err
}
if !match {
continue
}
result = append(result, resource)
}
}
return result, nil
}

View File

@@ -1,158 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package remove
import (
"errors"
"fmt"
"strings"
"testing"
"sigs.k8s.io/kustomize/v3/pkg/fs"
)
func TestRemoveResources(t *testing.T) {
type given struct {
resources []string
removeArgs []string
}
type expected struct {
resources []string
deleted []string
err error
}
testCases := []struct {
description string
given given
expected expected
}{
{
description: "remove resource",
given: given{
resources: []string{
"resource1.yaml",
"resource2.yaml",
"resource3.yaml",
},
removeArgs: []string{"resource1.yaml"},
},
expected: expected{
resources: []string{
"resource2.yaml",
"resource3.yaml",
},
deleted: []string{
"resource1.yaml",
},
},
},
{
description: "remove resources with pattern",
given: given{
resources: []string{
"foo/resource1.yaml",
"foo/resource2.yaml",
"foo/resource3.yaml",
"do/not/deleteme/please.yaml",
},
removeArgs: []string{"foo/resource*.yaml"},
},
expected: expected{
resources: []string{
"do/not/deleteme/please.yaml",
},
deleted: []string{
"foo/resource1.yaml",
"foo/resource2.yaml",
"foo/resource3.yaml",
},
},
},
{
description: "nothing found to remove",
given: given{
resources: []string{
"resource1.yaml",
"resource2.yaml",
"resource3.yaml",
},
removeArgs: []string{"foo"},
},
expected: expected{
resources: []string{
"resource2.yaml",
"resource3.yaml",
"resource1.yaml",
},
},
},
{
description: "no arguments",
given: given{},
expected: expected{
err: errors.New("must specify a resource file"),
},
},
{
description: "remove with multiple pattern arguments",
given: given{
resources: []string{
"foo/foo.yaml",
"bar/bar.yaml",
"resource3.yaml",
"do/not/deleteme/please.yaml",
},
removeArgs: []string{
"foo/*.*",
"bar/*.*",
"res*.yaml",
},
},
expected: expected{
resources: []string{
"do/not/deleteme/please.yaml",
},
deleted: []string{
"foo/foo.yaml",
"bar/bar.yaml",
"resource3.yaml",
},
},
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomizationWith([]byte(fmt.Sprintf("resources:\n - %s", strings.Join(tc.given.resources, "\n - "))))
cmd := newCmdRemoveResource(fakeFS)
err := cmd.RunE(cmd, tc.given.removeArgs)
if err != nil && tc.expected.err == nil {
t.Errorf("unexpected cmd error: %v", err)
} else if tc.expected.err != nil {
if err.Error() != tc.expected.err.Error() {
t.Errorf("expected error did not occurred. Expected: %v. Actual: %v", tc.expected.err, err)
}
return
}
content, err := fakeFS.ReadTestKustomization()
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
for _, resourceFileName := range tc.expected.resources {
if !strings.Contains(string(content), resourceFileName) {
t.Errorf("expected resource not found in kustomization file.\nResource: %s\nKustomization file:\n%s", resourceFileName, content)
}
}
for _, resourceFileName := range tc.expected.deleted {
if strings.Contains(string(content), resourceFileName) {
t.Errorf("expected deleted resource found in kustomization file. Resource: %s\nKustomization file:\n%s", resourceFileName, content)
}
}
})
}
}

View File

@@ -1,35 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package set
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/ifc"
)
// NewCmdSet returns an instance of 'set' subcommand.
func NewCmdSet(fsys fs.FileSystem, v ifc.Validator) *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>
# Sets the namesuffix field
kustomize edit set namesuffix <suffix-value>
`,
Args: cobra.MinimumNArgs(1),
}
c.AddCommand(
newCmdSetNamePrefix(fsys),
newCmdSetNameSuffix(fsys),
newCmdSetNamespace(fsys, v),
newCmdSetImage(fsys),
)
return c
}

View File

@@ -1,73 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package set
import (
"errors"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/fs"
)
type setNamePrefixOptions struct {
prefix string
}
// newCmdSetNamePrefix sets the value of the namePrefix field in the kustomization.
func newCmdSetNamePrefix(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(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(fSys fs.FileSystem) error {
mf, err := kustfile.NewKustomizationFile(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

@@ -1,47 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package set
import (
"strings"
"testing"
"sigs.k8s.io/kustomize/v3/pkg/fs"
)
const (
goodPrefixValue = "acme-"
)
func TestSetNamePrefixHappyPath(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
cmd := newCmdSetNamePrefix(fakeFS)
args := []string{goodPrefixValue}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadTestKustomization()
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) {
fakeFS := fs.MakeFakeFS()
cmd := newCmdSetNamePrefix(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

@@ -1,73 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package set
import (
"errors"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/fs"
)
type setNameSuffixOptions struct {
suffix string
}
// newCmdSetNameSuffix sets the value of the nameSuffix field in the kustomization.
func newCmdSetNameSuffix(fsys fs.FileSystem) *cobra.Command {
var o setNameSuffixOptions
cmd := &cobra.Command{
Use: "namesuffix",
Short: "Sets the value of the nameSuffix field in the kustomization file.",
Example: `
The command
set namesuffix -- -acme
will add the field "nameSuffix: -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.RunSetNameSuffix(fsys)
},
}
return cmd
}
// Validate validates setNameSuffix command.
func (o *setNameSuffixOptions) Validate(args []string) error {
if len(args) != 1 {
return errors.New("must specify exactly one suffix value")
}
// TODO: add further validation on the value.
o.suffix = args[0]
return nil
}
// Complete completes setNameSuffix command.
func (o *setNameSuffixOptions) Complete(cmd *cobra.Command, args []string) error {
return nil
}
// RunSetNameSuffix runs setNameSuffix command (does real work).
func (o *setNameSuffixOptions) RunSetNameSuffix(fSys fs.FileSystem) error {
mf, err := kustfile.NewKustomizationFile(fSys)
if err != nil {
return err
}
m, err := mf.Read()
if err != nil {
return err
}
m.NameSuffix = o.suffix
return mf.Write(m)
}

View File

@@ -1,47 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package set
import (
"strings"
"testing"
"sigs.k8s.io/kustomize/v3/pkg/fs"
)
const (
goodSuffixValue = "-acme"
)
func TestSetNameSuffixHappyPath(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
cmd := newCmdSetNameSuffix(fakeFS)
args := []string{goodSuffixValue}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadTestKustomization()
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
if !strings.Contains(string(content), goodSuffixValue) {
t.Errorf("expected suffix value in kustomization file")
}
}
func TestSetNameSuffixNoArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
cmd := newCmdSetNameSuffix(fakeFS)
err := cmd.Execute()
if err == nil {
t.Errorf("expected error: %v", err)
}
if err.Error() != "must specify exactly one suffix value" {
t.Errorf("incorrect error: %v", err.Error())
}
}

View File

@@ -1,195 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package set
import (
"errors"
"regexp"
"sort"
"strings"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/image"
)
type setImageOptions struct {
imageMap map[string]image.Image
}
var pattern = regexp.MustCompile("^(.*):([a-zA-Z0-9._-]*)$")
// errors
var (
errImageNoArgs = errors.New("no image specified")
errImageInvalidArgs = errors.New(`invalid format of image, use one of the following options:
- <image>=<newimage>:<newtag>
- <image>=<newimage>@<newtag>
- <image>=<newimage>
- <image>:<newtag>
- <image>@<digest>`)
)
const separator = "="
// newCmdSetImage sets the new names, tags or digests for images in the kustomization.
func newCmdSetImage(fsys fs.FileSystem) *cobra.Command {
var o setImageOptions
cmd := &cobra.Command{
Use: "image",
Short: `Sets images and their new names, new tags or digests in the kustomization file`,
Example: `
The command
set image postgres=eu.gcr.io/my-project/postgres:latest my-app=my-registry/my-app@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3
will add
images:
- name: postgres
newName: eu.gcr.io/my-project/postgres
newTag: latest
- digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3
name: my-app
newName: my-registry/my-app
to the kustomization file if it doesn't exist,
and overwrite the previous ones if the image name exists.
The command
set image node:8.15.0 mysql=mariadb alpine@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3
will add
images:
- name: node
newTag: 8.15.0
- name: mysql
newName: mariadb
- digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3
name: alpine
to the kustomization file if it doesn't exist,
and overwrite the previous ones if the image name exists.
`,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args)
if err != nil {
return err
}
return o.RunSetImage(fsys)
},
}
return cmd
}
type overwrite struct {
name string
digest string
tag string
}
// Validate validates setImage command.
func (o *setImageOptions) Validate(args []string) error {
if len(args) == 0 {
return errImageNoArgs
}
o.imageMap = make(map[string]image.Image)
for _, arg := range args {
img, err := parse(arg)
if err != nil {
return err
}
o.imageMap[img.Name] = img
}
return nil
}
// RunSetImage runs setImage command.
func (o *setImageOptions) RunSetImage(fSys fs.FileSystem) error {
mf, err := kustfile.NewKustomizationFile(fSys)
if err != nil {
return err
}
m, err := mf.Read()
if err != nil {
return err
}
// append only new images from kustomize file
for _, im := range m.Images {
if _, ok := o.imageMap[im.Name]; ok {
continue
}
o.imageMap[im.Name] = im
}
var images []image.Image
for _, v := range o.imageMap {
images = append(images, v)
}
sort.Slice(images, func(i, j int) bool {
return images[i].Name < images[j].Name
})
m.Images = images
return mf.Write(m)
}
func parse(arg string) (image.Image, error) {
// matches if there is an image name to overwrite
// <image>=<new-image><:|@><new-tag>
if s := strings.Split(arg, separator); len(s) == 2 {
p, err := parseOverwrite(s[1], true)
return image.Image{
Name: s[0],
NewName: p.name,
NewTag: p.tag,
Digest: p.digest,
}, err
}
// matches only for <tag|digest> overwrites
// <image><:|@><new-tag>
p, err := parseOverwrite(arg, false)
return image.Image{
Name: p.name,
NewTag: p.tag,
Digest: p.digest,
}, err
}
// parseOverwrite parses the overwrite parameters
// from the given arg into a struct
func parseOverwrite(arg string, overwriteImage bool) (overwrite, error) {
// match <image>@<digest>
if d := strings.Split(arg, "@"); len(d) > 1 {
return overwrite{
name: d[0],
digest: d[1],
}, nil
}
// match <image>:<tag>
if t := pattern.FindStringSubmatch(arg); len(t) == 3 {
return overwrite{
name: t[1],
tag: t[2],
}, nil
}
// match <image>
if len(arg) > 0 && overwriteImage {
return overwrite{
name: arg,
}, nil
}
return overwrite{}, errImageInvalidArgs
}

View File

@@ -1,224 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package set
import (
"fmt"
"strings"
"testing"
"sigs.k8s.io/kustomize/v3/pkg/fs"
)
func TestSetImage(t *testing.T) {
type given struct {
args []string
infileImages []string
}
type expected struct {
fileOutput []string
err error
}
testCases := []struct {
description string
given given
expected expected
}{
{
given: given{
args: []string{"image1=my-image1:my-tag"},
},
expected: expected{
fileOutput: []string{
"images:",
"- name: image1",
" newName: my-image1",
" newTag: my-tag",
}},
},
{
given: given{
args: []string{"image1=my-image1@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3"},
},
expected: expected{
fileOutput: []string{
"images:",
"- digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3",
" name: image1",
" newName: my-image1",
}},
},
{
given: given{
args: []string{"image1:my-tag"},
},
expected: expected{
fileOutput: []string{
"images:",
"- name: image1",
" newTag: my-tag",
}},
},
{
given: given{
args: []string{"image1@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3"},
},
expected: expected{
fileOutput: []string{
"images:",
"- digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3",
" name: image1",
}},
},
{
description: "<image>=<image>",
given: given{
args: []string{"ngnix=localhost:5000/my-project/ngnix"},
},
expected: expected{
fileOutput: []string{
"images:",
"- name: ngnix",
" newName: localhost:5000/my-project/ngnix",
}},
},
{
given: given{
args: []string{"ngnix=localhost:5000/my-project/ngnix:dev-01"},
},
expected: expected{
fileOutput: []string{
"images:",
"- name: ngnix",
" newName: localhost:5000/my-project/ngnix",
" newTag: dev-01",
}},
},
{
description: "override file",
given: given{
args: []string{"image1=foo.bar.foo:8800/foo/image1:foo-bar"},
infileImages: []string{
"images:",
"- name: image1",
" newName: my-image1",
" newTag: my-tag",
"- name: image2",
" newName: my-image2",
" newTag: my-tag2",
},
},
expected: expected{
fileOutput: []string{
"images:",
"- name: image1",
" newName: foo.bar.foo:8800/foo/image1",
" newTag: foo-bar",
"- name: image2",
" newName: my-image2",
" newTag: my-tag2",
}},
},
{
description: "override new tag and new name with just a new tag",
given: given{
args: []string{"image1:v1"},
infileImages: []string{
"images:",
"- name: image1",
" newTag: my-tag",
},
},
expected: expected{
fileOutput: []string{
"images:",
"- name: image1",
" newTag: v1",
}},
},
{
description: "multiple args with multiple overrides",
given: given{
args: []string{
"image1=foo.bar.foo:8800/foo/image1:foo-bar",
"image2=my-image2@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3",
"image3:my-tag",
},
infileImages: []string{
"images:",
"- name: image1",
" newName: my-image1",
" newTag: my-tag1",
"- name: image2",
" newName: my-image2",
" newTag: my-tag2",
"- name: image3",
" newTag: my-tag",
},
},
expected: expected{
fileOutput: []string{
"images:",
"- name: image1",
" newName: foo.bar.foo:8800/foo/image1",
" newTag: foo-bar",
"- digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3",
" name: image2",
" newName: my-image2",
"- name: image3",
" newTag: my-tag",
}},
},
{
description: "error: no args",
expected: expected{
err: errImageNoArgs,
},
},
{
description: "error: invalid args",
given: given{
args: []string{"bad", "args"},
},
expected: expected{
err: errImageInvalidArgs,
},
},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("%s%v", tc.description, tc.given.args), func(t *testing.T) {
// arrange
fakeFS := fs.MakeFakeFS()
cmd := newCmdSetImage(fakeFS)
if len(tc.given.infileImages) > 0 {
// write file with infileImages
fakeFS.WriteTestKustomizationWith([]byte(strings.Join(tc.given.infileImages, "\n")))
} else {
// writes default kustomization file
fakeFS.WriteTestKustomization()
}
// act
err := cmd.RunE(cmd, tc.given.args)
// assert
if err != tc.expected.err {
t.Errorf("Unexpedted error from set image command. Actual: %v\nExpected: %v", err, tc.expected.err)
t.FailNow()
}
content, err := fakeFS.ReadTestKustomization()
if err != nil {
t.Errorf("unexpected read error: %v", err)
t.FailNow()
}
expectedStr := strings.Join(tc.expected.fileOutput, "\n")
if !strings.Contains(string(content), expectedStr) {
t.Errorf("unexpected image in kustomization file. \nActual:\n%s\nExpected:\n%s", content, expectedStr)
}
})
}
}

View File

@@ -1,72 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package set
import (
"errors"
"fmt"
"strings"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/ifc"
)
type setNamespaceOptions struct {
namespace string
validator ifc.Validator
}
// newCmdSetNamespace sets the value of the namespace field in the kustomization.
func newCmdSetNamespace(fsys fs.FileSystem, v ifc.Validator) *cobra.Command {
var o setNamespaceOptions
cmd := &cobra.Command{
Use: "namespace",
Short: "Sets the value of the namespace field in the kustomization file",
Example: `
The command
set namespace staging
will add the field "namespace: staging" to the kustomization file if it doesn't exist,
and overwrite the value with "staging" if the field does exist.
`,
RunE: func(cmd *cobra.Command, args []string) error {
o.validator = v
err := o.Validate(args)
if err != nil {
return err
}
return o.RunSetNamespace(fsys)
},
}
return cmd
}
// Validate validates setNamespace command.
func (o *setNamespaceOptions) Validate(args []string) error {
if len(args) != 1 {
return errors.New("must specify exactly one namespace value")
}
ns := args[0]
if errs := o.validator.ValidateNamespace(ns); len(errs) != 0 {
return fmt.Errorf("%q is not a valid namespace name: %s", ns, strings.Join(errs, ";"))
}
o.namespace = ns
return nil
}
// RunSetNamespace runs setNamespace command (does real work).
func (o *setNamespaceOptions) RunSetNamespace(fSys fs.FileSystem) error {
mf, err := kustfile.NewKustomizationFile(fSys)
if err != nil {
return err
}
m, err := mf.Read()
if err != nil {
return err
}
m.Namespace = o.namespace
return mf.Write(m)
}

View File

@@ -1,89 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package set
import (
"fmt"
"strings"
"testing"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/validators"
)
const (
goodNamespaceValue = "staging"
)
func TestSetNamespaceHappyPath(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
cmd := newCmdSetNamespace(fakeFS, validators.MakeFakeValidator())
args := []string{goodNamespaceValue}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadTestKustomization()
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
expected := []byte(fmt.Sprintf("namespace: %s", goodNamespaceValue))
if !strings.Contains(string(content), string(expected)) {
t.Errorf("expected namespace in kustomization file")
}
}
func TestSetNamespaceOverride(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
cmd := newCmdSetNamespace(fakeFS, validators.MakeFakeValidator())
args := []string{goodNamespaceValue}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
args = []string{"newnamespace"}
err = cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadTestKustomization()
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
expected := []byte("namespace: newnamespace")
if !strings.Contains(string(content), string(expected)) {
t.Errorf("expected namespace in kustomization file %s", string(content))
}
}
func TestSetNamespaceNoArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
cmd := newCmdSetNamespace(fakeFS, validators.MakeFakeValidator())
err := cmd.Execute()
if err == nil {
t.Errorf("expected error: %v", err)
}
if err.Error() != "must specify exactly one namespace value" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestSetNamespaceInvalid(t *testing.T) {
fakeFS := fs.MakeFakeFS()
cmd := newCmdSetNamespace(fakeFS, validators.MakeFakeValidator())
args := []string{"/badnamespace/"}
err := cmd.RunE(cmd, args)
if err == nil {
t.Errorf("expected error: %v", err)
}
if !strings.Contains(err.Error(), "is not a valid namespace name") {
t.Errorf("unexpected error: %v", err.Error())
}
}

View File

@@ -1,294 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package kustfile
import (
"bytes"
"errors"
"fmt"
"io"
"log"
"reflect"
"regexp"
"strings"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/pgmconfig"
"sigs.k8s.io/kustomize/v3/pkg/types"
"sigs.k8s.io/yaml"
)
var fieldMarshallingOrder = determineFieldOrder()
// determineFieldOrder returns a slice of Kustomization field
// names in the preferred order for serialization to a file.
// The field list is checked against the actual struct type
// to confirm that all fields are present, and no unknown
// fields are specified. Deprecated fields are removed from
// the list, meaning they will drop to the bottom on output
// (if present). The ordering and/or deprecation of fields
// in nested structs is not determined or considered.
func determineFieldOrder() []string {
m := make(map[string]bool)
s := reflect.ValueOf(&types.Kustomization{}).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
m[string(typeOfT.Field(i).Name)] = false
}
ordered := []string{
"Resources",
"Bases",
"NamePrefix",
"NameSuffix",
"Namespace",
"Crds",
"CommonLabels",
"CommonAnnotations",
"PatchesStrategicMerge",
"PatchesJson6902",
"Patches",
"ConfigMapGenerator",
"SecretGenerator",
"GeneratorOptions",
"Vars",
"Images",
"Replicas",
"Configurations",
"Generators",
"Transformers",
"Inventory",
}
// Add deprecated fields here.
deprecated := map[string]bool{}
// Account for the inlined TypeMeta fields.
var result []string
result = append(result, "APIVersion", "Kind")
m["TypeMeta"] = true
// Make sure all these fields are recognized.
for _, n := range ordered {
if _, ok := m[n]; ok {
m[n] = true
} else {
log.Fatalf("%s is not a recognized field.", n)
}
// Keep if not deprecated.
if _, f := deprecated[n]; !f {
result = append(result, n)
}
}
return result
}
// commentedField records the comment associated with a kustomization field
// field has to be a recognized kustomization field
// comment can be empty
type commentedField struct {
field string
comment []byte
}
func (cf *commentedField) appendComment(comment []byte) {
cf.comment = append(cf.comment, comment...)
}
func squash(x [][]byte) []byte {
return bytes.Join(x, []byte(``))
}
type kustomizationFile struct {
path string
fSys fs.FileSystem
originalFields []*commentedField
}
// NewKustomizationFile returns a new instance.
func NewKustomizationFile(fSys fs.FileSystem) (*kustomizationFile, error) { // nolint
mf := &kustomizationFile{fSys: fSys}
err := mf.validate()
if err != nil {
return nil, err
}
return mf, nil
}
func (mf *kustomizationFile) validate() error {
match := 0
var path []string
for _, kfilename := range pgmconfig.KustomizationFileNames {
if mf.fSys.Exists(kfilename) {
match += 1
path = append(path, kfilename)
}
}
switch match {
case 0:
return fmt.Errorf("Missing kustomization file '%s'.\n", pgmconfig.KustomizationFileNames[0])
case 1:
mf.path = path[0]
default:
return fmt.Errorf("Found multiple kustomization file: %v\n", path)
}
if mf.fSys.IsDir(mf.path) {
return fmt.Errorf("%s should be a file", mf.path)
}
return nil
}
func (mf *kustomizationFile) Read() (*types.Kustomization, error) {
data, err := mf.fSys.ReadFile(mf.path)
if err != nil {
return nil, err
}
data = types.FixKustomizationPreUnmarshalling(data)
var k types.Kustomization
err = yaml.Unmarshal(data, &k)
if err != nil {
return nil, err
}
k.FixKustomizationPostUnmarshalling()
err = mf.parseCommentedFields(data)
if err != nil {
return nil, err
}
return &k, err
}
func (mf *kustomizationFile) Write(kustomization *types.Kustomization) error {
if kustomization == nil {
return errors.New("util: kustomization file arg is nil")
}
data, err := mf.marshal(kustomization)
if err != nil {
return err
}
return mf.fSys.WriteFile(mf.path, data)
}
// StringInSlice returns true if the string is in the slice.
func StringInSlice(str string, list []string) bool {
for _, v := range list {
if v == str {
return true
}
}
return false
}
func (mf *kustomizationFile) parseCommentedFields(content []byte) error {
buffer := bytes.NewBuffer(content)
var comments [][]byte
line, err := buffer.ReadBytes('\n')
for err == nil {
if isCommentOrBlankLine(line) {
comments = append(comments, line)
} else {
matched, field := findMatchedField(line)
if matched {
mf.originalFields = append(mf.originalFields, &commentedField{field: field, comment: squash(comments)})
comments = [][]byte{}
} else if len(comments) > 0 {
mf.originalFields[len(mf.originalFields)-1].appendComment(squash(comments))
comments = [][]byte{}
}
}
line, err = buffer.ReadBytes('\n')
}
if err != io.EOF {
return err
}
return nil
}
// marshal converts a kustomization to a byte stream.
func (mf *kustomizationFile) marshal(kustomization *types.Kustomization) ([]byte, error) {
var output []byte
for _, comment := range mf.originalFields {
output = append(output, comment.comment...)
content, err := marshalField(comment.field, kustomization)
if err != nil {
return content, err
}
output = append(output, content...)
}
for _, field := range fieldMarshallingOrder {
if mf.hasField(field) {
continue
}
content, err := marshalField(field, kustomization)
if err != nil {
return content, nil
}
output = append(output, content...)
}
return output, nil
}
func (mf *kustomizationFile) hasField(name string) bool {
for _, n := range mf.originalFields {
if n.field == name {
return true
}
}
return false
}
/*
isCommentOrBlankLine determines if a line is a comment or blank line
Return true for following lines
# This line is a comment
# This line is also a comment with several leading white spaces
(The line above is a blank line)
*/
func isCommentOrBlankLine(line []byte) bool {
s := bytes.TrimRight(bytes.TrimLeft(line, " "), "\n")
return len(s) == 0 || bytes.HasPrefix(s, []byte(`#`))
}
func findMatchedField(line []byte) (bool, string) {
for _, field := range fieldMarshallingOrder {
// (?i) is for case insensitive regexp matching
r := regexp.MustCompile("^(" + "(?i)" + field + "):")
if r.Match(line) {
return true, field
}
}
return false, ""
}
// marshalField marshal a given field of a kustomization object into yaml format.
// If the field wasn't in the original kustomization.yaml file or wasn't added,
// an empty []byte is returned.
func marshalField(field string, kustomization *types.Kustomization) ([]byte, error) {
r := reflect.ValueOf(*kustomization)
v := r.FieldByName(strings.Title(field))
if !v.IsValid() || isEmpty(v) {
return []byte{}, nil
}
k := &types.Kustomization{}
kr := reflect.ValueOf(k)
kv := kr.Elem().FieldByName(strings.Title(field))
kv.Set(v)
return yaml.Marshal(k)
}
func isEmpty(v reflect.Value) bool {
// If v is a pointer type
if v.Type().Kind() == reflect.Ptr {
return v.IsNil()
}
return v.Len() == 0
}

View File

@@ -1,337 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package kustfile
import (
"reflect"
"strings"
"testing"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/pgmconfig"
"sigs.k8s.io/kustomize/v3/pkg/types"
)
func TestFieldOrder(t *testing.T) {
expected := []string{
"APIVersion",
"Kind",
"Resources",
"Bases",
"NamePrefix",
"NameSuffix",
"Namespace",
"Crds",
"CommonLabels",
"CommonAnnotations",
"PatchesStrategicMerge",
"PatchesJson6902",
"Patches",
"ConfigMapGenerator",
"SecretGenerator",
"GeneratorOptions",
"Vars",
"Images",
"Replicas",
"Configurations",
"Generators",
"Transformers",
"Inventory",
}
actual := determineFieldOrder()
if len(expected) != len(actual) {
t.Fatalf("Incorrect field count.")
}
for i, n := range expected {
if n != actual[i] {
t.Fatalf("Bad field order.")
}
}
}
func TestWriteAndRead(t *testing.T) {
kustomization := &types.Kustomization{
NamePrefix: "prefix",
}
fSys := fs.MakeFakeFS()
fSys.WriteTestKustomization()
mf, err := NewKustomizationFile(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)
}
kustomization.FixKustomizationPostUnmarshalling()
if !reflect.DeepEqual(kustomization, content) {
t.Fatal("Read kustomization is different from written kustomization")
}
}
func TestNewNotExist(t *testing.T) {
fakeFS := fs.MakeFakeFS()
_, err := NewKustomizationFile(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(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)
}
}
func TestSecondarySuffix(t *testing.T) {
kcontent := `
configMapGenerator:
- literals:
- foo=bar
- baz=qux
name: my-configmap
`
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(pgmconfig.KustomizationFileNames[1], []byte(kcontent))
k, err := NewKustomizationFile(fakeFS)
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
if k.path != pgmconfig.KustomizationFileNames[1] {
t.Fatalf("Load incorrect file path %s", k.path)
}
}
func TestPreserveComments(t *testing.T) {
kustomizationContentWithComments := []byte(
`# shem qing some comments
# This is some comment we should preserve
# don't delete it
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../namespaces
- pod.yaml
- service.yaml
# something you may want to keep
vars:
- fieldref:
fieldPath: metadata.name
name: MY_SERVICE_NAME
objref:
apiVersion: v1
kind: Service
name: my-service
# some descriptions for the patches
patchesStrategicMerge:
- service.yaml
- pod.yaml
`)
fSys := fs.MakeFakeFS()
fSys.WriteTestKustomizationWith(kustomizationContentWithComments)
mf, err := NewKustomizationFile(fSys)
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
kustomization, err := mf.Read()
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
if err = mf.Write(kustomization); err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
bytes, _ := fSys.ReadFile(mf.path)
if !reflect.DeepEqual(kustomizationContentWithComments, bytes) {
t.Fatal("written kustomization with comments is not the same as original one")
}
}
func TestPreserveCommentsWithAdjust(t *testing.T) {
kustomizationContentWithComments := []byte(`
# Some comments
# This is some comment we should preserve
# don't delete it
RESOURCES:
- ../namespaces
- pod.yaml
# See which field this comment goes into
- service.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: kustomization
# something you may want to keep
vars:
- fieldref:
fieldPath: metadata.name
name: MY_SERVICE_NAME
objref:
apiVersion: v1
kind: Service
name: my-service
# some descriptions for the patches
patchesStrategicMerge:
- service.yaml
- pod.yaml
# generator options
generatorOptions:
disableNameSuffixHash: true
`)
expected := []byte(`
# Some comments
# This is some comment we should preserve
# don't delete it
# See which field this comment goes into
resources:
- ../namespaces
- pod.yaml
- service.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: kustomization
# something you may want to keep
vars:
- fieldref:
fieldPath: metadata.name
name: MY_SERVICE_NAME
objref:
apiVersion: v1
kind: Service
name: my-service
# some descriptions for the patches
patchesStrategicMerge:
- service.yaml
- pod.yaml
# generator options
generatorOptions:
disableNameSuffixHash: true
`)
fSys := fs.MakeFakeFS()
fSys.WriteTestKustomizationWith(kustomizationContentWithComments)
mf, err := NewKustomizationFile(fSys)
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
kustomization, err := mf.Read()
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
if err = mf.Write(kustomization); err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
bytes, _ := fSys.ReadFile(mf.path)
if string(expected) != string(bytes) {
t.Fatalf(
"expected =\n%s\n\nactual =\n%s\n",
string(expected), string(bytes))
}
}
func TestFixPatchesField(t *testing.T) {
kustomizationContentWithComments := []byte(`
patches:
- patch1.yaml
- patch2.yaml
`)
expected := []byte(`
patchesStrategicMerge:
- patch1.yaml
- patch2.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
`)
fSys := fs.MakeFakeFS()
fSys.WriteTestKustomizationWith(kustomizationContentWithComments)
mf, err := NewKustomizationFile(fSys)
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
kustomization, err := mf.Read()
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
if err = mf.Write(kustomization); err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
bytes, _ := fSys.ReadFile(mf.path)
if string(expected) != string(bytes) {
t.Fatalf(
"expected =\n%s\n\nactual =\n%s\n",
string(expected), string(bytes))
}
}
func TestFixPatchesFieldForExtendedPatch(t *testing.T) {
kustomizationContentWithComments := []byte(`
patches:
- path: patch1.yaml
target:
kind: Deployment
- path: patch2.yaml
target:
kind: Service
`)
expected := []byte(`
patches:
- path: patch1.yaml
target:
kind: Deployment
- path: patch2.yaml
target:
kind: Service
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
`)
fSys := fs.MakeFakeFS()
fSys.WriteTestKustomizationWith(kustomizationContentWithComments)
mf, err := NewKustomizationFile(fSys)
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
kustomization, err := mf.Read()
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
if err = mf.Write(kustomization); err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
bytes, _ := fSys.ReadFile(mf.path)
if string(expected) != string(bytes) {
t.Fatalf(
"expected =\n%s\n\nactual =\n%s\n",
string(expected), string(bytes))
}
}

View File

@@ -1,101 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package misc
import (
"fmt"
"path/filepath"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/transformers/config/defaultconfig"
)
// NewCmdConfig returns an instance of 'config' subcommand.
func NewCmdConfig(fsys fs.FileSystem) *cobra.Command {
c := &cobra.Command{
Use: "config",
Short: "Config Kustomize transformers",
Long: "",
Example: `
# Save the default transformer configurations to a local directory
kustomize config save -d ~/.kustomize/config
`,
Args: cobra.MinimumNArgs(1),
}
c.AddCommand(
newCmdSave(fsys),
)
return c
}
type saveOptions struct {
saveDirectory string
}
func newCmdSave(fsys fs.FileSystem) *cobra.Command {
var o saveOptions
c := &cobra.Command{
Use: "save",
Short: "Save default kustomize transformer configurations to a local directory",
Long: "",
Example: `
# Save the default transformer configurations to a local directory
save -d ~/.kustomize/config
`,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate()
if err != nil {
return err
}
err = o.Complete(fsys)
if err != nil {
return err
}
return o.RunSave(fsys)
},
}
c.Flags().StringVarP(
&o.saveDirectory,
"directory", "d", "",
"Directory to save the default transformer configurations")
return c
}
// Validate validates the saveOptions is not empty
func (o *saveOptions) Validate() error {
if o.saveDirectory == "" {
return fmt.Errorf("must specify one local directory to save the default transformer configurations")
}
return nil
}
// Complete creates the save directory when the directory doesn't exist
func (o *saveOptions) Complete(fsys fs.FileSystem) error {
if !fsys.Exists(o.saveDirectory) {
return fsys.MkdirAll(o.saveDirectory)
}
if fsys.IsDir(o.saveDirectory) {
return nil
}
return fmt.Errorf("%s is not a directory", o.saveDirectory)
}
// RunSave saves the default transformer configurations local directory
func (o *saveOptions) RunSave(fsys fs.FileSystem) error {
m := defaultconfig.GetDefaultFieldSpecsAsMap()
for tname, tcfg := range m {
filename := filepath.Join(o.saveDirectory, tname+".yaml")
err := fsys.WriteFile(filename, []byte(tcfg))
if err != nil {
return err
}
}
return nil
}

View File

@@ -1,74 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package misc
import (
"fmt"
"reflect"
"strings"
"testing"
"sigs.k8s.io/kustomize/v3/pkg/fs"
)
func TestValidate(t *testing.T) {
o := saveOptions{
saveDirectory: "",
}
err := o.Validate()
if !strings.Contains(err.Error(), "must specify one local directory") {
t.Fatalf("Incorrect error %v", err)
}
o.saveDirectory = "/some/dir"
err = o.Validate()
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
}
func TestComplete(t *testing.T) {
fsys := fs.MakeFakeFS()
fsys.Mkdir("/some/dir")
fsys.WriteFile("/some/file", []byte(`some file`))
type testcase struct {
dir string
expect error
}
testcases := []testcase{
{
dir: "/some/dir",
expect: nil,
},
{
dir: "/some/dir/not/existing",
expect: nil,
},
{
dir: "/some/file",
expect: fmt.Errorf("%s is not a directory", "/some/file"),
},
}
for _, tcase := range testcases {
o := saveOptions{saveDirectory: tcase.dir}
actual := o.Complete(fsys)
if !reflect.DeepEqual(actual, tcase.expect) {
t.Fatalf("Expected %v\n but bot %v\n", tcase.expect, actual)
}
}
}
func TestRunSave(t *testing.T) {
fsys := fs.MakeFakeFS()
o := saveOptions{saveDirectory: "/some/dir"}
err := o.RunSave(fsys)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if !fsys.Exists("/some/dir/nameprefix.yaml") {
t.Fatal("default configurations are not successfully save.")
}
}

View File

@@ -1,73 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package misc
import (
"fmt"
"io"
"runtime"
"github.com/spf13/cobra"
)
var (
kustomizeVersion = "unknown"
goos = runtime.GOOS
goarch = runtime.GOARCH
gitCommit = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD)
buildDate = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
)
// version returns the version of kustomize.
type version struct {
// KustomizeVersion is a kustomize binary version.
KustomizeVersion string `json:"kustomizeVersion"`
// GitCommit is a git commit
GitCommit string `json:"gitCommit"`
// BuildDate is a build date of the binary.
BuildDate string `json:"buildDate"`
// GoOs holds OS name.
GoOs string `json:"goOs"`
// GoArch holds architecture name.
GoArch string `json:"goArch"`
}
// getVersion returns version.
func getVersion() version {
return version{
kustomizeVersion,
gitCommit,
buildDate,
goos,
goarch,
}
}
// Print prints version.
func (v version) Print(w io.Writer, short bool) {
if short {
fmt.Fprintf(w, "%s\n", v.KustomizeVersion)
} else {
fmt.Fprintf(w, "Version: %+v\n", v)
}
}
// NewCmdVersion makes version command.
func NewCmdVersion(w io.Writer) *cobra.Command {
var short bool
versionCmd := cobra.Command{
Use: "version",
Short: "Prints the kustomize version",
Example: `kustomize version`,
Run: func(cmd *cobra.Command, args []string) {
getVersion().Print(w, short)
},
}
versionCmd.Flags().BoolVar(&short, "short", false, "print just the version number")
return &versionCmd
}

View File

@@ -1,65 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package util
import (
"fmt"
"log"
"strings"
"sigs.k8s.io/kustomize/v3/pkg/fs"
)
// GlobPatterns accepts a slice of glob strings and returns the set of
// matching file paths.
func GlobPatterns(fsys fs.FileSystem, patterns []string) ([]string, error) {
var result []string
for _, pattern := range patterns {
files, err := fsys.Glob(pattern)
if err != nil {
return nil, err
}
if len(files) == 0 {
log.Printf("%s has no match", pattern)
continue
}
result = append(result, files...)
}
return result, nil
}
// ConvertToMap converts a slice of strings in the form of
// `key:value` into a map.
func ConvertToMap(input string, kind string) (map[string]string, error) {
result := make(map[string]string)
if input == "" {
return result, nil
}
inputs := strings.Split(input, ",")
for _, input := range inputs {
c := strings.Index(input, ":")
if c == 0 {
// key is not passed
return nil, fmt.Errorf("invalid %s: '%s' (%s)", kind, input, "need k:v pair where v may be quoted")
} else if c < 0 {
// only key passed
result[input] = ""
} else {
// both key and value passed
key := input[:c]
value := trimQuotes(input[c+1:])
result[key] = value
}
}
return result, nil
}
func trimQuotes(s string) string {
if len(s) >= 2 {
if s[0] == '"' && s[len(s)-1] == '"' {
return s[1 : len(s)-1]
}
}
return s
}

View File

@@ -1,37 +0,0 @@
package util
import (
"reflect"
"testing"
)
func TestConvertToMap(t *testing.T) {
args := "a:b,c:\"d\",e:\"f:g\",g:h:k"
expected := make(map[string]string)
expected["a"] = "b"
expected["c"] = "d"
expected["e"] = "f:g"
expected["g"] = "h:k"
result, err := ConvertToMap(args, "annotation")
if err != nil {
t.Errorf("unexpected error: %v", err.Error())
}
eq := reflect.DeepEqual(expected, result)
if !eq {
t.Errorf("Converted map does not match expected, expected: %v, result: %v\n", expected, result)
}
}
func TestConvertToMapError(t *testing.T) {
args := "a:b,c:\"d\",:f:g"
_, err := ConvertToMap(args, "annotation")
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "invalid annotation: ':f:g' (need k:v pair where v may be quoted)" {
t.Errorf("incorrect error: %v", err.Error())
}
}