Skip to content

Commit

Permalink
Add create subcommand
Browse files Browse the repository at this point in the history
  • Loading branch information
richardmarshall committed Jul 30, 2019
1 parent e904f61 commit 5dfa929
Show file tree
Hide file tree
Showing 6 changed files with 450 additions and 0 deletions.
2 changes: 2 additions & 0 deletions pkg/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"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"
Expand Down Expand Up @@ -44,6 +45,7 @@ See https://sigs.k8s.io/kustomize
stdOut, fSys, v,
rf, pf),
edit.NewCmdEdit(fSys, v, uf),
create.NewCmdCreate(fSys),
misc.NewCmdConfig(fSys),
misc.NewCmdVersion(stdOut),
)
Expand Down
181 changes: 181 additions & 0 deletions pkg/commands/create/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package create

import (
"fmt"
"os"
"path/filepath"

"github.com/spf13/cobra"

"sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct"
"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"
)

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) *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 --resource ../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 --resource depoyment.yaml --resource service.yaml --namespace staging --nameprefix acme-
`,
RunE: func(cmd *cobra.Command, args []string) error {
return runCreate(opts, fSys)
},
}
c.Flags().StringSliceVar(
&opts.resources,
"resource",
[]string{},
"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,
"annotation",
"",
"Add one or more common annotations.")
c.Flags().StringVar(
&opts.labels,
"label",
"",
"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) error {
resources, err := util.GlobPatterns(fSys, 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, 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, base string, recursive bool) ([]string, error) {
var paths []string
factory := kunstruct.NewKunstructuredFactoryImpl()
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 := factory.SliceFromBytes(fContents); err != nil {
return nil
}
paths = append(paths, path)
return nil
})
return paths, err
}
179 changes: 179 additions & 0 deletions pkg/commands/create/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package create

import (
"reflect"
"testing"

"sigs.k8s.io/kustomize/v3/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/types"
)

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)
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(""))
opts := createFlags{resources: []string{"foo.yaml"}}
err := runCreate(opts, fakeFS)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
m := readKustomizationFS(t, fakeFS)
expected := []string{"foo.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)
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)
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)
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)
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)
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.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.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)
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)
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)
}
}

0 comments on commit 5dfa929

Please sign in to comment.