Improve command package isolation.

This commit is contained in:
Jeffrey Regan
2018-10-03 15:04:57 -07:00
parent f5fee4decf
commit bb9fafa6cc
104 changed files with 301 additions and 212 deletions

View File

@@ -0,0 +1,44 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package set
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/pkg/fs"
"sigs.k8s.io/kustomize/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>
`,
Args: cobra.MinimumNArgs(1),
}
c.AddCommand(
newCmdSetNamePrefix(fsys),
newCmdSetNamespace(fsys, v),
newCmdSetImageTag(fsys),
)
return c
}

View File

@@ -0,0 +1,87 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package set
import (
"errors"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/pkg/constants"
"sigs.k8s.io/kustomize/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(constants.KustomizationFileName, fsys)
if err != nil {
return err
}
m, err := mf.Read()
if err != nil {
return err
}
m.NamePrefix = o.prefix
return mf.Write(m)
}

View File

@@ -0,0 +1,61 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package set
import (
"strings"
"testing"
"sigs.k8s.io/kustomize/pkg/constants"
"sigs.k8s.io/kustomize/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.ReadFile(constants.KustomizationFileName)
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
if !strings.Contains(string(content), goodPrefixValue) {
t.Errorf("expected prefix value in kustomization file")
}
}
func TestSetNamePrefixNoArgs(t *testing.T) {
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

@@ -0,0 +1,131 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package set
import (
"errors"
"regexp"
"sort"
"strings"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/pkg/constants"
"sigs.k8s.io/kustomize/pkg/fs"
"sigs.k8s.io/kustomize/pkg/types"
)
type setImageTagOptions struct {
imageTagMap map[string]types.ImageTag
}
var pattern = regexp.MustCompile("^(.*):([a-zA-Z0-9._-]*)$")
// newCmdSetImageTag sets the new tags for images in the kustomization.
func newCmdSetImageTag(fsys fs.FileSystem) *cobra.Command {
var o setImageTagOptions
cmd := &cobra.Command{
Use: "imagetag",
Short: "Sets images and their new tags or digests in the kustomization file",
Example: `
The command
set imagetag nginx:1.8.0 my-app:latest alpine@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3
will add
imageTags:
- name: nginx
newTag: 1.8.0
- name: my-app
newTag: latest
- name: alpine
digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3
to the kustomization file if it doesn't exist,
and overwrite the previous ones if the image tag exists.
`,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args)
if err != nil {
return err
}
return o.RunSetImageTags(fsys)
},
}
return cmd
}
// Validate validates setImageTag command.
func (o *setImageTagOptions) Validate(args []string) error {
if len(args) == 0 {
return errors.New("no image specified")
}
o.imageTagMap = make(map[string]types.ImageTag)
for _, arg := range args {
if s := strings.Split(arg, "@"); len(s) > 1 {
o.imageTagMap[s[0]] = types.ImageTag{
Name: s[0],
Digest: s[1],
}
continue
}
s := pattern.FindStringSubmatch(arg)
if len(s) != 3 {
return errors.New("invalid format of imagetag, must specify it as <image>:<newtag> or <image>@<digest>")
}
o.imageTagMap[s[1]] = types.ImageTag{
Name: s[1],
NewTag: s[2],
}
}
return nil
}
// RunSetImageTags runs setImageTags command (does real work).
func (o *setImageTagOptions) RunSetImageTags(fsys fs.FileSystem) error {
mf, err := kustfile.NewKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
return err
}
m, err := mf.Read()
if err != nil {
return err
}
for _, it := range m.ImageTags {
if _, ok := o.imageTagMap[it.Name]; ok {
continue
}
o.imageTagMap[it.Name] = it
}
var imageTags []types.ImageTag
for _, v := range o.imageTagMap {
imageTags = append(imageTags, v)
}
sort.Slice(imageTags, func(i, j int) bool {
return imageTags[i].Name < imageTags[j].Name
})
m.ImageTags = imageTags
return mf.Write(m)
}

View File

@@ -0,0 +1,102 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package set
import (
"strings"
"testing"
"sigs.k8s.io/kustomize/pkg/constants"
"sigs.k8s.io/kustomize/pkg/fs"
)
func TestSetImageTagsHappyPath(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
cmd := newCmdSetImageTag(fakeFS)
args := []string{"image1:tag1", "image2:tag2", "localhost:5000/operator:1.0.0",
"foo.bar.baz:5000/one/two@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadFile(constants.KustomizationFileName)
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
expected := []byte(`
imageTags:
- digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3
name: foo.bar.baz:5000/one/two
- name: image1
newTag: tag1
- name: image2
newTag: tag2
- name: localhost:5000/operator
newTag: 1.0.0
`)
if !strings.Contains(string(content), string(expected)) {
t.Errorf("expected imageTags in kustomization file")
}
}
func TestSetImageTagsOverride(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomization()
cmd := newCmdSetImageTag(fakeFS)
args := []string{"image1:tag1", "image2:tag1"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
args = []string{"image2:tag2", "image3:tag3"}
err = cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadFile(constants.KustomizationFileName)
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
expected := []byte(`
imageTags:
- name: image1
newTag: tag1
- name: image2
newTag: tag2
- name: image3
newTag: tag3
`)
if !strings.Contains(string(content), string(expected)) {
t.Errorf("expected imageTags in kustomization file %s", string(content))
}
}
func TestSetImageTagsNoArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
cmd := newCmdSetImageTag(fakeFS)
err := cmd.Execute()
if err == nil {
t.Errorf("expected error: %v", err)
}
if err.Error() != "no image specified" {
t.Errorf("incorrect error: %v", err.Error())
}
}

View File

@@ -0,0 +1,86 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package set
import (
"errors"
"fmt"
"strings"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/pkg/constants"
"sigs.k8s.io/kustomize/pkg/fs"
"sigs.k8s.io/kustomize/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(constants.KustomizationFileName, 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

@@ -0,0 +1,103 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package set
import (
"fmt"
"strings"
"testing"
"sigs.k8s.io/kustomize/pkg/constants"
"sigs.k8s.io/kustomize/pkg/fs"
"sigs.k8s.io/kustomize/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.ReadFile(constants.KustomizationFileName)
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.ReadFile(constants.KustomizationFileName)
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())
}
}