mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-29 17:41:13 +00:00
Merge remote-tracking branch 'upstream/master' into release-cmd/config-v0.0
This commit is contained in:
@@ -45,6 +45,7 @@ Advanced Documentation Topics:
|
||||
|
||||
// Export commands publicly for composition
|
||||
var (
|
||||
Annotate = commands.AnnotateCommand
|
||||
Cat = commands.CatCommand
|
||||
Count = commands.CountCommand
|
||||
CreateSetter = commands.CreateSetterCommand
|
||||
@@ -58,6 +59,8 @@ var (
|
||||
Sink = commands.SinkCommand
|
||||
Source = commands.SourceCommand
|
||||
Tree = commands.TreeCommand
|
||||
Wrap = commands.WrapCommand
|
||||
XArgs = commands.XArgsCommand
|
||||
|
||||
StackOnError = &commands.StackOnError
|
||||
ExitOnError = &commands.ExitOnError
|
||||
@@ -89,6 +92,7 @@ func NewConfigCommand(name string) *cobra.Command {
|
||||
|
||||
name = strings.TrimSpace(name + " config")
|
||||
commands.ExitOnError = true
|
||||
root.AddCommand(commands.AnnotateCommand(name))
|
||||
root.AddCommand(commands.GrepCommand(name))
|
||||
root.AddCommand(commands.TreeCommand(name))
|
||||
root.AddCommand(commands.CatCommand(name))
|
||||
|
||||
18
cmd/config/docs/commands/annotate.md
Normal file
18
cmd/config/docs/commands/annotate.md
Normal file
@@ -0,0 +1,18 @@
|
||||
## annotate
|
||||
|
||||
[Alpha] Set an annotation on Resources.
|
||||
|
||||
### Synopsis
|
||||
|
||||
[Alpha] Set an annotation on Resources.
|
||||
|
||||
DIR:
|
||||
Path to local directory.
|
||||
|
||||
### Examples
|
||||
|
||||
kustomize config annotate my-dir/ --kv foo=bar
|
||||
|
||||
kustomize config annotate my-dir/ --kv foo=bar --kv a=b
|
||||
|
||||
kustomize config annotate my-dir/ --kv foo=bar --kind Deployment --name foo
|
||||
103
cmd/config/internal/commands/annotate.go
Normal file
103
cmd/config/internal/commands/annotate.go
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
|
||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// NewAnnotateRunner returns a command runner.
|
||||
func NewAnnotateRunner(parent string) *AnnotateRunner {
|
||||
r := &AnnotateRunner{}
|
||||
c := &cobra.Command{
|
||||
Use: "annotate [DIR]",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Short: commands.AnnotateShort,
|
||||
Long: commands.AnnotateLong,
|
||||
Example: commands.AnnotateExamples,
|
||||
RunE: r.runE,
|
||||
}
|
||||
fixDocs(parent, c)
|
||||
r.Command = c
|
||||
c.Flags().StringVar(&r.Kind, "kind", "", "Resource kind to annotate")
|
||||
c.Flags().StringVar(&r.ApiVersion, "apiVersion", "", "Resource apiVersion to annotate")
|
||||
c.Flags().StringVar(&r.Name, "name", "", "Resource name to annotate")
|
||||
c.Flags().StringVar(&r.Namespace, "namespace", "", "Resource namespace to annotate")
|
||||
c.Flags().StringSliceVar(&r.Values, "kv", []string{}, "annotation as KEY=VALUE")
|
||||
return r
|
||||
}
|
||||
|
||||
func AnnotateCommand(parent string) *cobra.Command {
|
||||
return NewAnnotateRunner(parent).Command
|
||||
}
|
||||
|
||||
type AnnotateRunner struct {
|
||||
Command *cobra.Command
|
||||
Values []string
|
||||
Kind string
|
||||
Name string
|
||||
ApiVersion string
|
||||
Namespace string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (r *AnnotateRunner) runE(c *cobra.Command, args []string) error {
|
||||
var input []kio.Reader
|
||||
var output []kio.Writer
|
||||
if len(args) == 0 {
|
||||
rw := &kio.ByteReadWriter{Reader: c.InOrStdin(), Writer: c.OutOrStdout()}
|
||||
input = []kio.Reader{rw}
|
||||
output = []kio.Writer{rw}
|
||||
} else {
|
||||
rw := &kio.LocalPackageReadWriter{PackagePath: args[0], NoDeleteFiles: true}
|
||||
input = []kio.Reader{rw}
|
||||
output = []kio.Writer{rw}
|
||||
}
|
||||
return handleError(c, kio.Pipeline{
|
||||
Inputs: input,
|
||||
Filters: []kio.Filter{r},
|
||||
Outputs: output,
|
||||
}.Execute())
|
||||
}
|
||||
|
||||
func (r *AnnotateRunner) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
for i := range nodes {
|
||||
n := nodes[i]
|
||||
m, err := n.GetMeta()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.Kind != "" && r.Kind != m.Kind {
|
||||
continue
|
||||
}
|
||||
if r.ApiVersion != "" && r.ApiVersion != m.APIVersion {
|
||||
continue
|
||||
}
|
||||
if r.Namespace != "" && r.Namespace != m.Namespace {
|
||||
continue
|
||||
}
|
||||
if r.Name != "" && r.Name != m.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
for i := range r.Values {
|
||||
// split key, value pairs
|
||||
kv := strings.SplitN(r.Values[i], "=", 2)
|
||||
if len(kv) != 2 {
|
||||
return nil, errors.Errorf("must specify --kv as KEY=VALUE: %s", r.Values[i])
|
||||
}
|
||||
if err := n.PipeE(yaml.SetAnnotation(kv[0], kv[1])); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
488
cmd/config/internal/commands/annotate_test.go
Normal file
488
cmd/config/internal/commands/annotate_test.go
Normal file
@@ -0,0 +1,488 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
func TestAnnotateCommand(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
args []string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "single value",
|
||||
args: []string{"--kv", "a=b"},
|
||||
expected: expectedSingleValue,
|
||||
},
|
||||
{
|
||||
name: "multi value",
|
||||
args: []string{"--kv", "a=b", "--kv", "c=d"},
|
||||
expected: expectedMultiValue,
|
||||
},
|
||||
{
|
||||
name: "filter kind",
|
||||
args: []string{"--kv", "a=b", "--kind", "Service"},
|
||||
expected: expectedFilterKindService,
|
||||
},
|
||||
{
|
||||
name: "filter apiVersion",
|
||||
args: []string{"--kv", "a=b", "--apiVersion", "v1"},
|
||||
expected: expectedFilterApiVersionV1,
|
||||
},
|
||||
{
|
||||
name: "filter name",
|
||||
args: []string{"--kv", "a=b", "--name", "bar"},
|
||||
expected: expectedFilterNameBar,
|
||||
},
|
||||
{
|
||||
name: "filter namespace",
|
||||
args: []string{"--kv", "a=b", "--namespace", "bar"},
|
||||
expected: expectedFilterNamespaceBar,
|
||||
},
|
||||
}
|
||||
for i := range tests {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := initTestDir(t)
|
||||
defer os.RemoveAll(d)
|
||||
|
||||
a := NewAnnotateRunner("")
|
||||
a.Command.SetArgs(append([]string{d}, tt.args...))
|
||||
a.Command.SilenceUsage = true
|
||||
a.Command.SilenceErrors = true
|
||||
|
||||
err := a.Command.Execute()
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
actual := &bytes.Buffer{}
|
||||
err = kio.Pipeline{
|
||||
Inputs: []kio.Reader{kio.LocalPackageReader{PackagePath: d}},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: actual, KeepReaderAnnotations: true}},
|
||||
}.Execute()
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tt.expected),
|
||||
strings.TrimSpace(actual.String())) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func initTestDir(t *testing.T) string {
|
||||
d, err := ioutil.TempDir("", "kustomize-annotate-test")
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(f1Input), 0600)
|
||||
if !assert.NoError(t, err) {
|
||||
defer os.RemoveAll(d)
|
||||
t.FailNow()
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(f2Input), 0600)
|
||||
if !assert.NoError(t, err) {
|
||||
defer os.RemoveAll(d)
|
||||
t.FailNow()
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
var (
|
||||
f1Input = `
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx2
|
||||
name: foo
|
||||
annotations:
|
||||
app: nginx2
|
||||
spec:
|
||||
replicas: 1
|
||||
---
|
||||
kind: Service
|
||||
metadata:
|
||||
name: foo
|
||||
annotations:
|
||||
app: nginx
|
||||
spec:
|
||||
selector:
|
||||
app: nginx
|
||||
`
|
||||
|
||||
f2Input = `
|
||||
apiVersion: v1
|
||||
kind: Abstraction
|
||||
metadata:
|
||||
name: foo
|
||||
configFn:
|
||||
container:
|
||||
image: gcr.io/example/reconciler:v1
|
||||
annotations:
|
||||
config.kubernetes.io/local-config: "true"
|
||||
namespace: bar
|
||||
spec:
|
||||
replicas: 3
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
name: bar
|
||||
annotations:
|
||||
app: nginx
|
||||
namespace: foo
|
||||
spec:
|
||||
replicas: 3
|
||||
`
|
||||
|
||||
expectedSingleValue = `kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx2
|
||||
name: foo
|
||||
annotations:
|
||||
app: nginx2
|
||||
a: 'b'
|
||||
config.kubernetes.io/index: '0'
|
||||
config.kubernetes.io/path: 'f1.yaml'
|
||||
spec:
|
||||
replicas: 1
|
||||
---
|
||||
kind: Service
|
||||
metadata:
|
||||
name: foo
|
||||
annotations:
|
||||
app: nginx
|
||||
a: 'b'
|
||||
config.kubernetes.io/index: '1'
|
||||
config.kubernetes.io/path: 'f1.yaml'
|
||||
spec:
|
||||
selector:
|
||||
app: nginx
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Abstraction
|
||||
metadata:
|
||||
name: foo
|
||||
configFn:
|
||||
container:
|
||||
image: gcr.io/example/reconciler:v1
|
||||
annotations:
|
||||
config.kubernetes.io/local-config: "true"
|
||||
a: 'b'
|
||||
config.kubernetes.io/index: '0'
|
||||
config.kubernetes.io/path: 'f2.yaml'
|
||||
namespace: bar
|
||||
spec:
|
||||
replicas: 3
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
name: bar
|
||||
annotations:
|
||||
app: nginx
|
||||
a: 'b'
|
||||
config.kubernetes.io/index: '1'
|
||||
config.kubernetes.io/path: 'f2.yaml'
|
||||
namespace: foo
|
||||
spec:
|
||||
replicas: 3
|
||||
`
|
||||
|
||||
expectedMultiValue = `kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx2
|
||||
name: foo
|
||||
annotations:
|
||||
app: nginx2
|
||||
a: 'b'
|
||||
c: 'd'
|
||||
config.kubernetes.io/index: '0'
|
||||
config.kubernetes.io/path: 'f1.yaml'
|
||||
spec:
|
||||
replicas: 1
|
||||
---
|
||||
kind: Service
|
||||
metadata:
|
||||
name: foo
|
||||
annotations:
|
||||
app: nginx
|
||||
a: 'b'
|
||||
c: 'd'
|
||||
config.kubernetes.io/index: '1'
|
||||
config.kubernetes.io/path: 'f1.yaml'
|
||||
spec:
|
||||
selector:
|
||||
app: nginx
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Abstraction
|
||||
metadata:
|
||||
name: foo
|
||||
configFn:
|
||||
container:
|
||||
image: gcr.io/example/reconciler:v1
|
||||
annotations:
|
||||
config.kubernetes.io/local-config: "true"
|
||||
a: 'b'
|
||||
c: 'd'
|
||||
config.kubernetes.io/index: '0'
|
||||
config.kubernetes.io/path: 'f2.yaml'
|
||||
namespace: bar
|
||||
spec:
|
||||
replicas: 3
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
name: bar
|
||||
annotations:
|
||||
app: nginx
|
||||
a: 'b'
|
||||
c: 'd'
|
||||
config.kubernetes.io/index: '1'
|
||||
config.kubernetes.io/path: 'f2.yaml'
|
||||
namespace: foo
|
||||
spec:
|
||||
replicas: 3
|
||||
`
|
||||
|
||||
expectedFilterKindService = `kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx2
|
||||
name: foo
|
||||
annotations:
|
||||
app: nginx2
|
||||
config.kubernetes.io/index: '0'
|
||||
config.kubernetes.io/path: 'f1.yaml'
|
||||
spec:
|
||||
replicas: 1
|
||||
---
|
||||
kind: Service
|
||||
metadata:
|
||||
name: foo
|
||||
annotations:
|
||||
app: nginx
|
||||
a: 'b'
|
||||
config.kubernetes.io/index: '1'
|
||||
config.kubernetes.io/path: 'f1.yaml'
|
||||
spec:
|
||||
selector:
|
||||
app: nginx
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Abstraction
|
||||
metadata:
|
||||
name: foo
|
||||
configFn:
|
||||
container:
|
||||
image: gcr.io/example/reconciler:v1
|
||||
annotations:
|
||||
config.kubernetes.io/local-config: "true"
|
||||
config.kubernetes.io/index: '0'
|
||||
config.kubernetes.io/path: 'f2.yaml'
|
||||
namespace: bar
|
||||
spec:
|
||||
replicas: 3
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
name: bar
|
||||
annotations:
|
||||
app: nginx
|
||||
config.kubernetes.io/index: '1'
|
||||
config.kubernetes.io/path: 'f2.yaml'
|
||||
namespace: foo
|
||||
spec:
|
||||
replicas: 3
|
||||
`
|
||||
|
||||
expectedFilterApiVersionV1 = `kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx2
|
||||
name: foo
|
||||
annotations:
|
||||
app: nginx2
|
||||
config.kubernetes.io/index: '0'
|
||||
config.kubernetes.io/path: 'f1.yaml'
|
||||
spec:
|
||||
replicas: 1
|
||||
---
|
||||
kind: Service
|
||||
metadata:
|
||||
name: foo
|
||||
annotations:
|
||||
app: nginx
|
||||
config.kubernetes.io/index: '1'
|
||||
config.kubernetes.io/path: 'f1.yaml'
|
||||
spec:
|
||||
selector:
|
||||
app: nginx
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Abstraction
|
||||
metadata:
|
||||
name: foo
|
||||
configFn:
|
||||
container:
|
||||
image: gcr.io/example/reconciler:v1
|
||||
annotations:
|
||||
config.kubernetes.io/local-config: "true"
|
||||
a: 'b'
|
||||
config.kubernetes.io/index: '0'
|
||||
config.kubernetes.io/path: 'f2.yaml'
|
||||
namespace: bar
|
||||
spec:
|
||||
replicas: 3
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
name: bar
|
||||
annotations:
|
||||
app: nginx
|
||||
config.kubernetes.io/index: '1'
|
||||
config.kubernetes.io/path: 'f2.yaml'
|
||||
namespace: foo
|
||||
spec:
|
||||
replicas: 3
|
||||
`
|
||||
|
||||
expectedFilterNameBar = `kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx2
|
||||
name: foo
|
||||
annotations:
|
||||
app: nginx2
|
||||
config.kubernetes.io/index: '0'
|
||||
config.kubernetes.io/path: 'f1.yaml'
|
||||
spec:
|
||||
replicas: 1
|
||||
---
|
||||
kind: Service
|
||||
metadata:
|
||||
name: foo
|
||||
annotations:
|
||||
app: nginx
|
||||
config.kubernetes.io/index: '1'
|
||||
config.kubernetes.io/path: 'f1.yaml'
|
||||
spec:
|
||||
selector:
|
||||
app: nginx
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Abstraction
|
||||
metadata:
|
||||
name: foo
|
||||
configFn:
|
||||
container:
|
||||
image: gcr.io/example/reconciler:v1
|
||||
annotations:
|
||||
config.kubernetes.io/local-config: "true"
|
||||
config.kubernetes.io/index: '0'
|
||||
config.kubernetes.io/path: 'f2.yaml'
|
||||
namespace: bar
|
||||
spec:
|
||||
replicas: 3
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
name: bar
|
||||
annotations:
|
||||
app: nginx
|
||||
a: 'b'
|
||||
config.kubernetes.io/index: '1'
|
||||
config.kubernetes.io/path: 'f2.yaml'
|
||||
namespace: foo
|
||||
spec:
|
||||
replicas: 3
|
||||
`
|
||||
|
||||
expectedFilterNamespaceBar = `kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx2
|
||||
name: foo
|
||||
annotations:
|
||||
app: nginx2
|
||||
config.kubernetes.io/index: '0'
|
||||
config.kubernetes.io/path: 'f1.yaml'
|
||||
spec:
|
||||
replicas: 1
|
||||
---
|
||||
kind: Service
|
||||
metadata:
|
||||
name: foo
|
||||
annotations:
|
||||
app: nginx
|
||||
config.kubernetes.io/index: '1'
|
||||
config.kubernetes.io/path: 'f1.yaml'
|
||||
spec:
|
||||
selector:
|
||||
app: nginx
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Abstraction
|
||||
metadata:
|
||||
name: foo
|
||||
configFn:
|
||||
container:
|
||||
image: gcr.io/example/reconciler:v1
|
||||
annotations:
|
||||
config.kubernetes.io/local-config: "true"
|
||||
a: 'b'
|
||||
config.kubernetes.io/index: '0'
|
||||
config.kubernetes.io/path: 'f2.yaml'
|
||||
namespace: bar
|
||||
spec:
|
||||
replicas: 3
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
name: bar
|
||||
annotations:
|
||||
app: nginx
|
||||
config.kubernetes.io/index: '1'
|
||||
config.kubernetes.io/path: 'f2.yaml'
|
||||
namespace: foo
|
||||
spec:
|
||||
replicas: 3
|
||||
`
|
||||
)
|
||||
@@ -4,6 +4,20 @@
|
||||
// Code generated by "mdtogo"; DO NOT EDIT.
|
||||
package commands
|
||||
|
||||
var AnnotateShort = `[Alpha] Set an annotation on Resources.`
|
||||
var AnnotateLong = `
|
||||
[Alpha] Set an annotation on Resources.
|
||||
|
||||
DIR:
|
||||
Path to local directory.
|
||||
`
|
||||
var AnnotateExamples = `
|
||||
kustomize config annotate my-dir/ --kv foo=bar
|
||||
|
||||
kustomize config annotate my-dir/ --kv foo=bar --kv a=b
|
||||
|
||||
kustomize config annotate my-dir/ --kv foo=bar --kind Deployment --name foo`
|
||||
|
||||
var CatShort = `[Alpha] Print Resource Config from a local directory.`
|
||||
var CatLong = `
|
||||
[Alpha] Print Resource Config from a local directory.
|
||||
|
||||
@@ -11,11 +11,11 @@ require (
|
||||
k8s.io/component-base v0.17.0 // indirect
|
||||
k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd
|
||||
sigs.k8s.io/controller-runtime v0.4.0
|
||||
sigs.k8s.io/kustomize/kstatus v0.0.0-20200109211150-9555095de939
|
||||
sigs.k8s.io/kustomize/kstatus v0.0.1
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.2
|
||||
)
|
||||
|
||||
replace (
|
||||
sigs.k8s.io/kustomize/kstatus v0.0.0-20200109211150-9555095de939 => ../../kstatus
|
||||
sigs.k8s.io/kustomize/kstatus v0.0.1 => ../../kstatus
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.0 => ../../kyaml
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ package kubectlcobra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -81,3 +82,99 @@ func (i *Inventory) String() string {
|
||||
i.GroupKind.Group, fieldSeparator,
|
||||
i.GroupKind.Kind)
|
||||
}
|
||||
|
||||
// InventorySet encapsulates a grouping of unique Inventory
|
||||
// structs. Organizes the Inventory structs with a map,
|
||||
// which ensures there are no duplicates. Allows set
|
||||
// operations such as merging sets and subtracting sets.
|
||||
type InventorySet struct {
|
||||
set map[string]*Inventory
|
||||
}
|
||||
|
||||
// NewInventorySet returns a pointer to an InventorySet
|
||||
// struct grouping the passed Inventory items.
|
||||
func NewInventorySet(items []*Inventory) *InventorySet {
|
||||
invSet := InventorySet{set: map[string]*Inventory{}}
|
||||
invSet.AddItems(items)
|
||||
return &invSet
|
||||
}
|
||||
|
||||
// GetItems returns the set of pointers to Inventory
|
||||
// structs.
|
||||
func (is *InventorySet) GetItems() []*Inventory {
|
||||
items := []*Inventory{}
|
||||
for _, item := range is.set {
|
||||
items = append(items, item)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// AddItems adds Inventory structs to the set which
|
||||
// are not already in the set.
|
||||
func (is *InventorySet) AddItems(items []*Inventory) {
|
||||
for _, item := range items {
|
||||
if item != nil {
|
||||
is.set[item.String()] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteItem removes an Inventory struct from the
|
||||
// set if it exists in the set. Returns true if the
|
||||
// Inventory item was deleted, false if it did not exist
|
||||
// in the set.
|
||||
func (is *InventorySet) DeleteItem(item *Inventory) bool {
|
||||
if item == nil {
|
||||
return false
|
||||
}
|
||||
if _, ok := is.set[item.String()]; ok {
|
||||
delete(is.set, item.String())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Merge combines the unique set of Inventory items from the
|
||||
// current set with the passed "other" set, returning a new
|
||||
// set or error. Returns an error if the passed set to merge
|
||||
// is nil.
|
||||
func (is *InventorySet) Merge(other *InventorySet) (*InventorySet, error) {
|
||||
if other == nil {
|
||||
return nil, fmt.Errorf("InventorySet to merge is nil.")
|
||||
}
|
||||
// Copy the current InventorySet into result
|
||||
result := NewInventorySet(is.GetItems())
|
||||
result.AddItems(other.GetItems())
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Subtract removes the Inventory items in the "other" set from the
|
||||
// current set, returning a new set. This does not modify the current
|
||||
// set. Returns an error if the passed set to subtract is nil.
|
||||
func (is *InventorySet) Subtract(other *InventorySet) (*InventorySet, error) {
|
||||
if other == nil {
|
||||
return nil, fmt.Errorf("InventorySet to subtract is nil.")
|
||||
}
|
||||
// Copy the current InventorySet into result
|
||||
result := NewInventorySet(is.GetItems())
|
||||
// Remove each item in "other" which exists in "result"
|
||||
for _, item := range other.GetItems() {
|
||||
result.DeleteItem(item)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// String returns a string describing set of Inventory structs.
|
||||
func (is *InventorySet) String() string {
|
||||
strs := []string{}
|
||||
for _, item := range is.GetItems() {
|
||||
strs = append(strs, item.String())
|
||||
}
|
||||
sort.Strings(strs)
|
||||
return strings.Join(strs, ", ")
|
||||
}
|
||||
|
||||
// Size returns the number of Inventory structs in the set.
|
||||
func (is *InventorySet) Size() int {
|
||||
return len(is.set)
|
||||
}
|
||||
|
||||
@@ -216,3 +216,274 @@ func TestParseInventory(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var inventory1 = Inventory{
|
||||
Namespace: "test-namespace",
|
||||
Name: "test-inv-1",
|
||||
GroupKind: schema.GroupKind{
|
||||
Group: "apps",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
}
|
||||
|
||||
var inventory2 = Inventory{
|
||||
Namespace: "test-namespace",
|
||||
Name: "test-inv-2",
|
||||
GroupKind: schema.GroupKind{
|
||||
Group: "",
|
||||
Kind: "Pod",
|
||||
},
|
||||
}
|
||||
|
||||
var inventory3 = Inventory{
|
||||
Namespace: "test-namespace",
|
||||
Name: "test-inv-3",
|
||||
GroupKind: schema.GroupKind{
|
||||
Group: "",
|
||||
Kind: "Service",
|
||||
},
|
||||
}
|
||||
|
||||
var inventory4 = Inventory{
|
||||
Namespace: "test-namespace",
|
||||
Name: "test-inv-4",
|
||||
GroupKind: schema.GroupKind{
|
||||
Group: "apps",
|
||||
Kind: "DaemonSet",
|
||||
},
|
||||
}
|
||||
|
||||
func TestNewInventorySet(t *testing.T) {
|
||||
tests := []struct {
|
||||
items []*Inventory
|
||||
expectedStr string
|
||||
expectedSize int
|
||||
}{
|
||||
{
|
||||
items: []*Inventory{},
|
||||
expectedStr: "",
|
||||
expectedSize: 0,
|
||||
},
|
||||
{
|
||||
items: []*Inventory{&inventory1},
|
||||
expectedStr: "test-namespace_test-inv-1_apps_Deployment",
|
||||
expectedSize: 1,
|
||||
},
|
||||
{
|
||||
items: []*Inventory{&inventory1, &inventory2},
|
||||
expectedStr: "test-namespace_test-inv-1_apps_Deployment, test-namespace_test-inv-2__Pod",
|
||||
expectedSize: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
invSet := NewInventorySet(test.items)
|
||||
actualStr := invSet.String()
|
||||
actualSize := invSet.Size()
|
||||
if test.expectedStr != actualStr {
|
||||
t.Errorf("Expected InventorySet (%s), got (%s)\n", test.expectedStr, actualStr)
|
||||
}
|
||||
if test.expectedSize != actualSize {
|
||||
t.Errorf("Expected InventorySet size (%d), got (%d)\n", test.expectedSize, actualSize)
|
||||
}
|
||||
actualItems := invSet.GetItems()
|
||||
if len(test.items) != len(actualItems) {
|
||||
t.Errorf("Expected num inventory items (%d), got (%d)\n", len(test.items), len(actualItems))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInventorySetAddItems(t *testing.T) {
|
||||
tests := []struct {
|
||||
initialItems []*Inventory
|
||||
addItems []*Inventory
|
||||
expectedItems []*Inventory
|
||||
}{
|
||||
// Adding no items to empty inventory set.
|
||||
{
|
||||
initialItems: []*Inventory{},
|
||||
addItems: []*Inventory{},
|
||||
expectedItems: []*Inventory{},
|
||||
},
|
||||
// Adding item to empty inventory set.
|
||||
{
|
||||
initialItems: []*Inventory{},
|
||||
addItems: []*Inventory{&inventory1},
|
||||
expectedItems: []*Inventory{&inventory1},
|
||||
},
|
||||
// Adding no items does not change the inventory set
|
||||
{
|
||||
initialItems: []*Inventory{&inventory1},
|
||||
addItems: []*Inventory{},
|
||||
expectedItems: []*Inventory{&inventory1},
|
||||
},
|
||||
// Adding an item which alread exists does not increase size.
|
||||
{
|
||||
initialItems: []*Inventory{&inventory1, &inventory2},
|
||||
addItems: []*Inventory{&inventory1},
|
||||
expectedItems: []*Inventory{&inventory1, &inventory2},
|
||||
},
|
||||
{
|
||||
initialItems: []*Inventory{&inventory1, &inventory2},
|
||||
addItems: []*Inventory{&inventory3, &inventory4},
|
||||
expectedItems: []*Inventory{&inventory1, &inventory2, &inventory3, &inventory4},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
invSet := NewInventorySet(test.initialItems)
|
||||
invSet.AddItems(test.addItems)
|
||||
if len(test.expectedItems) != invSet.Size() {
|
||||
t.Errorf("Expected num inventory items (%d), got (%d)\n", len(test.expectedItems), invSet.Size())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInventorySetDeleteItem(t *testing.T) {
|
||||
tests := []struct {
|
||||
initialItems []*Inventory
|
||||
deleteItem *Inventory
|
||||
expected bool
|
||||
expectedItems []*Inventory
|
||||
}{
|
||||
{
|
||||
initialItems: []*Inventory{},
|
||||
deleteItem: nil,
|
||||
expected: false,
|
||||
expectedItems: []*Inventory{},
|
||||
},
|
||||
{
|
||||
initialItems: []*Inventory{},
|
||||
deleteItem: &inventory1,
|
||||
expected: false,
|
||||
expectedItems: []*Inventory{},
|
||||
},
|
||||
{
|
||||
initialItems: []*Inventory{&inventory2},
|
||||
deleteItem: &inventory1,
|
||||
expected: false,
|
||||
expectedItems: []*Inventory{&inventory2},
|
||||
},
|
||||
{
|
||||
initialItems: []*Inventory{&inventory1},
|
||||
deleteItem: &inventory1,
|
||||
expected: true,
|
||||
expectedItems: []*Inventory{},
|
||||
},
|
||||
{
|
||||
initialItems: []*Inventory{&inventory1, &inventory2},
|
||||
deleteItem: &inventory1,
|
||||
expected: true,
|
||||
expectedItems: []*Inventory{&inventory2},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
invSet := NewInventorySet(test.initialItems)
|
||||
actual := invSet.DeleteItem(test.deleteItem)
|
||||
if test.expected != actual {
|
||||
t.Errorf("Expected return value (%t), got (%t)\n", test.expected, actual)
|
||||
}
|
||||
if len(test.expectedItems) != invSet.Size() {
|
||||
t.Errorf("Expected num inventory items (%d), got (%d)\n", len(test.expectedItems), invSet.Size())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInventorySetMerge(t *testing.T) {
|
||||
tests := []struct {
|
||||
set1 []*Inventory
|
||||
set2 []*Inventory
|
||||
merged []*Inventory
|
||||
}{
|
||||
{
|
||||
set1: []*Inventory{},
|
||||
set2: []*Inventory{},
|
||||
merged: []*Inventory{},
|
||||
},
|
||||
{
|
||||
set1: []*Inventory{},
|
||||
set2: []*Inventory{&inventory1},
|
||||
merged: []*Inventory{&inventory1},
|
||||
},
|
||||
{
|
||||
set1: []*Inventory{&inventory1},
|
||||
set2: []*Inventory{},
|
||||
merged: []*Inventory{&inventory1},
|
||||
},
|
||||
{
|
||||
set1: []*Inventory{&inventory1, &inventory2},
|
||||
set2: []*Inventory{&inventory1},
|
||||
merged: []*Inventory{&inventory1, &inventory2},
|
||||
},
|
||||
{
|
||||
set1: []*Inventory{&inventory1, &inventory2},
|
||||
set2: []*Inventory{&inventory1, &inventory2},
|
||||
merged: []*Inventory{&inventory1, &inventory2},
|
||||
},
|
||||
{
|
||||
set1: []*Inventory{&inventory1, &inventory2},
|
||||
set2: []*Inventory{&inventory3, &inventory4},
|
||||
merged: []*Inventory{&inventory1, &inventory2, &inventory3, &inventory4},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
invSet1 := NewInventorySet(test.set1)
|
||||
invSet2 := NewInventorySet(test.set2)
|
||||
expected := NewInventorySet(test.merged)
|
||||
merged, _ := invSet1.Merge(invSet2)
|
||||
if expected.Size() != merged.Size() {
|
||||
t.Errorf("Expected merged inventory set size (%d), got (%d)\n", expected.Size(), merged.Size())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInventorySetSubtract(t *testing.T) {
|
||||
tests := []struct {
|
||||
initialItems []*Inventory
|
||||
subtractItems []*Inventory
|
||||
expected []*Inventory
|
||||
}{
|
||||
{
|
||||
initialItems: []*Inventory{},
|
||||
subtractItems: []*Inventory{},
|
||||
expected: []*Inventory{},
|
||||
},
|
||||
{
|
||||
initialItems: []*Inventory{},
|
||||
subtractItems: []*Inventory{&inventory1},
|
||||
expected: []*Inventory{},
|
||||
},
|
||||
{
|
||||
initialItems: []*Inventory{&inventory1},
|
||||
subtractItems: []*Inventory{},
|
||||
expected: []*Inventory{&inventory1},
|
||||
},
|
||||
{
|
||||
initialItems: []*Inventory{&inventory1, &inventory2},
|
||||
subtractItems: []*Inventory{&inventory1},
|
||||
expected: []*Inventory{&inventory2},
|
||||
},
|
||||
{
|
||||
initialItems: []*Inventory{&inventory1, &inventory2},
|
||||
subtractItems: []*Inventory{&inventory1, &inventory2},
|
||||
expected: []*Inventory{},
|
||||
},
|
||||
{
|
||||
initialItems: []*Inventory{&inventory1, &inventory2},
|
||||
subtractItems: []*Inventory{&inventory3, &inventory4},
|
||||
expected: []*Inventory{&inventory1, &inventory2},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
invInitialItems := NewInventorySet(test.initialItems)
|
||||
invSubtractItems := NewInventorySet(test.subtractItems)
|
||||
expected := NewInventorySet(test.expected)
|
||||
actual, _ := invInitialItems.Subtract(invSubtractItems)
|
||||
if expected.Size() != actual.Size() {
|
||||
t.Errorf("Expected subtracted inventory set size (%d), got (%d)\n", expected.Size(), actual.Size())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user