mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-14 02:20:53 +00:00
Merge pull request #3721 from monopole/removeWrappyLayer
Remove the wrappy package in service of 3588
This commit is contained in:
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type HashTransformerPlugin struct {
|
type HashTransformerPlugin struct {
|
||||||
hasher ifc.KunstructuredHasher
|
hasher ifc.KustHasher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HashTransformerPlugin) Config(
|
func (p *HashTransformerPlugin) Config(
|
||||||
@@ -24,7 +24,7 @@ func (p *HashTransformerPlugin) Config(
|
|||||||
func (p *HashTransformerPlugin) Transform(m resmap.ResMap) error {
|
func (p *HashTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||||
for _, res := range m.Resources() {
|
for _, res := range m.Resources() {
|
||||||
if res.NeedHashSuffix() {
|
if res.NeedHashSuffix() {
|
||||||
h, err := p.hasher.Hash(res)
|
h, err := res.Hash(p.hasher)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,20 +30,15 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, r := range m.Resources() {
|
for _, r := range m.Resources() {
|
||||||
empty, err := r.IsEmpty()
|
if r.IsEmpty() {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if empty {
|
|
||||||
// Don't mutate empty objects?
|
// Don't mutate empty objects?
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
r.StorePreviousId()
|
r.StorePreviousId()
|
||||||
err = r.ApplyFilter(namespace.Filter{
|
if err := r.ApplyFilter(namespace.Filter{
|
||||||
Namespace: p.Namespace,
|
Namespace: p.Namespace,
|
||||||
FsSlice: p.FieldSpecs,
|
FsSlice: p.FieldSpecs,
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)
|
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ func (ns Filter) roleBindingHack(obj *yaml.RNode, meta yaml.ResourceMeta) error
|
|||||||
|
|
||||||
// add the namespace to each "subject" with name: default
|
// add the namespace to each "subject" with name: default
|
||||||
err = obj.VisitElements(func(o *yaml.RNode) error {
|
err = obj.VisitElements(func(o *yaml.RNode) error {
|
||||||
// copied from kunstruct based kustomize NamespaceTransformer plugin
|
|
||||||
// The only case we need to force the namespace
|
// The only case we need to force the namespace
|
||||||
// if for the "service account". "default" is
|
// if for the "service account". "default" is
|
||||||
// kind of hardcoded here for right now.
|
// kind of hardcoded here for right now.
|
||||||
|
|||||||
@@ -6,13 +6,11 @@ require (
|
|||||||
github.com/evanphx/json-patch v4.5.0+incompatible
|
github.com/evanphx/json-patch v4.5.0+incompatible
|
||||||
github.com/go-errors/errors v1.0.1
|
github.com/go-errors/errors v1.0.1
|
||||||
github.com/go-openapi/spec v0.19.5
|
github.com/go-openapi/spec v0.19.5
|
||||||
github.com/google/go-cmp v0.4.0
|
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/imdario/mergo v0.3.5
|
github.com/imdario/mergo v0.3.5
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/stretchr/testify v1.4.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
|
||||||
sigs.k8s.io/kustomize/kyaml v0.10.15
|
sigs.k8s.io/kustomize/kyaml v0.10.15
|
||||||
sigs.k8s.io/yaml v1.2.0
|
sigs.k8s.io/yaml v1.2.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -242,7 +242,6 @@ golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGm
|
|||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ func SortArrayAndComputeHash(s []string) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return Encode(Hash(string(data)))
|
return encode(hex256(string(data)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copied from https://github.com/kubernetes/kubernetes
|
// Copied from https://github.com/kubernetes/kubernetes
|
||||||
// /blob/master/pkg/kubectl/util/hash/hash.go
|
// /blob/master/pkg/kubectl/util/hash/hash.go
|
||||||
func Encode(hex string) (string, error) {
|
func encode(hex string) (string, error) {
|
||||||
if len(hex) < 10 {
|
if len(hex) < 10 {
|
||||||
return "", fmt.Errorf(
|
return "", fmt.Errorf(
|
||||||
"input length must be at least 10")
|
"input length must be at least 10")
|
||||||
@@ -48,23 +48,18 @@ func Encode(hex string) (string, error) {
|
|||||||
return string(enc), nil
|
return string(enc), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns the hex form of the sha256 of the argument.
|
// hex256 returns the hex form of the sha256 of the argument.
|
||||||
func Hash(data string) string {
|
func hex256(data string) string {
|
||||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
|
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashRNode returns the hash value of input RNode
|
// Hasher computes the hash of an RNode.
|
||||||
func HashRNode(node *yaml.RNode) (string, error) {
|
type Hasher struct{}
|
||||||
// get node kind
|
|
||||||
kindNode, err := node.Pipe(yaml.FieldMatcher{Name: "kind"})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
kind := kindNode.YNode().Value
|
|
||||||
|
|
||||||
// calculate hash for different kinds
|
// Hash returns a hash of the argument.
|
||||||
encoded := ""
|
func (h *Hasher) Hash(node *yaml.RNode) (r string, err error) {
|
||||||
switch kind {
|
var encoded string
|
||||||
|
switch node.GetKind() {
|
||||||
case "ConfigMap":
|
case "ConfigMap":
|
||||||
encoded, err = encodeConfigMap(node)
|
encoded, err = encodeConfigMap(node)
|
||||||
case "Secret":
|
case "Secret":
|
||||||
@@ -77,10 +72,11 @@ func HashRNode(node *yaml.RNode) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return Encode(Hash(encoded))
|
return encode(hex256(encoded))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNodeValues(node *yaml.RNode, paths []string) (map[string]interface{}, error) {
|
func getNodeValues(
|
||||||
|
node *yaml.RNode, paths []string) (map[string]interface{}, error) {
|
||||||
values := make(map[string]interface{})
|
values := make(map[string]interface{})
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
vn, err := node.Pipe(yaml.Lookup(p))
|
vn, err := node.Pipe(yaml.Lookup(p))
|
||||||
@@ -117,8 +113,11 @@ func encodeConfigMap(node *yaml.RNode) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
m := map[string]interface{}{"kind": "ConfigMap", "name": values["metadata/name"],
|
m := map[string]interface{}{
|
||||||
"data": values["data"]}
|
"kind": "ConfigMap",
|
||||||
|
"name": values["metadata/name"],
|
||||||
|
"data": values["data"],
|
||||||
|
}
|
||||||
if _, ok := values["binaryData"].(map[string]interface{}); ok {
|
if _, ok := values["binaryData"].(map[string]interface{}); ok {
|
||||||
m["binaryData"] = values["binaryData"]
|
m["binaryData"] = values["binaryData"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ func TestSortArrayAndComputeHash(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHash(t *testing.T) {
|
func Test_hex256(t *testing.T) {
|
||||||
// hash the empty string to be sure that sha256 is being used
|
// hash the empty string to be sure that sha256 is being used
|
||||||
expect := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
expect := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
sum := Hash("")
|
sum := hex256("")
|
||||||
if expect != sum {
|
if expect != sum {
|
||||||
t.Errorf("expected hash %q but got %q", expect, sum)
|
t.Errorf("expected hash %q but got %q", expect, sum)
|
||||||
}
|
}
|
||||||
@@ -93,17 +93,17 @@ data:
|
|||||||
binaryData:
|
binaryData:
|
||||||
two: ""`, "698h7c7t9m", ""},
|
two: ""`, "698h7c7t9m", ""},
|
||||||
}
|
}
|
||||||
|
h := &Hasher{}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
node, err := yaml.Parse(c.cmYaml)
|
node, err := yaml.Parse(c.cmYaml)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
h, err := HashRNode(node)
|
hashed, err := h.Hash(node)
|
||||||
if SkipRest(t, c.desc, err, c.err) {
|
if SkipRest(t, c.desc, err, c.err) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c.hash != h {
|
if c.hash != hashed {
|
||||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,35 +154,34 @@ type: my-type
|
|||||||
data:
|
data:
|
||||||
one: ""`, "74bd68bm66", ""},
|
one: ""`, "74bd68bm66", ""},
|
||||||
}
|
}
|
||||||
|
h := &Hasher{}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
node, err := yaml.Parse(c.secretYaml)
|
node, err := yaml.Parse(c.secretYaml)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
h, err := HashRNode(node)
|
hashed, err := h.Hash(node)
|
||||||
if SkipRest(t, c.desc, err, c.err) {
|
if SkipRest(t, c.desc, err, c.err) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c.hash != h {
|
if c.hash != hashed {
|
||||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnstructuredHash(t *testing.T) {
|
func TestBasicHash(t *testing.T) {
|
||||||
cases := []struct {
|
cases := map[string]struct {
|
||||||
desc string
|
res string
|
||||||
unstructured string
|
hash string
|
||||||
hash string
|
err string
|
||||||
err string
|
|
||||||
}{
|
}{
|
||||||
{"minimal", `
|
"minimal": {`
|
||||||
apiVersion: test/v1
|
apiVersion: test/v1
|
||||||
kind: TestResource
|
kind: TestResource
|
||||||
metadata:
|
metadata:
|
||||||
name: my-resource`, "244782mkb7", ""},
|
name: my-resource`, "244782mkb7", ""},
|
||||||
{"with spec", `
|
"with spec": {`
|
||||||
apiVersion: test/v1
|
apiVersion: test/v1
|
||||||
kind: TestResource
|
kind: TestResource
|
||||||
metadata:
|
metadata:
|
||||||
@@ -191,19 +190,22 @@ spec:
|
|||||||
foo: 1
|
foo: 1
|
||||||
bar: abc`, "59m2mdccg4", ""},
|
bar: abc`, "59m2mdccg4", ""},
|
||||||
}
|
}
|
||||||
|
h := &Hasher{}
|
||||||
for _, c := range cases {
|
for n := range cases {
|
||||||
node, err := yaml.Parse(c.unstructured)
|
c := cases[n]
|
||||||
if err != nil {
|
t.Run(n, func(t *testing.T) {
|
||||||
t.Fatal(err)
|
node, err := yaml.Parse(c.res)
|
||||||
}
|
if err != nil {
|
||||||
h, err := HashRNode(node)
|
t.Fatal(err)
|
||||||
if SkipRest(t, c.desc, err, c.err) {
|
}
|
||||||
continue
|
hashed, err := h.Hash(node)
|
||||||
}
|
if SkipRest(t, n, err, c.err) {
|
||||||
if c.hash != h {
|
return
|
||||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
}
|
||||||
}
|
if c.hash != hashed {
|
||||||
|
t.Errorf("case %q, expect hash %q but got %q", n, c.hash, h)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,9 +336,10 @@ data:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SkipRest returns true if there was a non-nil error or if we expected an error that didn't happen,
|
// SkipRest returns true if there was a non-nil error or if we expected an
|
||||||
// and logs the appropriate error on the test object.
|
// error that didn't happen, and logs the appropriate error on the test object.
|
||||||
// The return value indicates whether we should skip the rest of the test case due to the error result.
|
// The return value indicates whether we should skip the rest of the test case
|
||||||
|
// due to the error result.
|
||||||
func SkipRest(t *testing.T, desc string, err error, contains string) bool {
|
func SkipRest(t *testing.T, desc string, err error, contains string) bool {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if len(contains) == 0 {
|
if len(contains) == 0 {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
package ifc
|
package ifc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validator provides functions to validate annotations and labels
|
// Validator provides functions to validate annotations and labels
|
||||||
@@ -38,92 +38,10 @@ type Loader interface {
|
|||||||
Cleanup() error
|
Cleanup() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kunstructured represents a Kubernetes Resource Model object.
|
// KustHasher returns a hash of the argument
|
||||||
type Kunstructured interface {
|
|
||||||
// Several uses.
|
|
||||||
Copy() Kunstructured
|
|
||||||
|
|
||||||
// GetAnnotations returns the k8s annotations.
|
|
||||||
GetAnnotations() map[string]string
|
|
||||||
|
|
||||||
// GetData returns a top-level "data" field, as in a ConfigMap.
|
|
||||||
GetDataMap() map[string]string
|
|
||||||
|
|
||||||
// GetData returns a top-level "binaryData" field, as in a ConfigMap.
|
|
||||||
GetBinaryDataMap() map[string]string
|
|
||||||
|
|
||||||
// Used by ResAccumulator and ReplacementTransformer.
|
|
||||||
GetFieldValue(string) (interface{}, error)
|
|
||||||
|
|
||||||
// Used by Resource.OrgId
|
|
||||||
GetGvk() resid.Gvk
|
|
||||||
|
|
||||||
// Used by resource.Factory.SliceFromBytes
|
|
||||||
GetKind() string
|
|
||||||
|
|
||||||
// GetLabels returns the k8s labels.
|
|
||||||
GetLabels() map[string]string
|
|
||||||
|
|
||||||
// Used by Resource.CurId and resource factory.
|
|
||||||
GetName() string
|
|
||||||
|
|
||||||
// Used by special case code in
|
|
||||||
// ResMap.SubsetThatCouldBeReferencedByResource
|
|
||||||
GetSlice(path string) ([]interface{}, error)
|
|
||||||
|
|
||||||
// GetString returns the value of a string field.
|
|
||||||
// Used by Resource.GetNamespace
|
|
||||||
GetString(string) (string, error)
|
|
||||||
|
|
||||||
// Several uses.
|
|
||||||
Map() (map[string]interface{}, error)
|
|
||||||
|
|
||||||
// Used by Resource.AsYAML and Resource.String
|
|
||||||
MarshalJSON() ([]byte, error)
|
|
||||||
|
|
||||||
// Used by resWrangler.Select
|
|
||||||
MatchesAnnotationSelector(selector string) (bool, error)
|
|
||||||
|
|
||||||
// Used by resWrangler.Select
|
|
||||||
MatchesLabelSelector(selector string) (bool, error)
|
|
||||||
|
|
||||||
// SetAnnotations replaces the k8s annotations.
|
|
||||||
SetAnnotations(map[string]string)
|
|
||||||
|
|
||||||
// SetDataMap sets a top-level "data" field, as in a ConfigMap.
|
|
||||||
SetDataMap(map[string]string)
|
|
||||||
|
|
||||||
// SetDataMap sets a top-level "binaryData" field, as in a ConfigMap.
|
|
||||||
SetBinaryDataMap(map[string]string)
|
|
||||||
// Used by PatchStrategicMergeTransformer.
|
|
||||||
SetGvk(resid.Gvk)
|
|
||||||
|
|
||||||
// SetLabels replaces the k8s labels.
|
|
||||||
SetLabels(map[string]string)
|
|
||||||
|
|
||||||
// SetName changes the name.
|
|
||||||
SetName(string)
|
|
||||||
|
|
||||||
// SetNamespace changes the namespace.
|
|
||||||
SetNamespace(string)
|
|
||||||
|
|
||||||
// Needed, for now, by kyaml/filtersutil.ApplyToJSON.
|
|
||||||
UnmarshalJSON([]byte) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// KunstructuredFactory makes instances of Kunstructured.
|
|
||||||
type KunstructuredFactory interface {
|
|
||||||
SliceFromBytes([]byte) ([]Kunstructured, error)
|
|
||||||
FromMap(m map[string]interface{}) Kunstructured
|
|
||||||
Hasher() KunstructuredHasher
|
|
||||||
MakeConfigMap(kvLdr KvLoader, args *types.ConfigMapArgs) (Kunstructured, error)
|
|
||||||
MakeSecret(kvLdr KvLoader, args *types.SecretArgs) (Kunstructured, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// KunstructuredHasher returns a hash of the argument
|
|
||||||
// or an error.
|
// or an error.
|
||||||
type KunstructuredHasher interface {
|
type KustHasher interface {
|
||||||
Hash(Kunstructured) (string, error)
|
Hash(*yaml.RNode) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// See core.v1.SecretTypeOpaque
|
// See core.v1.SecretTypeOpaque
|
||||||
|
|||||||
@@ -37,22 +37,10 @@ func (o *multiTransformer) Transform(m resmap.ResMap) error {
|
|||||||
|
|
||||||
func (o *multiTransformer) transform(m resmap.ResMap) error {
|
func (o *multiTransformer) transform(m resmap.ResMap) error {
|
||||||
for _, t := range o.transformers {
|
for _, t := range o.transformers {
|
||||||
err := t.Transform(m)
|
if err := t.Transform(m); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
m.DropEmpties()
|
||||||
for _, r := range m.Resources() {
|
|
||||||
empty, err := r.IsEmpty()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if empty {
|
|
||||||
err := m.Remove(r.CurId())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package wrappy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/hasher"
|
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
|
||||||
"sigs.k8s.io/kustomize/api/internal/generators"
|
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WNodeFactory makes instances of WNode.
|
|
||||||
//
|
|
||||||
// These instances in turn adapt
|
|
||||||
// sigs.k8s.io/kustomize/kyaml/yaml.RNode
|
|
||||||
// to implement ifc.Unstructured.
|
|
||||||
// This factory is meant to implement ifc.KunstructuredFactory.
|
|
||||||
//
|
|
||||||
// This implementation should be thin, as both WNode and WNodeFactory must be
|
|
||||||
// factored away (deleted) along with ifc.Kunstructured in favor of direct use
|
|
||||||
// of RNode methods upon completion of
|
|
||||||
// https://github.com/kubernetes-sigs/kustomize/issues/2506.
|
|
||||||
//
|
|
||||||
// See also api/krusty/internal/provider/depprovider.go
|
|
||||||
type WNodeFactory struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ifc.KunstructuredFactory = (*WNodeFactory)(nil)
|
|
||||||
|
|
||||||
func (k *WNodeFactory) SliceFromBytes(bs []byte) ([]ifc.Kunstructured, error) {
|
|
||||||
yamlRNodes, err := kio.FromBytes(bs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var result []ifc.Kunstructured
|
|
||||||
for i := range yamlRNodes {
|
|
||||||
rn := yamlRNodes[i]
|
|
||||||
meta, err := rn.GetValidatedMetadata()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !shouldDropObject(meta) {
|
|
||||||
if foundNil, path := rn.HasNilEntryInList(); foundNil {
|
|
||||||
return nil, fmt.Errorf("empty item at %v in object %v", path, rn)
|
|
||||||
}
|
|
||||||
result = append(result, FromRNode(rn))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// shouldDropObject returns true if the resource should not be accumulated.
|
|
||||||
func shouldDropObject(m yaml.ResourceMeta) bool {
|
|
||||||
_, y := m.ObjectMeta.Annotations[konfig.IgnoredByKustomizeAnnotation]
|
|
||||||
return y
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *WNodeFactory) FromMap(m map[string]interface{}) ifc.Kunstructured {
|
|
||||||
rn, err := FromMap(m)
|
|
||||||
if err != nil {
|
|
||||||
// TODO(#WNodeFactory): handle or bubble error"
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return rn
|
|
||||||
}
|
|
||||||
|
|
||||||
// kustHash computes a hash of an unstructured object.
|
|
||||||
type kustHash struct{}
|
|
||||||
|
|
||||||
// Hash returns a hash of the given object
|
|
||||||
func (h *kustHash) Hash(m ifc.Kunstructured) (string, error) {
|
|
||||||
node, err := filtersutil.GetRNode(m)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return hasher.HashRNode(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *WNodeFactory) Hasher() ifc.KunstructuredHasher {
|
|
||||||
return &kustHash{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeConfigMap makes a wrapped configmap.
|
|
||||||
func (k *WNodeFactory) MakeConfigMap(
|
|
||||||
ldr ifc.KvLoader, args *types.ConfigMapArgs) (ifc.Kunstructured, error) {
|
|
||||||
rn, err := generators.MakeConfigMap(ldr, args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return FromRNode(rn), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeSecret makes a wrapped secret.
|
|
||||||
func (k *WNodeFactory) MakeSecret(
|
|
||||||
ldr ifc.KvLoader, args *types.SecretArgs) (ifc.Kunstructured, error) {
|
|
||||||
rn, err := generators.MakeSecret(ldr, args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return FromRNode(rn), nil
|
|
||||||
}
|
|
||||||
@@ -1,324 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package wrappy_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
. "sigs.k8s.io/kustomize/api/internal/wrappy"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHasher(t *testing.T) {
|
|
||||||
input := `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: foo
|
|
||||||
data:
|
|
||||||
one: ""
|
|
||||||
binaryData:
|
|
||||||
two: ""
|
|
||||||
`
|
|
||||||
expect := "698h7c7t9m"
|
|
||||||
|
|
||||||
factory := &WNodeFactory{}
|
|
||||||
k, err := factory.SliceFromBytes([]byte(input))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hasher := factory.Hasher()
|
|
||||||
result, err := hasher.Hash(k[0])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if result != expect {
|
|
||||||
t.Fatalf("expect %s but got %s", expect, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSliceFromBytes(t *testing.T) {
|
|
||||||
factory := &WNodeFactory{}
|
|
||||||
testConfigMap :=
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "winnie",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
testConfigMapList :=
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMapList",
|
|
||||||
"items": []interface{}{
|
|
||||||
testConfigMap,
|
|
||||||
testConfigMap,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
testDeploymentSpec := map[string]interface{}{
|
|
||||||
"template": map[string]interface{}{
|
|
||||||
"spec": map[string]interface{}{
|
|
||||||
"hostAliases": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"hostnames": []interface{}{
|
|
||||||
"a.example.com",
|
|
||||||
},
|
|
||||||
"ip": "8.8.8.8",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
testDeploymentA := map[string]interface{}{
|
|
||||||
"apiVersion": "apps/v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "deployment-a",
|
|
||||||
},
|
|
||||||
"spec": testDeploymentSpec,
|
|
||||||
}
|
|
||||||
testDeploymentB := map[string]interface{}{
|
|
||||||
"apiVersion": "apps/v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "deployment-b",
|
|
||||||
},
|
|
||||||
"spec": testDeploymentSpec,
|
|
||||||
}
|
|
||||||
testDeploymentList :=
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "DeploymentList",
|
|
||||||
"items": []interface{}{
|
|
||||||
testDeploymentA,
|
|
||||||
testDeploymentB,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type expected struct {
|
|
||||||
out []map[string]interface{}
|
|
||||||
isErr bool
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
input []byte
|
|
||||||
exp expected
|
|
||||||
}{
|
|
||||||
"garbage": {
|
|
||||||
input: []byte("garbageIn: garbageOut"),
|
|
||||||
exp: expected{
|
|
||||||
isErr: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"noBytes": {
|
|
||||||
input: []byte{},
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"goodJson": {
|
|
||||||
input: []byte(`
|
|
||||||
{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie"}}
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{testConfigMap},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"goodYaml1": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{testConfigMap},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"goodYaml2": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{testConfigMap, testConfigMap},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"localConfigYaml": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie-skip
|
|
||||||
annotations:
|
|
||||||
# this annotation causes the Resource to be ignored by kustomize
|
|
||||||
config.kubernetes.io/local-config: ""
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{testConfigMap},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"garbageInOneOfTwoObjects": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
---
|
|
||||||
WOOOOOOOOOOOOOOOOOOOOOOOOT: woot
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
isErr: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"emptyObjects": {
|
|
||||||
input: []byte(`
|
|
||||||
---
|
|
||||||
#a comment
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Missing .metadata.name in object": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
annotations:
|
|
||||||
foo: bar
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
isErr: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nil value in list": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: builtin
|
|
||||||
kind: ConfigMapGenerator
|
|
||||||
metadata:
|
|
||||||
name: kube100-site
|
|
||||||
labels:
|
|
||||||
app: web
|
|
||||||
testList:
|
|
||||||
- testA
|
|
||||||
-
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
isErr: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"List": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: List
|
|
||||||
items:
|
|
||||||
- apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
- apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{
|
|
||||||
testConfigMap,
|
|
||||||
testConfigMap},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"ConfigMapList": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMapList
|
|
||||||
items:
|
|
||||||
- apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
- apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{testConfigMapList},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"listWithAnchors": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: DeploymentList
|
|
||||||
items:
|
|
||||||
- apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deployment-a
|
|
||||||
spec: &hostAliases
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
hostAliases:
|
|
||||||
- hostnames:
|
|
||||||
- a.example.com
|
|
||||||
ip: 8.8.8.8
|
|
||||||
- apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deployment-b
|
|
||||||
spec:
|
|
||||||
<<: *hostAliases
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{testDeploymentList},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for n := range testCases {
|
|
||||||
tc := testCases[n]
|
|
||||||
t.Run(n, func(t *testing.T) {
|
|
||||||
rs, err := factory.SliceFromBytes(tc.input)
|
|
||||||
if err != nil {
|
|
||||||
assert.True(t, tc.exp.isErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
assert.False(t, tc.exp.isErr)
|
|
||||||
assert.Equal(t, len(tc.exp.out), len(rs))
|
|
||||||
for i := range rs {
|
|
||||||
rsMap, err := rs[i].Map()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(
|
|
||||||
t, fmt.Sprintf("%v", tc.exp.out[i]), fmt.Sprintf("%v", rsMap))
|
|
||||||
if n != "listWithAnchors" {
|
|
||||||
// https://github.com/kubernetes-sigs/kustomize/issues/3271
|
|
||||||
m, _ := rs[i].Map()
|
|
||||||
if !reflect.DeepEqual(tc.exp.out[i], m) {
|
|
||||||
t.Fatalf("%s:\nexpected: %v\n actual: %v",
|
|
||||||
n, tc.exp.out[i], m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package wrappy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WNode implements ifc.Kunstructured using yaml.RNode.
|
|
||||||
//
|
|
||||||
// It exists only to help manage a switch from
|
|
||||||
// kunstruct.UnstructAdapter to yaml.RNode as the core
|
|
||||||
// representation of KRM objects in kustomize.
|
|
||||||
//
|
|
||||||
// It's got a silly name because we don't want it around for long,
|
|
||||||
// and want its use to be obvious.
|
|
||||||
type WNode struct {
|
|
||||||
node *yaml.RNode
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ifc.Kunstructured = (*WNode)(nil)
|
|
||||||
|
|
||||||
func NewWNode() *WNode {
|
|
||||||
return FromRNode(yaml.NewRNode(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromMap(m map[string]interface{}) (*WNode, error) {
|
|
||||||
n, err := yaml.FromMap(m)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return FromRNode(n), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromRNode(node *yaml.RNode) *WNode {
|
|
||||||
return &WNode{node: node}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wn *WNode) AsRNode() *yaml.RNode {
|
|
||||||
return wn.node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wn *WNode) demandMetaData(label string) yaml.ResourceMeta {
|
|
||||||
meta, err := wn.node.GetMeta()
|
|
||||||
if err != nil {
|
|
||||||
// Log and die since interface doesn't allow error.
|
|
||||||
log.Fatalf("for %s', expected valid resource: %v", label, err)
|
|
||||||
}
|
|
||||||
return meta
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) Copy() ifc.Kunstructured {
|
|
||||||
return &WNode{node: wn.node.Copy()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAnnotations implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetAnnotations() map[string]string {
|
|
||||||
return wn.demandMetaData("GetAnnotations").Annotations
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertSliceIndex traverses the items in `fields` and find
|
|
||||||
// if there is a slice index in the item and change it to a
|
|
||||||
// valid Lookup field path. For example, 'ports[0]' will be
|
|
||||||
// converted to 'ports' and '0'.
|
|
||||||
func convertSliceIndex(fields []string) []string {
|
|
||||||
var res []string
|
|
||||||
for _, s := range fields {
|
|
||||||
if !strings.HasSuffix(s, "]") {
|
|
||||||
res = append(res, s)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
re := regexp.MustCompile(`^(.*)\[(\d+)\]$`)
|
|
||||||
groups := re.FindStringSubmatch(s)
|
|
||||||
if len(groups) == 0 {
|
|
||||||
// no match, add to result
|
|
||||||
res = append(res, s)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if groups[1] != "" {
|
|
||||||
res = append(res, groups[1])
|
|
||||||
}
|
|
||||||
res = append(res, groups[2])
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFieldValue implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetFieldValue(path string) (interface{}, error) {
|
|
||||||
fields := convertSliceIndex(strings.Split(path, "."))
|
|
||||||
rn, err := wn.node.Pipe(yaml.Lookup(fields...))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if rn == nil {
|
|
||||||
return nil, NoFieldError{path}
|
|
||||||
}
|
|
||||||
yn := rn.YNode()
|
|
||||||
|
|
||||||
// If this is an alias node, resolve it
|
|
||||||
if yn.Kind == yaml.AliasNode {
|
|
||||||
yn = yn.Alias
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return value as map for DocumentNode and MappingNode kinds
|
|
||||||
if yn.Kind == yaml.DocumentNode || yn.Kind == yaml.MappingNode {
|
|
||||||
var result map[string]interface{}
|
|
||||||
if err := yn.Decode(&result); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return value as slice for SequenceNode kind
|
|
||||||
if yn.Kind == yaml.SequenceNode {
|
|
||||||
var result []interface{}
|
|
||||||
if err := yn.Decode(&result); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
if yn.Kind != yaml.ScalarNode {
|
|
||||||
return nil, fmt.Errorf("expected ScalarNode, got Kind=%d", yn.Kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: When doing kustomize var replacement, which is likely a
|
|
||||||
// a primary use of this function and the reason it returns interface{}
|
|
||||||
// rather than string, we do conversion from Nodes to Go types and back
|
|
||||||
// to nodes. We should figure out how to do replacement using raw nodes,
|
|
||||||
// assuming we keep the var feature in kustomize.
|
|
||||||
// The other end of this is: refvar.go:updateNodeValue.
|
|
||||||
switch yn.Tag {
|
|
||||||
case yaml.NodeTagString:
|
|
||||||
return yn.Value, nil
|
|
||||||
case yaml.NodeTagInt:
|
|
||||||
return strconv.Atoi(yn.Value)
|
|
||||||
case yaml.NodeTagFloat:
|
|
||||||
return strconv.ParseFloat(yn.Value, 64)
|
|
||||||
case yaml.NodeTagBool:
|
|
||||||
return strconv.ParseBool(yn.Value)
|
|
||||||
default:
|
|
||||||
// Possibly this should be an error or log.
|
|
||||||
return yn.Value, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGvk implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetGvk() resid.Gvk {
|
|
||||||
meta := wn.demandMetaData("GetGvk")
|
|
||||||
g, v := resid.ParseGroupVersion(meta.APIVersion)
|
|
||||||
return resid.Gvk{Group: g, Version: v, Kind: meta.Kind}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDataMap implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetDataMap() map[string]string {
|
|
||||||
return wn.node.GetDataMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDataMap implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) SetDataMap(m map[string]string) {
|
|
||||||
wn.node.SetDataMap(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBinaryDataMap implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetBinaryDataMap() map[string]string {
|
|
||||||
return wn.node.GetBinaryDataMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBinaryDataMap implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) SetBinaryDataMap(m map[string]string) {
|
|
||||||
wn.node.SetBinaryDataMap(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKind implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetKind() string {
|
|
||||||
return wn.demandMetaData("GetKind").Kind
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLabels implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetLabels() map[string]string {
|
|
||||||
return wn.demandMetaData("GetLabels").Labels
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetName() string {
|
|
||||||
return wn.demandMetaData("GetName").Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSlice implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetSlice(path string) ([]interface{}, error) {
|
|
||||||
value, err := wn.GetFieldValue(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if sliceValue, ok := value.([]interface{}); ok {
|
|
||||||
return sliceValue, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("node %s is not a slice", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSlice implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetString(path string) (string, error) {
|
|
||||||
value, err := wn.GetFieldValue(path)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if v, ok := value.(string); ok {
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("node %s is not a string: %v", path, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) Map() (map[string]interface{}, error) {
|
|
||||||
return wn.node.Map()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) MarshalJSON() ([]byte, error) {
|
|
||||||
return wn.node.MarshalJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchesAnnotationSelector implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) MatchesAnnotationSelector(selector string) (bool, error) {
|
|
||||||
return wn.node.MatchesAnnotationSelector(selector)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchesLabelSelector implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) MatchesLabelSelector(selector string) (bool, error) {
|
|
||||||
return wn.node.MatchesLabelSelector(selector)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAnnotations implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) SetAnnotations(annotations map[string]string) {
|
|
||||||
if err := wn.node.SetAnnotations(annotations); err != nil {
|
|
||||||
log.Fatal(err) // interface doesn't allow error.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetGvk implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) SetGvk(gvk resid.Gvk) {
|
|
||||||
wn.setMapField(yaml.NewScalarRNode(gvk.Kind), yaml.KindField)
|
|
||||||
wn.setMapField(yaml.NewScalarRNode(gvk.ApiVersion()), yaml.APIVersionField)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLabels implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) SetLabels(labels map[string]string) {
|
|
||||||
if err := wn.node.SetLabels(labels); err != nil {
|
|
||||||
log.Fatal(err) // interface doesn't allow error.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetName implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) SetName(name string) {
|
|
||||||
wn.setMapField(yaml.NewScalarRNode(name), yaml.MetadataField, yaml.NameField)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNamespace implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) SetNamespace(ns string) {
|
|
||||||
if err := wn.node.SetNamespace(ns); err != nil {
|
|
||||||
log.Fatal(err) // interface doesn't allow error.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wn *WNode) setMapField(value *yaml.RNode, path ...string) {
|
|
||||||
if err := wn.node.SetMapField(value, path...); err != nil {
|
|
||||||
// Log and die since interface doesn't allow error.
|
|
||||||
log.Fatalf("failed to set field %v: %v", path, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) UnmarshalJSON(data []byte) error {
|
|
||||||
return wn.node.UnmarshalJSON(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
type NoFieldError struct {
|
|
||||||
Field string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e NoFieldError) Error() string {
|
|
||||||
return fmt.Sprintf("no field named '%s'", e.Field)
|
|
||||||
}
|
|
||||||
@@ -1,670 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package wrappy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
deploymentLittleJson = `{"apiVersion":"apps/v1","kind":"Deployment",` +
|
|
||||||
`"metadata":{"name":"homer","namespace":"simpsons"}}`
|
|
||||||
|
|
||||||
deploymentBiggerJson = `
|
|
||||||
{
|
|
||||||
"apiVersion": "apps/v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": {
|
|
||||||
"name": "homer",
|
|
||||||
"namespace": "simpsons",
|
|
||||||
"labels": {
|
|
||||||
"fruit": "apple",
|
|
||||||
"veggie": "carrot"
|
|
||||||
},
|
|
||||||
"annotations": {
|
|
||||||
"area": "51",
|
|
||||||
"greeting": "Take me to your leader."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"template": {
|
|
||||||
"spec": {
|
|
||||||
"containers": [
|
|
||||||
{
|
|
||||||
"env": [
|
|
||||||
{
|
|
||||||
"name": "CM_FOO",
|
|
||||||
"valueFrom": {
|
|
||||||
"configMapKeyRef": {
|
|
||||||
"key": "somekey",
|
|
||||||
"name": "myCm"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "SECRET_FOO",
|
|
||||||
"valueFrom": {
|
|
||||||
"secretKeyRef": {
|
|
||||||
"key": "someKey",
|
|
||||||
"name": "mySecret"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"image": "nginx:1.7.9",
|
|
||||||
"name": "nginx"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
bigMapYaml = `Kind: Service
|
|
||||||
complextree:
|
|
||||||
- field1:
|
|
||||||
- boolfield: true
|
|
||||||
floatsubfield: 1.01
|
|
||||||
intsubfield: 1010
|
|
||||||
stringsubfield: idx1010
|
|
||||||
- boolfield: false
|
|
||||||
floatsubfield: 1.011
|
|
||||||
intsubfield: 1011
|
|
||||||
stringsubfield: idx1011
|
|
||||||
field2:
|
|
||||||
- boolfield: true
|
|
||||||
floatsubfield: 1.02
|
|
||||||
intsubfield: 1020
|
|
||||||
stringsubfield: idx1020
|
|
||||||
- boolfield: false
|
|
||||||
floatsubfield: 1.021
|
|
||||||
intsubfield: 1021
|
|
||||||
stringsubfield: idx1021
|
|
||||||
- field1:
|
|
||||||
- boolfield: true
|
|
||||||
floatsubfield: 1.11
|
|
||||||
intsubfield: 1110
|
|
||||||
stringsubfield: idx1110
|
|
||||||
- boolfield: false
|
|
||||||
floatsubfield: 1.111
|
|
||||||
intsubfield: 1111
|
|
||||||
stringsubfield: idx1111
|
|
||||||
field2:
|
|
||||||
- boolfield: true
|
|
||||||
floatsubfield: 1.112
|
|
||||||
intsubfield: 1120
|
|
||||||
stringsubfield: idx1120
|
|
||||||
- boolfield: false
|
|
||||||
floatsubfield: 1.1121
|
|
||||||
intsubfield: 1121
|
|
||||||
stringsubfield: idx1121
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: application-name
|
|
||||||
name: service-name
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
port: 80
|
|
||||||
that:
|
|
||||||
- idx0
|
|
||||||
- idx1
|
|
||||||
- idx2
|
|
||||||
- idx3
|
|
||||||
these:
|
|
||||||
- field1:
|
|
||||||
- idx010
|
|
||||||
- idx011
|
|
||||||
field2:
|
|
||||||
- idx020
|
|
||||||
- idx021
|
|
||||||
- field1:
|
|
||||||
- idx110
|
|
||||||
- idx111
|
|
||||||
field2:
|
|
||||||
- idx120
|
|
||||||
- idx121
|
|
||||||
- field1:
|
|
||||||
- idx210
|
|
||||||
- idx211
|
|
||||||
field2:
|
|
||||||
- idx220
|
|
||||||
- idx221
|
|
||||||
this:
|
|
||||||
is:
|
|
||||||
aBool: true
|
|
||||||
aFloat: 1.001
|
|
||||||
aNilValue: null
|
|
||||||
aNumber: 1000
|
|
||||||
anEmptyMap: {}
|
|
||||||
anEmptySlice: []
|
|
||||||
those:
|
|
||||||
- field1: idx0foo
|
|
||||||
field2: idx0bar
|
|
||||||
- field1: idx1foo
|
|
||||||
field2: idx1bar
|
|
||||||
- field1: idx2foo
|
|
||||||
field2: idx2bar
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeBigMap() map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"Kind": "Service",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"labels": map[string]interface{}{
|
|
||||||
"app": "application-name",
|
|
||||||
},
|
|
||||||
"name": "service-name",
|
|
||||||
},
|
|
||||||
"spec": map[string]interface{}{
|
|
||||||
"ports": map[string]interface{}{
|
|
||||||
"port": int64(80),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"this": map[string]interface{}{
|
|
||||||
"is": map[string]interface{}{
|
|
||||||
"aNumber": int64(1000),
|
|
||||||
"aFloat": float64(1.001),
|
|
||||||
"aNilValue": nil,
|
|
||||||
"aBool": true,
|
|
||||||
"anEmptyMap": map[string]interface{}{},
|
|
||||||
"anEmptySlice": []interface{}{},
|
|
||||||
/*
|
|
||||||
TODO: test for unrecognizable (e.g. a function)
|
|
||||||
"unrecognizable": testing.InternalExample{
|
|
||||||
Name: "fooBar",
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"that": []interface{}{
|
|
||||||
"idx0",
|
|
||||||
"idx1",
|
|
||||||
"idx2",
|
|
||||||
"idx3",
|
|
||||||
},
|
|
||||||
"those": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": "idx0foo",
|
|
||||||
"field2": "idx0bar",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": "idx1foo",
|
|
||||||
"field2": "idx1bar",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": "idx2foo",
|
|
||||||
"field2": "idx2bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"these": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": []interface{}{"idx010", "idx011"},
|
|
||||||
"field2": []interface{}{"idx020", "idx021"},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": []interface{}{"idx110", "idx111"},
|
|
||||||
"field2": []interface{}{"idx120", "idx121"},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": []interface{}{"idx210", "idx211"},
|
|
||||||
"field2": []interface{}{"idx220", "idx221"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"complextree": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"stringsubfield": "idx1010",
|
|
||||||
"intsubfield": int64(1010),
|
|
||||||
"floatsubfield": float64(1.010),
|
|
||||||
"boolfield": true,
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"stringsubfield": "idx1011",
|
|
||||||
"intsubfield": int64(1011),
|
|
||||||
"floatsubfield": float64(1.011),
|
|
||||||
"boolfield": false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"field2": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"stringsubfield": "idx1020",
|
|
||||||
"intsubfield": int64(1020),
|
|
||||||
"floatsubfield": float64(1.020),
|
|
||||||
"boolfield": true,
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"stringsubfield": "idx1021",
|
|
||||||
"intsubfield": int64(1021),
|
|
||||||
"floatsubfield": float64(1.021),
|
|
||||||
"boolfield": false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"stringsubfield": "idx1110",
|
|
||||||
"intsubfield": int64(1110),
|
|
||||||
"floatsubfield": float64(1.110),
|
|
||||||
"boolfield": true,
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"stringsubfield": "idx1111",
|
|
||||||
"intsubfield": int64(1111),
|
|
||||||
"floatsubfield": float64(1.111),
|
|
||||||
"boolfield": false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"field2": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"stringsubfield": "idx1120",
|
|
||||||
"intsubfield": int64(1120),
|
|
||||||
"floatsubfield": float64(1.1120),
|
|
||||||
"boolfield": true,
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"stringsubfield": "idx1121",
|
|
||||||
"intsubfield": int64(1121),
|
|
||||||
"floatsubfield": float64(1.1121),
|
|
||||||
"boolfield": false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBasicYamlOperationFromMap(t *testing.T) {
|
|
||||||
bytes, err := yaml.Marshal(makeBigMap())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
|
||||||
}
|
|
||||||
if string(bytes) != bigMapYaml {
|
|
||||||
t.Fatalf("unexpected string equality")
|
|
||||||
}
|
|
||||||
rNode, err := kyaml.Parse(string(bytes))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
|
||||||
}
|
|
||||||
rNodeString := rNode.MustString()
|
|
||||||
// The result from MustString has more indentation
|
|
||||||
// than bigMapYaml.
|
|
||||||
rNodeStrings := strings.Split(rNodeString, "\n")
|
|
||||||
bigMapStrings := strings.Split(bigMapYaml, "\n")
|
|
||||||
if len(rNodeStrings) != len(bigMapStrings) {
|
|
||||||
t.Fatalf("line count mismatch")
|
|
||||||
}
|
|
||||||
for i := range rNodeStrings {
|
|
||||||
s1 := strings.TrimSpace(rNodeStrings[i])
|
|
||||||
s2 := strings.TrimSpace(bigMapStrings[i])
|
|
||||||
if s1 != s2 {
|
|
||||||
t.Fatalf("expected '%s'=='%s'", s1, s2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoundTripJSON(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
err := wn.UnmarshalJSON([]byte(deploymentLittleJson))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected UnmarshalJSON err: %v", err)
|
|
||||||
}
|
|
||||||
data, err := wn.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected MarshalJSON err: %v", err)
|
|
||||||
}
|
|
||||||
actual := string(data)
|
|
||||||
if actual != deploymentLittleJson {
|
|
||||||
t.Fatalf("expected %s, got %s", deploymentLittleJson, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGettingFields(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
err := wn.UnmarshalJSON([]byte(deploymentBiggerJson))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
gvk := wn.GetGvk()
|
|
||||||
expected := "apps"
|
|
||||||
actual := gvk.Group
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
expected = "v1"
|
|
||||||
actual = gvk.Version
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
expected = "Deployment"
|
|
||||||
actual = gvk.Kind
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
actual = wn.GetKind()
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
expected = "homer"
|
|
||||||
actual = wn.GetName()
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
actualMap := wn.GetLabels()
|
|
||||||
v, ok := actualMap["fruit"]
|
|
||||||
if !ok || v != "apple" {
|
|
||||||
t.Fatalf("unexpected labels '%v'", actualMap)
|
|
||||||
}
|
|
||||||
actualMap = wn.GetAnnotations()
|
|
||||||
v, ok = actualMap["greeting"]
|
|
||||||
if !ok || v != "Take me to your leader." {
|
|
||||||
t.Fatalf("unexpected annotations '%v'", actualMap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetFieldValueReturnsMap(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
expected := map[string]interface{}{
|
|
||||||
"fruit": "apple",
|
|
||||||
"veggie": "carrot",
|
|
||||||
}
|
|
||||||
actual, err := wn.GetFieldValue("metadata.labels")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting field value: %v", err)
|
|
||||||
}
|
|
||||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
|
||||||
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetFieldValueReturnsStuff(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
expected := []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"env": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "CM_FOO",
|
|
||||||
"valueFrom": map[string]interface{}{
|
|
||||||
"configMapKeyRef": map[string]interface{}{
|
|
||||||
"key": "somekey",
|
|
||||||
"name": "myCm",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "SECRET_FOO",
|
|
||||||
"valueFrom": map[string]interface{}{
|
|
||||||
"secretKeyRef": map[string]interface{}{
|
|
||||||
"key": "someKey",
|
|
||||||
"name": "mySecret",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"image": string("nginx:1.7.9"),
|
|
||||||
"name": string("nginx"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
actual, err := wn.GetFieldValue("spec.template.spec.containers")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting field value: %v", err)
|
|
||||||
}
|
|
||||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
|
||||||
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
|
|
||||||
}
|
|
||||||
// Cannot go deeper yet.
|
|
||||||
_, err = wn.GetFieldValue("spec.template.spec.containers.env")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected err %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetFieldValueReturnsSlice(t *testing.T) {
|
|
||||||
bytes, err := yaml.Marshal(makeBigMap())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
|
||||||
}
|
|
||||||
rNode, err := kyaml.Parse(string(bytes))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
|
||||||
}
|
|
||||||
wn := FromRNode(rNode)
|
|
||||||
expected := []interface{}{"idx0", "idx1", "idx2", "idx3"}
|
|
||||||
actual, err := wn.GetFieldValue("that")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting slice: %v", err)
|
|
||||||
}
|
|
||||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
|
||||||
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetFieldValueReturnsSliceOfMappings(t *testing.T) {
|
|
||||||
bytes, err := yaml.Marshal(makeBigMap())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
|
||||||
}
|
|
||||||
rNode, err := kyaml.Parse(string(bytes))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
|
||||||
}
|
|
||||||
wn := FromRNode(rNode)
|
|
||||||
expected := []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": "idx0foo",
|
|
||||||
"field2": "idx0bar",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": "idx1foo",
|
|
||||||
"field2": "idx1bar",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": "idx2foo",
|
|
||||||
"field2": "idx2bar",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
actual, err := wn.GetFieldValue("those")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting slice: %v", err)
|
|
||||||
}
|
|
||||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
|
||||||
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetFieldValueReturnsString(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
actual, err := wn.GetFieldValue("metadata.labels.fruit")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting field value: %v", err)
|
|
||||||
}
|
|
||||||
v, ok := actual.(string)
|
|
||||||
if !ok || v != "apple" {
|
|
||||||
t.Fatalf("unexpected value '%v'", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetFieldValueResolvesAlias(t *testing.T) {
|
|
||||||
yamlWithAlias := `
|
|
||||||
foo: &a theValue
|
|
||||||
bar: *a
|
|
||||||
`
|
|
||||||
rNode, err := kyaml.Parse(yamlWithAlias)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml parse error: %v", err)
|
|
||||||
}
|
|
||||||
wn := FromRNode(rNode)
|
|
||||||
actual, err := wn.GetFieldValue("bar")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting field value: %v", err)
|
|
||||||
}
|
|
||||||
v, ok := actual.(string)
|
|
||||||
if !ok || v != "theValue" {
|
|
||||||
t.Fatalf("unexpected value '%v'", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetString(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
expected := "carrot"
|
|
||||||
actual, err := wn.GetString("metadata.labels.veggie")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting string: %v", err)
|
|
||||||
}
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetSlice(t *testing.T) {
|
|
||||||
bytes, err := yaml.Marshal(makeBigMap())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
|
||||||
}
|
|
||||||
rNode, err := kyaml.Parse(string(bytes))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
|
||||||
}
|
|
||||||
wn := FromRNode(rNode)
|
|
||||||
expected := []interface{}{"idx0", "idx1", "idx2", "idx3"}
|
|
||||||
actual, err := wn.GetSlice("that")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting slice: %v", err)
|
|
||||||
}
|
|
||||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
|
||||||
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMapEmpty(t *testing.T) {
|
|
||||||
newNodeMap, err := NewWNode().Map()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, 0, len(newNodeMap))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMap(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentLittleJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := map[string]interface{}{
|
|
||||||
"apiVersion": "apps/v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "homer",
|
|
||||||
"namespace": "simpsons",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
actual, err := wn.Map()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
|
||||||
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetName(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
wn.SetName("marge")
|
|
||||||
if expected, actual := "marge", wn.GetName(); expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetNamespace(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
wn.SetNamespace("flanders")
|
|
||||||
meta, _ := wn.node.GetMeta()
|
|
||||||
if expected, actual := "flanders", meta.Namespace; expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetLabels(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
wn.SetLabels(map[string]string{
|
|
||||||
"label1": "foo",
|
|
||||||
"label2": "bar",
|
|
||||||
})
|
|
||||||
labels := wn.GetLabels()
|
|
||||||
if expected, actual := 2, len(labels); expected != actual {
|
|
||||||
t.Fatalf("expected '%d', got '%d'", expected, actual)
|
|
||||||
}
|
|
||||||
if expected, actual := "foo", labels["label1"]; expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
if expected, actual := "bar", labels["label2"]; expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetAnnotations(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
wn.SetAnnotations(map[string]string{
|
|
||||||
"annotation1": "foo",
|
|
||||||
"annotation2": "bar",
|
|
||||||
})
|
|
||||||
annotations := wn.GetAnnotations()
|
|
||||||
if expected, actual := 2, len(annotations); expected != actual {
|
|
||||||
t.Fatalf("expected '%d', got '%d'", expected, actual)
|
|
||||||
}
|
|
||||||
if expected, actual := "foo", annotations["annotation1"]; expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
if expected, actual := "bar", annotations["annotation2"]; expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetGvk(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
wn.SetGvk(resid.GvkFromString("grp_ver_knd"))
|
|
||||||
gvk := wn.GetGvk()
|
|
||||||
if expected, actual := "grp", gvk.Group; expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
if expected, actual := "ver", gvk.Version; expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
if expected, actual := "knd", gvk.Kind; expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ package krusty_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,5 +36,8 @@ spec:
|
|||||||
- name: PODINFO_UI_COLOR
|
- name: PODINFO_UI_COLOR
|
||||||
value: "#34577c"
|
value: "#34577c"
|
||||||
`)
|
`)
|
||||||
th.RunWithErr(".", th.MakeDefaultOptions())
|
m := th.Run(".", th.MakeDefaultOptions())
|
||||||
|
_, err := m.AsYaml()
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "mapping key \"env\" already defined")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,103 +4,21 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/api/hasher"
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
"sigs.k8s.io/kustomize/api/internal/conflict"
|
"sigs.k8s.io/kustomize/api/internal/conflict"
|
||||||
"sigs.k8s.io/kustomize/api/internal/validate"
|
"sigs.k8s.io/kustomize/api/internal/validate"
|
||||||
"sigs.k8s.io/kustomize/api/internal/wrappy"
|
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DepProvider is a dependency provider.
|
// DepProvider is a dependency provider, injecting different
|
||||||
|
// implementations depending on the context.
|
||||||
//
|
//
|
||||||
// The instances it returns are either
|
// Notes on interfaces:
|
||||||
// - old implementations backed by k8sdeps code,
|
|
||||||
// - new implementations backed by kyaml code.
|
|
||||||
//
|
//
|
||||||
// History:
|
// - resource.ConflictDetector
|
||||||
//
|
//
|
||||||
// kubectl depends on k8s.io code, and at the time of writing, so
|
// implemented by api/internal/conflict.smPatchMergeOnlyDetector
|
||||||
// does kustomize. Code that imports k8s.io/api* cannot be imported
|
|
||||||
// back into k8s.io/*, yet kustomize appears inside k8s.io/kubectl.
|
|
||||||
//
|
|
||||||
// To allow kustomize to appear inside kubectl, yet still be developed
|
|
||||||
// outside kubectl, the kustomize code was divided into the following
|
|
||||||
// packages
|
|
||||||
//
|
|
||||||
// api/
|
|
||||||
// k8sdeps/ (and internal/ks8deps/)
|
|
||||||
// ifc/
|
|
||||||
// krusty/
|
|
||||||
// everythingElse/
|
|
||||||
//
|
|
||||||
// with the following rules:
|
|
||||||
//
|
|
||||||
// - Only k8sdeps/ may import k8s.io/api*.
|
|
||||||
//
|
|
||||||
// - Only krusty/ (and its internals) may import k8sdeps/.
|
|
||||||
// I.e., ifc/ and everythingElse/ must not
|
|
||||||
// import k8sdeps/ or k8s.io/api*.
|
|
||||||
//
|
|
||||||
// - Code in krusty/ may use code in k8sdeps/ to create
|
|
||||||
// objects then inject said objects into
|
|
||||||
// everythingElse/ behind dependency neutral interfaces.
|
|
||||||
//
|
|
||||||
// The idea was to periodically copy, not import, the large k8sdeps/
|
|
||||||
// tree (plus a snippet from krusty/kustomizer.go) into the kubectl
|
|
||||||
// codebase via a large PR, and have kubectl depend on the rest via
|
|
||||||
// normal importing.
|
|
||||||
//
|
|
||||||
// Over 2019, however, kubectl underwent large changes including
|
|
||||||
// a switch to Go modules, and a concerted attempt to extract kubectl
|
|
||||||
// from the k8s repo. This made large kustomize integration PRs too
|
|
||||||
// intrusive to review.
|
|
||||||
//
|
|
||||||
// In 2020, kubectl is based on Go modules, and almost entirely
|
|
||||||
// extracted from the k8s.io repositories, and further the kyaml
|
|
||||||
// library has a appeared as a viable replacement to k8s.io/api*
|
|
||||||
// KRM manipulation code.
|
|
||||||
//
|
|
||||||
// The new plan is to eliminate k8sdeps/ entirely, along with its
|
|
||||||
// k8s.io/api* dependence, allowing kustomize code to be imported
|
|
||||||
// into kubectl via normal Go module imports. Then the kustomize API
|
|
||||||
// code can then move into the github.com/kubernetes-sigs/cli-utils
|
|
||||||
// repo. The kustomize CLI in github.com/kubernetes-sigs/kustomize
|
|
||||||
// and the kubectl CLI can then both depend on the kustomize API.
|
|
||||||
//
|
|
||||||
// So, all code that depends on k8sdeps must go behind interfaces,
|
|
||||||
// and kustomize must be factored to choose the implementation.
|
|
||||||
//
|
|
||||||
// That problem has been reduced to three interfaces, each having
|
|
||||||
// two implementations. (1) is k8sdeps-based, (2) is kyaml-based.
|
|
||||||
//
|
|
||||||
// - ifc.Kunstructured
|
|
||||||
//
|
|
||||||
// 1) api/k8sdeps/kunstruct.UnstructAdapter
|
|
||||||
//
|
|
||||||
// This adapts structs in
|
|
||||||
// k8s.io/apimachinery/pkg/apis/meta/v1/unstructured
|
|
||||||
// to ifc.Kunstructured.
|
|
||||||
//
|
|
||||||
// 2) api/wrappy.WNode
|
|
||||||
//
|
|
||||||
// This adapts sigs.k8s.io/kustomize/kyaml/yaml.RNode
|
|
||||||
// to ifc.Unstructured.
|
|
||||||
//
|
|
||||||
// At time of writing, implementation started.
|
|
||||||
// Further reducing the size of ifc.Kunstructed
|
|
||||||
// would really reduce the work
|
|
||||||
// (e.g. drop Vars, drop ReplacementTranformer).
|
|
||||||
//
|
|
||||||
// - resource.ConflictDetector
|
|
||||||
//
|
|
||||||
// 1) api/internal/k8sdeps/conflict.conflictDetectorJson
|
|
||||||
// api/internal/k8sdeps/conflict.conflictDetectorSm
|
|
||||||
//
|
|
||||||
// Uses k8s.io/apimachinery/pkg/util/strategicpatch,
|
|
||||||
// apimachinery/pkg/util/mergepatch, etc. to merge
|
|
||||||
// resource.Resource instances.
|
|
||||||
//
|
|
||||||
// 2) api/internal/conflict.smPatchMergeOnlyDetector
|
|
||||||
//
|
//
|
||||||
// At time of writing, this doesn't report conflicts,
|
// At time of writing, this doesn't report conflicts,
|
||||||
// but it does know how to merge patches. Conflict
|
// but it does know how to merge patches. Conflict
|
||||||
@@ -113,49 +31,23 @@ import (
|
|||||||
// is plainly visible and usable in the output, even if
|
// is plainly visible and usable in the output, even if
|
||||||
// a conflict happened but wasn't reported as an error.
|
// a conflict happened but wasn't reported as an error.
|
||||||
//
|
//
|
||||||
// - ifc.Validator
|
// - ifc.Validator
|
||||||
//
|
//
|
||||||
// 1) api/k8sdeps/validator.KustValidator
|
// implemented by api/internal/validate.FieldValidator
|
||||||
//
|
|
||||||
// Uses k8s.io/apimachinery/pkg/api/validation and
|
|
||||||
// friends to validate strings.
|
|
||||||
//
|
|
||||||
// 2) api/internal/validate.FieldValidator
|
|
||||||
//
|
//
|
||||||
// See TODO inside the validator for status.
|
// See TODO inside the validator for status.
|
||||||
// At time of writing, this is a do-nothing
|
// At time of writing, this is a do-nothing
|
||||||
// validator as it's not critical to kustomize function.
|
// validator as it's not critical to kustomize function.
|
||||||
//
|
//
|
||||||
// Proposed plan:
|
|
||||||
// [x] Ship kustomize with the ability to switch from 1 to 2 via
|
|
||||||
// an --enable_kyaml flag.
|
|
||||||
// [x] Make --enable_kyaml true by default.
|
|
||||||
// [x] When 2 is not noticeably more buggy than 1, delete 1.
|
|
||||||
// I.e. delete k8sdeps/, transitively deleting all k8s.io/api* deps.
|
|
||||||
// This DepProvider should be left in place to retain these
|
|
||||||
// comments, but it will have only one choice.
|
|
||||||
// [x] The way is now clear to reintegrate into kubectl.
|
|
||||||
// This should be done ASAP; the last step is cleanup.
|
|
||||||
// [ ] Cleanup. With only one impl of Kunstructure remaining,
|
|
||||||
// that interface and WNode can be deleted, along with this
|
|
||||||
// DepProvider. The other two interfaces could be dropped too.
|
|
||||||
//
|
|
||||||
// When the above is done, kustomize will use yaml.RNode and/or
|
|
||||||
// KRM Config Functions directly and exclusively.
|
|
||||||
// If you're reading this, plan not done.
|
|
||||||
//
|
|
||||||
type DepProvider struct {
|
type DepProvider struct {
|
||||||
kFactory ifc.KunstructuredFactory
|
|
||||||
resourceFactory *resource.Factory
|
resourceFactory *resource.Factory
|
||||||
conflictDectectorFactory resource.ConflictDetectorFactory
|
conflictDectectorFactory resource.ConflictDetectorFactory
|
||||||
fieldValidator ifc.Validator
|
fieldValidator ifc.Validator
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDepProvider() *DepProvider {
|
func NewDepProvider() *DepProvider {
|
||||||
kf := &wrappy.WNodeFactory{}
|
rf := resource.NewFactory(&hasher.Hasher{})
|
||||||
rf := resource.NewFactory(kf)
|
|
||||||
return &DepProvider{
|
return &DepProvider{
|
||||||
kFactory: kf,
|
|
||||||
resourceFactory: rf,
|
resourceFactory: rf,
|
||||||
conflictDectectorFactory: conflict.NewFactory(),
|
conflictDectectorFactory: conflict.NewFactory(),
|
||||||
fieldValidator: validate.NewFieldValidator(),
|
fieldValidator: validate.NewFieldValidator(),
|
||||||
@@ -166,10 +58,6 @@ func NewDefaultDepProvider() *DepProvider {
|
|||||||
return NewDepProvider()
|
return NewDepProvider()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dp *DepProvider) GetKunstructuredFactory() ifc.KunstructuredFactory {
|
|
||||||
return dp.kFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dp *DepProvider) GetResourceFactory() *resource.Factory {
|
func (dp *DepProvider) GetResourceFactory() *resource.Factory {
|
||||||
return dp.resourceFactory
|
return dp.resourceFactory
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,14 @@ func ParseGroupVersion(apiVersion string) (group, version string) {
|
|||||||
// GvkFromString makes a Gvk from the output of Gvk.String().
|
// GvkFromString makes a Gvk from the output of Gvk.String().
|
||||||
func GvkFromString(s string) Gvk {
|
func GvkFromString(s string) Gvk {
|
||||||
values := strings.Split(s, fieldSep)
|
values := strings.Split(s, fieldSep)
|
||||||
|
if len(values) != 3 {
|
||||||
|
// ...then the string didn't come from Gvk.String().
|
||||||
|
return Gvk{
|
||||||
|
Group: noGroup,
|
||||||
|
Version: noVersion,
|
||||||
|
Kind: noKind,
|
||||||
|
}
|
||||||
|
}
|
||||||
g := values[0]
|
g := values[0]
|
||||||
if g == noGroup {
|
if g == noGroup {
|
||||||
g = ""
|
g = ""
|
||||||
@@ -213,7 +221,10 @@ func (x Gvk) toKyamlTypeMeta() yaml.TypeMeta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNamespaceableKind returns true if x is a namespaceable Gvk
|
// IsNamespaceableKind returns true if x is a namespaceable Gvk,
|
||||||
|
// e.g. instances of Pod and Deployment are namespaceable,
|
||||||
|
// but instances of Node and Namespace are not namespaceable.
|
||||||
|
// Alternative name for this method: IsNotClusterScoped.
|
||||||
// Implements https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#not-all-objects-are-in-a-namespace
|
// Implements https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#not-all-objects-are-in-a-namespace
|
||||||
func (x Gvk) IsNamespaceableKind() bool {
|
func (x Gvk) IsNamespaceableKind() bool {
|
||||||
isNamespaceScoped, found := openapi.IsNamespaceScoped(x.toKyamlTypeMeta())
|
isNamespaceScoped, found := openapi.IsNamespaceScoped(x.toKyamlTypeMeta())
|
||||||
|
|||||||
@@ -103,6 +103,12 @@ func TestString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGvkFromString(t *testing.T) {
|
||||||
|
for _, hey := range stringTests {
|
||||||
|
assert.Equal(t, hey.x, GvkFromString(hey.s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestApiVersion(t *testing.T) {
|
func TestApiVersion(t *testing.T) {
|
||||||
for _, hey := range []struct {
|
for _, hey := range []struct {
|
||||||
x Gvk
|
x Gvk
|
||||||
|
|||||||
@@ -146,18 +146,10 @@ func newResMapFromResourceSlice(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewResMapFromRNodeSlice returns a ResMap from a slice of RNodes
|
// NewResMapFromRNodeSlice returns a ResMap from a slice of RNodes
|
||||||
func (rmF *Factory) NewResMapFromRNodeSlice(rnodes []*yaml.RNode) (ResMap, error) {
|
func (rmF *Factory) NewResMapFromRNodeSlice(s []*yaml.RNode) (ResMap, error) {
|
||||||
var resources []*resource.Resource
|
rs, err := rmF.resF.ResourcesFromRNodes(s)
|
||||||
for _, rnode := range rnodes {
|
if err != nil {
|
||||||
s, err := rnode.String()
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r, err := rmF.resF.SliceFromBytes([]byte(s))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resources = append(resources, r...)
|
|
||||||
}
|
}
|
||||||
return newResMapFromResourceSlice(resources)
|
return newResMapFromResourceSlice(rs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -336,17 +336,18 @@ metadata:
|
|||||||
expected: resmaptest_test.NewRmBuilder(t, rf).ResMap(),
|
expected: resmaptest_test.NewRmBuilder(t, rf).ResMap(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for name, tc := range testcases {
|
for name := range testcases {
|
||||||
rnodes := []*yaml.RNode{
|
tc := testcases[name]
|
||||||
yaml.MustParse(tc.input),
|
t.Run(name, func(t *testing.T) {
|
||||||
}
|
rm, err := rmF.NewResMapFromRNodeSlice(
|
||||||
rm, err := rmF.NewResMapFromRNodeSlice(rnodes)
|
[]*yaml.RNode{yaml.MustParse(tc.input)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error in test case [%s]: %v", name, err)
|
t.Fatalf("unexpected error in test case [%s]: %v", name, err)
|
||||||
}
|
}
|
||||||
if err = tc.expected.ErrorIfNotEqualLists(rm); err != nil {
|
if err = tc.expected.ErrorIfNotEqualLists(rm); err != nil {
|
||||||
t.Fatalf("error in test case [%s]: %s", name, err)
|
t.Fatalf("error in test case [%s]: %s", name, err)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ type TransformerPlugin interface {
|
|||||||
// resource to transform, try the OrgId first, and if this
|
// resource to transform, try the OrgId first, and if this
|
||||||
// fails or finds too many, it might make sense to then try
|
// fails or finds too many, it might make sense to then try
|
||||||
// the CurrId. Depends on the situation.
|
// the CurrId. Depends on the situation.
|
||||||
|
//
|
||||||
|
// TODO: get rid of this interface (use bare resWrangler).
|
||||||
|
// There aren't multiple implementations any more.
|
||||||
type ResMap interface {
|
type ResMap interface {
|
||||||
// Size reports the number of resources.
|
// Size reports the number of resources.
|
||||||
Size() int
|
Size() int
|
||||||
@@ -189,6 +192,9 @@ type ResMap interface {
|
|||||||
// Clear removes all resources and Ids.
|
// Clear removes all resources and Ids.
|
||||||
Clear()
|
Clear()
|
||||||
|
|
||||||
|
// DropEmpties drops empty resources from the ResMap.
|
||||||
|
DropEmpties()
|
||||||
|
|
||||||
// SubsetThatCouldBeReferencedByResource returns a ResMap subset
|
// SubsetThatCouldBeReferencedByResource returns a ResMap subset
|
||||||
// of self with resources that could be referenced by the
|
// of self with resources that could be referenced by the
|
||||||
// resource argument.
|
// resource argument.
|
||||||
@@ -231,9 +237,8 @@ type ResMap interface {
|
|||||||
// are selected by a Selector
|
// are selected by a Selector
|
||||||
Select(types.Selector) ([]*resource.Resource, error)
|
Select(types.Selector) ([]*resource.Resource, error)
|
||||||
|
|
||||||
// ToRNodeSlice converts the resources in the resmp
|
// ToRNodeSlice returns a copy of the resources as RNodes.
|
||||||
// to a list of RNodes
|
ToRNodeSlice() []*yaml.RNode
|
||||||
ToRNodeSlice() ([]*yaml.RNode, error)
|
|
||||||
|
|
||||||
// ApplySmPatch applies a strategic-merge patch to the
|
// ApplySmPatch applies a strategic-merge patch to the
|
||||||
// selected set of resources.
|
// selected set of resources.
|
||||||
|
|||||||
@@ -6,14 +6,12 @@ package resmap
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
kyaml_yaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// resWrangler implements ResMap.
|
// resWrangler implements ResMap.
|
||||||
@@ -38,6 +36,18 @@ func (m *resWrangler) Clear() {
|
|||||||
m.rList = nil
|
m.rList = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DropEmpties quickly drops empty resources.
|
||||||
|
// It doesn't use Append, which checks for Id collisions.
|
||||||
|
func (m *resWrangler) DropEmpties() {
|
||||||
|
var rList []*resource.Resource
|
||||||
|
for _, r := range m.rList {
|
||||||
|
if !r.IsEmpty() {
|
||||||
|
rList = append(rList, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.rList = rList
|
||||||
|
}
|
||||||
|
|
||||||
// Size implements ResMap.
|
// Size implements ResMap.
|
||||||
func (m *resWrangler) Size() int {
|
func (m *resWrangler) Size() int {
|
||||||
return len(m.rList)
|
return len(m.rList)
|
||||||
@@ -66,22 +76,27 @@ func (m *resWrangler) Append(res *resource.Resource) error {
|
|||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"may not add resource with an already registered id: %s", id)
|
"may not add resource with an already registered id: %s", id)
|
||||||
}
|
}
|
||||||
m.rList = append(m.rList, res)
|
m.append(res)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// append appends without performing an Id check
|
||||||
|
func (m *resWrangler) append(res *resource.Resource) {
|
||||||
|
m.rList = append(m.rList, res)
|
||||||
|
}
|
||||||
|
|
||||||
// Remove implements ResMap.
|
// Remove implements ResMap.
|
||||||
func (m *resWrangler) Remove(adios resid.ResId) error {
|
func (m *resWrangler) Remove(adios resid.ResId) error {
|
||||||
tmp := newOne()
|
var rList []*resource.Resource
|
||||||
for _, r := range m.rList {
|
for _, r := range m.rList {
|
||||||
if r.CurId() != adios {
|
if r.CurId() != adios {
|
||||||
tmp.Append(r)
|
rList = append(rList, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tmp.Size() != m.Size()-1 {
|
if len(rList) != m.Size()-1 {
|
||||||
return fmt.Errorf("id %s not found in removal", adios)
|
return fmt.Errorf("id %s not found in removal", adios)
|
||||||
}
|
}
|
||||||
m.rList = tmp.rList
|
m.rList = rList
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,16 +133,7 @@ func (m *resWrangler) Debug(title string) {
|
|||||||
} else {
|
} else {
|
||||||
fmt.Println("---")
|
fmt.Println("---")
|
||||||
}
|
}
|
||||||
fmt.Printf("# %d %s\n", i, r.OrgId())
|
fmt.Printf("# %d %s\n%s\n", i, r.OrgId(), r.String())
|
||||||
m, err := r.Map()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
blob, err := yaml.Marshal(m)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Println(string(blob))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +279,7 @@ func (m *resWrangler) AsYaml() ([]byte, error) {
|
|||||||
firstObj := true
|
firstObj := true
|
||||||
var b []byte
|
var b []byte
|
||||||
buf := bytes.NewBuffer(b)
|
buf := bytes.NewBuffer(b)
|
||||||
for _, res := range m.Resources() {
|
for _, res := range m.rList {
|
||||||
out, err := res.AsYAML()
|
out, err := res.AsYAML()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m, _ := res.Map()
|
m, _ := res.Map()
|
||||||
@@ -297,7 +303,7 @@ func (m *resWrangler) AsYaml() ([]byte, error) {
|
|||||||
func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
|
func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
|
||||||
m2, ok := other.(*resWrangler)
|
m2, ok := other.(*resWrangler)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("bad cast")
|
return fmt.Errorf("bad cast to resWrangler 1")
|
||||||
}
|
}
|
||||||
if m.Size() != m2.Size() {
|
if m.Size() != m2.Size() {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
@@ -317,9 +323,9 @@ func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
|
|||||||
"id in self matches %d in other; id: %s", len(others), id)
|
"id in self matches %d in other; id: %s", len(others), id)
|
||||||
}
|
}
|
||||||
r2 := others[0]
|
r2 := others[0]
|
||||||
if !r1.KunstructEqual(r2) {
|
if !r1.NodeEqual(r2) {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"kunstruct not equal: \n -- %s,\n -- %s\n\n--\n%#v\n------\n%#v\n",
|
"nodes unequal: \n -- %s,\n -- %s\n\n--\n%#v\n------\n%#v\n",
|
||||||
r1, r2, r1, r2)
|
r1, r2, r1, r2)
|
||||||
}
|
}
|
||||||
seen[m2.indexOfResource(r2)] = true
|
seen[m2.indexOfResource(r2)] = true
|
||||||
@@ -334,7 +340,7 @@ func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
|
|||||||
func (m *resWrangler) ErrorIfNotEqualLists(other ResMap) error {
|
func (m *resWrangler) ErrorIfNotEqualLists(other ResMap) error {
|
||||||
m2, ok := other.(*resWrangler)
|
m2, ok := other.(*resWrangler)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("bad cast")
|
return fmt.Errorf("bad cast to resWrangler 2")
|
||||||
}
|
}
|
||||||
if m.Size() != m2.Size() {
|
if m.Size() != m2.Size() {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
@@ -388,7 +394,7 @@ func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
|
|||||||
}
|
}
|
||||||
result := newOne()
|
result := newOne()
|
||||||
roleBindingNamespaces := getNamespacesForRoleBinding(referrer)
|
roleBindingNamespaces := getNamespacesForRoleBinding(referrer)
|
||||||
for _, possibleTarget := range m.Resources() {
|
for _, possibleTarget := range m.rList {
|
||||||
id := possibleTarget.CurId()
|
id := possibleTarget.CurId()
|
||||||
if !id.IsNamespaceableKind() {
|
if !id.IsNamespaceableKind() {
|
||||||
// A cluster-scoped resource can be referred to by anything.
|
// A cluster-scoped resource can be referred to by anything.
|
||||||
@@ -435,16 +441,21 @@ func getNamespacesForRoleBinding(r *resource.Resource) map[string]bool {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *resWrangler) append(res *resource.Resource) {
|
|
||||||
m.rList = append(m.rList, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendAll implements ResMap.
|
// AppendAll implements ResMap.
|
||||||
func (m *resWrangler) AppendAll(other ResMap) error {
|
func (m *resWrangler) AppendAll(other ResMap) error {
|
||||||
if other == nil {
|
if other == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, res := range other.Resources() {
|
m2, ok := other.(*resWrangler)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("bad cast to resWrangler 3")
|
||||||
|
}
|
||||||
|
return m.appendAll(m2.rList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendAll appends all the resources, error on Id collision.
|
||||||
|
func (m *resWrangler) appendAll(list []*resource.Resource) error {
|
||||||
|
for _, res := range list {
|
||||||
if err := m.Append(res); err != nil {
|
if err := m.Append(res); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -457,7 +468,11 @@ func (m *resWrangler) AbsorbAll(other ResMap) error {
|
|||||||
if other == nil {
|
if other == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, r := range other.Resources() {
|
m2, ok := other.(*resWrangler)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("bad cast to resWrangler 4")
|
||||||
|
}
|
||||||
|
for _, r := range m2.rList {
|
||||||
err := m.appendReplaceOrMerge(r)
|
err := m.appendReplaceOrMerge(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -522,7 +537,7 @@ func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, r := range m.Resources() {
|
for _, r := range m.rList {
|
||||||
curId := r.CurId()
|
curId := r.CurId()
|
||||||
orgId := r.OrgId()
|
orgId := r.OrgId()
|
||||||
|
|
||||||
@@ -567,78 +582,39 @@ func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToRNodeSlice converts the resources in the resmp
|
// ToRNodeSlice returns a copy of the resources as RNodes.
|
||||||
// to a list of RNodes
|
func (m *resWrangler) ToRNodeSlice() []*kyaml.RNode {
|
||||||
func (m *resWrangler) ToRNodeSlice() ([]*kyaml_yaml.RNode, error) {
|
result := make([]*kyaml.RNode, len(m.rList))
|
||||||
var rnodes []*kyaml_yaml.RNode
|
for i := range m.rList {
|
||||||
for _, r := range m.Resources() {
|
result[i] = m.rList[i].AsRNode()
|
||||||
s, err := r.AsYAML()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rnode, err := kyaml_yaml.Parse(string(s))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rnodes = append(rnodes, rnode)
|
|
||||||
}
|
}
|
||||||
return rnodes, nil
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplySmPatch applies the patch, and errors on Id collisions.
|
||||||
func (m *resWrangler) ApplySmPatch(
|
func (m *resWrangler) ApplySmPatch(
|
||||||
selectedSet *resource.IdSet, patch *resource.Resource) error {
|
selectedSet *resource.IdSet, patch *resource.Resource) error {
|
||||||
newRm := New()
|
var list []*resource.Resource
|
||||||
for _, res := range m.Resources() {
|
for _, res := range m.rList {
|
||||||
if !selectedSet.Contains(res.CurId()) {
|
if selectedSet.Contains(res.CurId()) {
|
||||||
newRm.Append(res)
|
patchCopy := patch.DeepCopy()
|
||||||
continue
|
patchCopy.CopyMergeMetaDataFieldsFrom(patch)
|
||||||
}
|
patchCopy.SetGvk(res.GetGvk())
|
||||||
patchCopy := patch.DeepCopy()
|
patchCopy.SetKind(patch.GetKind())
|
||||||
patchCopy.CopyMergeMetaDataFieldsFrom(patch)
|
if err := res.ApplySmPatch(patchCopy); err != nil {
|
||||||
patchCopy.SetGvk(res.GetGvk())
|
|
||||||
patchCopy.SetKind(patch.GetKind())
|
|
||||||
err := res.ApplySmPatch(patchCopy)
|
|
||||||
if err != nil {
|
|
||||||
// Check for an error string from UnmarshalJSON that's indicative
|
|
||||||
// of an object that's missing basic KRM fields, and thus may have been
|
|
||||||
// entirely deleted (an acceptable outcome). This error handling should
|
|
||||||
// be deleted along with use of ResMap and apimachinery functions like
|
|
||||||
// UnmarshalJSON.
|
|
||||||
if !strings.Contains(err.Error(), "Object 'Kind' is missing") {
|
|
||||||
// Some unknown error, let it through.
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
empty, err := res.IsEmpty()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !empty {
|
|
||||||
m, _ := res.Map()
|
|
||||||
return errors.Wrapf(
|
|
||||||
err, "with unexpectedly non-empty object map of size %d",
|
|
||||||
len(m))
|
|
||||||
}
|
|
||||||
// Fall through to handle deleted object.
|
|
||||||
}
|
}
|
||||||
empty, err := res.IsEmpty()
|
if !res.IsEmpty() {
|
||||||
if err != nil {
|
list = append(list, res)
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !empty {
|
|
||||||
// IsEmpty means all fields have been removed from the object.
|
|
||||||
// This can happen if a patch required deletion of the
|
|
||||||
// entire resource (not just a part of it). This means
|
|
||||||
// the overall resmap must shrink by one.
|
|
||||||
newRm.Append(res)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.Clear()
|
m.Clear()
|
||||||
m.AppendAll(newRm)
|
return m.appendAll(list)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *resWrangler) RemoveBuildAnnotations() {
|
func (m *resWrangler) RemoveBuildAnnotations() {
|
||||||
for _, r := range m.Resources() {
|
for _, r := range m.rList {
|
||||||
r.RemoveBuildAnnotations()
|
r.RemoveBuildAnnotations()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -877,13 +877,8 @@ rules:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
rnodes, err := rm.ToRNodeSlice()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b := bytes.NewBufferString("")
|
b := bytes.NewBufferString("")
|
||||||
for i, n := range rnodes {
|
for i, n := range rm.ToRNodeSlice() {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
b.WriteString("---\n")
|
b.WriteString("---\n")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,28 +10,33 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
|
"sigs.k8s.io/kustomize/api/internal/generators"
|
||||||
"sigs.k8s.io/kustomize/api/internal/kusterr"
|
"sigs.k8s.io/kustomize/api/internal/kusterr"
|
||||||
|
"sigs.k8s.io/kustomize/api/konfig"
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Factory makes instances of Resource.
|
// Factory makes instances of Resource.
|
||||||
type Factory struct {
|
type Factory struct {
|
||||||
kf ifc.KunstructuredFactory
|
hasher ifc.KustHasher
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFactory makes an instance of Factory.
|
// NewFactory makes an instance of Factory.
|
||||||
func NewFactory(kf ifc.KunstructuredFactory) *Factory {
|
func NewFactory(h ifc.KustHasher) *Factory {
|
||||||
return &Factory{kf: kf}
|
return &Factory{hasher: h}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rf *Factory) Hasher() ifc.KunstructuredHasher {
|
// Hasher returns an ifc.KustHasher
|
||||||
return rf.kf.Hasher()
|
func (rf *Factory) Hasher() ifc.KustHasher {
|
||||||
|
return rf.hasher
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromMap returns a new instance of Resource.
|
// FromMap returns a new instance of Resource.
|
||||||
func (rf *Factory) FromMap(m map[string]interface{}) *Resource {
|
func (rf *Factory) FromMap(m map[string]interface{}) *Resource {
|
||||||
return rf.makeOne(rf.kf.FromMap(m), nil)
|
return rf.FromMapAndOption(m, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromMapWithName returns a new instance with the given "original" name.
|
// FromMapWithName returns a new instance with the given "original" name.
|
||||||
@@ -41,35 +46,30 @@ func (rf *Factory) FromMapWithName(n string, m map[string]interface{}) *Resource
|
|||||||
|
|
||||||
// FromMapWithNamespaceAndName returns a new instance with the given "original" namespace.
|
// FromMapWithNamespaceAndName returns a new instance with the given "original" namespace.
|
||||||
func (rf *Factory) FromMapWithNamespaceAndName(ns string, n string, m map[string]interface{}) *Resource {
|
func (rf *Factory) FromMapWithNamespaceAndName(ns string, n string, m map[string]interface{}) *Resource {
|
||||||
r := rf.makeOne(rf.kf.FromMap(m), nil)
|
r := rf.FromMapAndOption(m, nil)
|
||||||
return r.setPreviousId(ns, n, r.GetKind())
|
return r.setPreviousId(ns, n, r.GetKind())
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromMapAndOption returns a new instance of Resource with given options.
|
// FromMapAndOption returns a new instance of Resource with given options.
|
||||||
func (rf *Factory) FromMapAndOption(
|
func (rf *Factory) FromMapAndOption(
|
||||||
m map[string]interface{}, args *types.GeneratorArgs) *Resource {
|
m map[string]interface{}, args *types.GeneratorArgs) *Resource {
|
||||||
return rf.makeOne(rf.kf.FromMap(m), types.NewGenArgs(args))
|
n, err := yaml.FromMap(m)
|
||||||
}
|
if err != nil {
|
||||||
|
// TODO: return err instead of log.
|
||||||
// FromKunstructured returns a new instance of Resource.
|
log.Fatal(err)
|
||||||
func (rf *Factory) FromKunstructured(u ifc.Kunstructured) *Resource {
|
}
|
||||||
return rf.makeOne(u, nil)
|
return rf.makeOne(n, types.NewGenArgs(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeOne returns a new instance of Resource.
|
// makeOne returns a new instance of Resource.
|
||||||
func (rf *Factory) makeOne(
|
func (rf *Factory) makeOne(rn *yaml.RNode, o *types.GenArgs) *Resource {
|
||||||
u ifc.Kunstructured, o *types.GenArgs) *Resource {
|
if rn == nil {
|
||||||
if u == nil {
|
log.Fatal("RNode must not be null")
|
||||||
log.Fatal("unstruct ifc must not be null")
|
|
||||||
}
|
}
|
||||||
if o == nil {
|
if o == nil {
|
||||||
o = types.NewGenArgs(nil)
|
o = types.NewGenArgs(nil)
|
||||||
}
|
}
|
||||||
r := &Resource{
|
return &Resource{node: rn, options: o}
|
||||||
kunStr: u,
|
|
||||||
options: o,
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SliceFromPatches returns a slice of resources given a patch path
|
// SliceFromPatches returns a slice of resources given a patch path
|
||||||
@@ -106,47 +106,135 @@ func (rf *Factory) FromBytes(in []byte) (*Resource, error) {
|
|||||||
|
|
||||||
// SliceFromBytes unmarshals bytes into a Resource slice.
|
// SliceFromBytes unmarshals bytes into a Resource slice.
|
||||||
func (rf *Factory) SliceFromBytes(in []byte) ([]*Resource, error) {
|
func (rf *Factory) SliceFromBytes(in []byte) ([]*Resource, error) {
|
||||||
kunStructs, err := rf.kf.SliceFromBytes(in)
|
nodes, err := rf.RNodesFromBytes(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var result []*Resource
|
return rf.resourcesFromRNodes(nodes), nil
|
||||||
for len(kunStructs) > 0 {
|
}
|
||||||
u := kunStructs[0]
|
|
||||||
kunStructs = kunStructs[1:]
|
// ResourcesFromRNodes converts RNodes to Resources.
|
||||||
if strings.HasSuffix(u.GetKind(), "List") {
|
func (rf *Factory) ResourcesFromRNodes(
|
||||||
m, err := u.Map()
|
nodes []*yaml.RNode) (result []*Resource, err error) {
|
||||||
if err != nil {
|
nodes, err = rf.dropBadNodes(nodes)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rf.resourcesFromRNodes(nodes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resourcesFromRNode assumes all nodes are good.
|
||||||
|
func (rf *Factory) resourcesFromRNodes(
|
||||||
|
nodes []*yaml.RNode) (result []*Resource) {
|
||||||
|
for _, n := range nodes {
|
||||||
|
result = append(result, rf.makeOne(n, nil))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rf *Factory) RNodesFromBytes(b []byte) (result []*yaml.RNode, err error) {
|
||||||
|
nodes, err := kio.FromBytes(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodes, err = rf.dropBadNodes(nodes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for len(nodes) > 0 {
|
||||||
|
n0 := nodes[0]
|
||||||
|
nodes = nodes[1:]
|
||||||
|
kind := n0.GetKind()
|
||||||
|
if !strings.HasSuffix(kind, "List") {
|
||||||
|
result = append(result, n0)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Convert a FooList into a slice of Foo.
|
||||||
|
var m map[string]interface{}
|
||||||
|
m, err = n0.Map()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items, ok := m["items"]
|
||||||
|
if !ok {
|
||||||
|
// treat as an empty list
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
slice, ok := items.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
if items == nil {
|
||||||
|
// an empty list
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
items := m["items"]
|
return nil, fmt.Errorf(
|
||||||
itemsSlice, ok := items.([]interface{})
|
"expected array in %s/items, but found %T", kind, items)
|
||||||
if !ok {
|
}
|
||||||
if items == nil {
|
innerNodes, err := rf.convertObjectSliceToNodeSlice(slice)
|
||||||
// an empty list
|
if err != nil {
|
||||||
continue
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("items in List is type %T, expected array", items)
|
nodes = append(nodes, innerNodes...)
|
||||||
}
|
}
|
||||||
for _, item := range itemsSlice {
|
return result, nil
|
||||||
itemJSON, err := json.Marshal(item)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
// convertObjectSlice converts a list of objects to a list of RNode.
|
||||||
}
|
func (rf *Factory) convertObjectSliceToNodeSlice(
|
||||||
innerU, err := rf.kf.SliceFromBytes(itemJSON)
|
objects []interface{}) (result []*yaml.RNode, err error) {
|
||||||
if err != nil {
|
var bytes []byte
|
||||||
return nil, err
|
var nodes []*yaml.RNode
|
||||||
}
|
for _, obj := range objects {
|
||||||
// append innerU to kunStructs so nested Lists can be handled
|
bytes, err = json.Marshal(obj)
|
||||||
kunStructs = append(kunStructs, innerU...)
|
if err != nil {
|
||||||
}
|
return
|
||||||
} else {
|
}
|
||||||
result = append(result, rf.FromKunstructured(u))
|
nodes, err = kio.FromBytes(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nodes, err = rf.dropBadNodes(nodes)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result = append(result, nodes...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// dropBadNodes may drop some nodes from its input argument.
|
||||||
|
func (rf *Factory) dropBadNodes(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
|
var result []*yaml.RNode
|
||||||
|
for _, n := range nodes {
|
||||||
|
ignore, err := rf.shouldIgnore(n)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !ignore {
|
||||||
|
result = append(result, n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shouldIgnore returns true if there's some reason to ignore the node.
|
||||||
|
func (rf *Factory) shouldIgnore(n *yaml.RNode) (bool, error) {
|
||||||
|
if n.IsNilOrEmpty() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
md, err := n.GetValidatedMetadata()
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
_, ignore := md.ObjectMeta.Annotations[konfig.IgnoredByKustomizeAnnotation]
|
||||||
|
if ignore {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if foundNil, path := n.HasNilEntryInList(); foundNil {
|
||||||
|
return true, fmt.Errorf("empty item at %v in object %v", path, n)
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SliceFromBytesWithNames unmarshals bytes into a Resource slice with specified original
|
// SliceFromBytesWithNames unmarshals bytes into a Resource slice with specified original
|
||||||
// name.
|
// name.
|
||||||
func (rf *Factory) SliceFromBytesWithNames(names []string, in []byte) ([]*Resource, error) {
|
func (rf *Factory) SliceFromBytesWithNames(names []string, in []byte) ([]*Resource, error) {
|
||||||
@@ -165,18 +253,18 @@ func (rf *Factory) SliceFromBytesWithNames(names []string, in []byte) ([]*Resour
|
|||||||
|
|
||||||
// MakeConfigMap makes an instance of Resource for ConfigMap
|
// MakeConfigMap makes an instance of Resource for ConfigMap
|
||||||
func (rf *Factory) MakeConfigMap(kvLdr ifc.KvLoader, args *types.ConfigMapArgs) (*Resource, error) {
|
func (rf *Factory) MakeConfigMap(kvLdr ifc.KvLoader, args *types.ConfigMapArgs) (*Resource, error) {
|
||||||
u, err := rf.kf.MakeConfigMap(kvLdr, args)
|
rn, err := generators.MakeConfigMap(kvLdr, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return rf.makeOne(u, types.NewGenArgs(&args.GeneratorArgs)), nil
|
return rf.makeOne(rn, types.NewGenArgs(&args.GeneratorArgs)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeSecret makes an instance of Resource for Secret
|
// MakeSecret makes an instance of Resource for Secret
|
||||||
func (rf *Factory) MakeSecret(kvLdr ifc.KvLoader, args *types.SecretArgs) (*Resource, error) {
|
func (rf *Factory) MakeSecret(kvLdr ifc.KvLoader, args *types.SecretArgs) (*Resource, error) {
|
||||||
u, err := rf.kf.MakeSecret(kvLdr, args)
|
rn, err := generators.MakeSecret(kvLdr, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return rf.makeOne(u, types.NewGenArgs(&args.GeneratorArgs)), nil
|
return rf.makeOne(rn, types.NewGenArgs(&args.GeneratorArgs)), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package resource_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -151,35 +152,33 @@ spec:
|
|||||||
for name := range testCases {
|
for name := range testCases {
|
||||||
tc := testCases[name]
|
tc := testCases[name]
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
result, err := factory.SliceFromBytes([]byte(tc.input))
|
result, err := factory.RNodesFromBytes([]byte(tc.input))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%v: fails with err: %v", name, err)
|
t.Fatalf("%v: fails with err: %v", name, err)
|
||||||
}
|
}
|
||||||
if len(result) != len(tc.expected) {
|
if len(result) != len(tc.expected) {
|
||||||
for i := range result {
|
for i := range result {
|
||||||
bytes, err := result[i].AsYAML()
|
str, err := result[i].String()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%v: result to YAML fails with err: %v", name, err)
|
t.Fatalf("%v: result to YAML fails with err: %v", name, err)
|
||||||
}
|
}
|
||||||
tmp := string(bytes)
|
t.Logf("--- %d:\n%s", i, str)
|
||||||
t.Logf("--- %d:\n%s", i, tmp)
|
|
||||||
}
|
}
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"%v: actual len %d != expected len %d",
|
"%v: actual len %d != expected len %d",
|
||||||
name, len(result), len(tc.expected))
|
name, len(result), len(tc.expected))
|
||||||
}
|
}
|
||||||
for i := range tc.expected {
|
for i := range tc.expected {
|
||||||
bytes, err := result[i].AsYAML()
|
str, err := result[i].String()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%v: result to YAML fails with err: %v", name, err)
|
t.Fatalf("%v: result to YAML fails with err: %v", name, err)
|
||||||
}
|
}
|
||||||
tmp := string(bytes)
|
if str != tc.expected[i] {
|
||||||
if tmp != tc.expected[i] {
|
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"%v: string mismatch in item %d\n"+
|
"%v: string mismatch in item %d\n"+
|
||||||
"actual:\n-----\n%s\n-----\n"+
|
"actual:\n-----\n%s\n-----\n"+
|
||||||
"expected:\n-----\n%s\n-----\n",
|
"expected:\n-----\n%s\n-----\n",
|
||||||
name, i, tmp, tc.expected[i])
|
name, i, str, tc.expected[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -307,76 +306,376 @@ kind: List
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := map[string]struct {
|
||||||
name string
|
|
||||||
input []types.PatchStrategicMerge
|
input []types.PatchStrategicMerge
|
||||||
expectedOut []*Resource
|
expectedOut []*Resource
|
||||||
expectedErr bool
|
expectedErr bool
|
||||||
}{
|
}{
|
||||||
{
|
"happy": {
|
||||||
name: "happy",
|
|
||||||
input: []types.PatchStrategicMerge{patchGood1, patchGood2},
|
input: []types.PatchStrategicMerge{patchGood1, patchGood2},
|
||||||
expectedOut: []*Resource{testDeployment, testConfigMap},
|
expectedOut: []*Resource{testDeployment, testConfigMap},
|
||||||
expectedErr: false,
|
expectedErr: false,
|
||||||
},
|
},
|
||||||
{
|
"badFileName": {
|
||||||
name: "badFileName",
|
|
||||||
input: []types.PatchStrategicMerge{patchGood1, "doesNotExist"},
|
input: []types.PatchStrategicMerge{patchGood1, "doesNotExist"},
|
||||||
expectedOut: []*Resource{},
|
expectedOut: []*Resource{},
|
||||||
expectedErr: true,
|
expectedErr: true,
|
||||||
},
|
},
|
||||||
{
|
"badData": {
|
||||||
name: "badData",
|
|
||||||
input: []types.PatchStrategicMerge{patchGood1, patchBad},
|
input: []types.PatchStrategicMerge{patchGood1, patchBad},
|
||||||
expectedOut: []*Resource{},
|
expectedOut: []*Resource{},
|
||||||
expectedErr: true,
|
expectedErr: true,
|
||||||
},
|
},
|
||||||
{
|
"listOfPatches": {
|
||||||
name: "listOfPatches",
|
|
||||||
input: []types.PatchStrategicMerge{patchList},
|
input: []types.PatchStrategicMerge{patchList},
|
||||||
expectedOut: []*Resource{testDeployment, testConfigMap},
|
expectedOut: []*Resource{testDeployment, testConfigMap},
|
||||||
expectedErr: false,
|
expectedErr: false,
|
||||||
},
|
},
|
||||||
{
|
"listWithAnchorReference": {
|
||||||
name: "listWithAnchorReference",
|
|
||||||
input: []types.PatchStrategicMerge{patchList2},
|
input: []types.PatchStrategicMerge{patchList2},
|
||||||
expectedOut: []*Resource{testDeploymentA, testDeploymentB},
|
expectedOut: []*Resource{testDeploymentA, testDeploymentB},
|
||||||
// The error using kyaml is:
|
// The error using kyaml is:
|
||||||
// json: unsupported type: map[interface {}]interface {}
|
// json: unsupported type: map[interface {}]interface {}
|
||||||
// maybe arising from too many conversions between
|
// maybe arising from too many conversions between
|
||||||
// yaml, json, Resource, RNode, Unstructured etc.
|
// yaml, json, Resource, RNode, etc.
|
||||||
// These conversions go away after closing #3506
|
// These conversions go away after closing #3506
|
||||||
// TODO(#3271) This shouldn't have an error, but does when kyaml is used.
|
// TODO(#3271) This shouldn't have an error, but does when kyaml is used.
|
||||||
expectedErr: true,
|
expectedErr: true,
|
||||||
},
|
},
|
||||||
{
|
"listWithNoEntries": {
|
||||||
name: "listWithNoEntries",
|
|
||||||
input: []types.PatchStrategicMerge{patchList3},
|
input: []types.PatchStrategicMerge{patchList3},
|
||||||
expectedOut: []*Resource{},
|
expectedOut: []*Resource{},
|
||||||
expectedErr: false,
|
expectedErr: false,
|
||||||
},
|
},
|
||||||
{
|
"listWithNoItems": {
|
||||||
name: "listWithNoItems",
|
|
||||||
input: []types.PatchStrategicMerge{patchList4},
|
input: []types.PatchStrategicMerge{patchList4},
|
||||||
expectedOut: []*Resource{},
|
expectedOut: []*Resource{},
|
||||||
expectedErr: false,
|
expectedErr: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for n, test := range tests {
|
||||||
rs, err := factory.SliceFromPatches(ldr, test.input)
|
t.Run(n, func(t *testing.T) {
|
||||||
if err != nil {
|
rs, err := factory.SliceFromPatches(ldr, test.input)
|
||||||
assert.True(t, test.expectedErr,
|
if err != nil {
|
||||||
fmt.Sprintf("in test %s, got unexpected error: %v", test.name, err))
|
assert.True(t, test.expectedErr,
|
||||||
continue
|
fmt.Sprintf("in test %s, got unexpected error: %v", n, err))
|
||||||
}
|
return
|
||||||
assert.False(t, test.expectedErr, "expected no error in "+test.name)
|
}
|
||||||
assert.Equal(t, len(test.expectedOut), len(rs))
|
assert.False(t, test.expectedErr, "expected no error in "+n)
|
||||||
for i := range rs {
|
assert.Equal(t, len(test.expectedOut), len(rs))
|
||||||
expYaml, err := test.expectedOut[i].AsYAML()
|
for i := range rs {
|
||||||
assert.NoError(t, err)
|
expYaml, err := test.expectedOut[i].AsYAML()
|
||||||
actYaml, err := rs[i].AsYAML()
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, err)
|
actYaml, err := rs[i].AsYAML()
|
||||||
assert.Equal(t, expYaml, actYaml)
|
assert.NoError(t, err)
|
||||||
}
|
assert.Equal(t, expYaml, actYaml)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHash(t *testing.T) {
|
||||||
|
input := `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
data:
|
||||||
|
one: ""
|
||||||
|
binaryData:
|
||||||
|
two: ""
|
||||||
|
`
|
||||||
|
expect := "698h7c7t9m"
|
||||||
|
k, err := factory.SliceFromBytes([]byte(input))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
result, err := k[0].Hash(factory.Hasher())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if result != expect {
|
||||||
|
t.Fatalf("expect %s but got %s", expect, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSliceFromBytesMore(t *testing.T) {
|
||||||
|
testConfigMap :=
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "winnie",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testDeploymentSpec := map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"hostAliases": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"hostnames": []interface{}{
|
||||||
|
"a.example.com",
|
||||||
|
},
|
||||||
|
"ip": "8.8.8.8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testDeploymentA := map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deployment-a",
|
||||||
|
},
|
||||||
|
"spec": testDeploymentSpec,
|
||||||
|
}
|
||||||
|
testDeploymentB := map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deployment-b",
|
||||||
|
},
|
||||||
|
"spec": testDeploymentSpec,
|
||||||
|
}
|
||||||
|
testDeploymentList :=
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "DeploymentList",
|
||||||
|
"items": []interface{}{
|
||||||
|
testDeploymentA,
|
||||||
|
testDeploymentB,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type expected struct {
|
||||||
|
out []map[string]interface{}
|
||||||
|
isErr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
input []byte
|
||||||
|
exp expected
|
||||||
|
}{
|
||||||
|
"garbage": {
|
||||||
|
input: []byte("garbageIn: garbageOut"),
|
||||||
|
exp: expected{
|
||||||
|
isErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"noBytes": {
|
||||||
|
input: []byte{},
|
||||||
|
exp: expected{
|
||||||
|
out: []map[string]interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"goodJson": {
|
||||||
|
input: []byte(`
|
||||||
|
{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie"}}
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
out: []map[string]interface{}{testConfigMap},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"goodYaml1": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
out: []map[string]interface{}{testConfigMap},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"goodYaml2": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
out: []map[string]interface{}{testConfigMap, testConfigMap},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"localConfigYaml": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie-skip
|
||||||
|
annotations:
|
||||||
|
# this annotation causes the Resource to be ignored by kustomize
|
||||||
|
config.kubernetes.io/local-config: ""
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
out: []map[string]interface{}{testConfigMap},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"garbageInOneOfTwoObjects": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
---
|
||||||
|
WOOOOOOOOOOOOOOOOOOOOOOOOT: woot
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
isErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"emptyObjects": {
|
||||||
|
input: []byte(`
|
||||||
|
---
|
||||||
|
#a comment
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
out: []map[string]interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Missing .metadata.name in object": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
isErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nil value in list": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: builtin
|
||||||
|
kind: ConfigMapGenerator
|
||||||
|
metadata:
|
||||||
|
name: kube100-site
|
||||||
|
labels:
|
||||||
|
app: web
|
||||||
|
testList:
|
||||||
|
- testA
|
||||||
|
-
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
isErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"List": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: List
|
||||||
|
items:
|
||||||
|
- apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
- apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
out: []map[string]interface{}{
|
||||||
|
testConfigMap,
|
||||||
|
testConfigMap},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ConfigMapList": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMapList
|
||||||
|
items:
|
||||||
|
- apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
- apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
out: []map[string]interface{}{
|
||||||
|
testConfigMap,
|
||||||
|
testConfigMap,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"listWithAnchors": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: DeploymentList
|
||||||
|
items:
|
||||||
|
- apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deployment-a
|
||||||
|
spec: &hostAliases
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
hostAliases:
|
||||||
|
- hostnames:
|
||||||
|
- a.example.com
|
||||||
|
ip: 8.8.8.8
|
||||||
|
- apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deployment-b
|
||||||
|
spec:
|
||||||
|
<<: *hostAliases
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
// TODO(3271): This should work.
|
||||||
|
// https://github.com/kubernetes-sigs/kustomize/issues/3271
|
||||||
|
// json.Marshal(obj) fails on the 2nd list item.
|
||||||
|
// The value of the 1st list item's first spec field is
|
||||||
|
// map[string]interface{}
|
||||||
|
// The value of the 2nd list item's first spec field is
|
||||||
|
// map[interface{}]interface{}
|
||||||
|
// which causes a encoding/json.UnsupportedTypeError.
|
||||||
|
isErr: true,
|
||||||
|
out: []map[string]interface{}{testDeploymentList},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for n := range testCases {
|
||||||
|
tc := testCases[n]
|
||||||
|
t.Run(n, func(t *testing.T) {
|
||||||
|
rs, err := factory.RNodesFromBytes(tc.input)
|
||||||
|
if err != nil {
|
||||||
|
assert.True(t, tc.exp.isErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.False(t, tc.exp.isErr)
|
||||||
|
assert.Equal(t, len(tc.exp.out), len(rs))
|
||||||
|
for i := range rs {
|
||||||
|
rsMap, err := rs[i].Map()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(
|
||||||
|
t, fmt.Sprintf("%v", tc.exp.out[i]), fmt.Sprintf("%v", rsMap))
|
||||||
|
m, _ := rs[i].Map()
|
||||||
|
if !reflect.DeepEqual(tc.exp.out[i], m) {
|
||||||
|
t.Fatalf("%s:\nexpected: %v\n actual: %v",
|
||||||
|
n, tc.exp.out[i], m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/patchstrategicmerge"
|
"sigs.k8s.io/kustomize/api/filters/patchstrategicmerge"
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
"sigs.k8s.io/kustomize/api/internal/wrappy"
|
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
"sigs.k8s.io/kustomize/api/konfig"
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
@@ -22,11 +21,10 @@ import (
|
|||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Resource is a representation of a Kubernetes Resource Model (KRM) object
|
// Resource is an RNode, representing a Kubernetes Resource Model object,
|
||||||
// paired with metadata used by kustomize.
|
// paired with metadata used by kustomize.
|
||||||
// For more history, see sigs.k8s.io/kustomize/api/ifc.Unstructured
|
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
kunStr ifc.Kunstructured
|
node *kyaml.RNode
|
||||||
options *types.GenArgs
|
options *types.GenArgs
|
||||||
refBy []resid.ResId
|
refBy []resid.ResId
|
||||||
refVarNames []string
|
refVarNames []string
|
||||||
@@ -55,115 +53,133 @@ var buildAnnotations = []string{
|
|||||||
buildAnnotationAllowKindChange,
|
buildAnnotationAllowKindChange,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Resource) AsRNode() *kyaml.RNode {
|
||||||
|
return r.node.Copy()
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Resource) ResetPrimaryData(incoming *Resource) {
|
func (r *Resource) ResetPrimaryData(incoming *Resource) {
|
||||||
r.kunStr = incoming.Copy()
|
r.node = incoming.node.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetAnnotations() map[string]string {
|
func (r *Resource) GetAnnotations() map[string]string {
|
||||||
annotations := r.kunStr.GetAnnotations()
|
annotations, err := r.node.GetAnnotations()
|
||||||
if annotations == nil {
|
if err != nil || annotations == nil {
|
||||||
return make(map[string]string)
|
return make(map[string]string)
|
||||||
}
|
}
|
||||||
return annotations
|
return annotations
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) Copy() ifc.Kunstructured {
|
|
||||||
return r.kunStr.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resource) GetFieldValue(f string) (interface{}, error) {
|
func (r *Resource) GetFieldValue(f string) (interface{}, error) {
|
||||||
return r.kunStr.GetFieldValue(f)
|
//nolint:staticcheck
|
||||||
|
return r.node.GetFieldValue(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetDataMap() map[string]string {
|
func (r *Resource) GetDataMap() map[string]string {
|
||||||
return r.kunStr.GetDataMap()
|
return r.node.GetDataMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetBinaryDataMap() map[string]string {
|
func (r *Resource) GetBinaryDataMap() map[string]string {
|
||||||
return r.kunStr.GetBinaryDataMap()
|
return r.node.GetBinaryDataMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetGvk() resid.Gvk {
|
func (r *Resource) GetGvk() resid.Gvk {
|
||||||
return r.kunStr.GetGvk()
|
meta, err := r.node.GetMeta()
|
||||||
|
if err != nil {
|
||||||
|
return resid.GvkFromString("")
|
||||||
|
}
|
||||||
|
g, v := resid.ParseGroupVersion(meta.APIVersion)
|
||||||
|
return resid.Gvk{Group: g, Version: v, Kind: meta.Kind}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resource) Hash(h ifc.KustHasher) (string, error) {
|
||||||
|
return h.Hash(r.node)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetKind() string {
|
func (r *Resource) GetKind() string {
|
||||||
return r.kunStr.GetKind()
|
return r.node.GetKind()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetLabels() map[string]string {
|
func (r *Resource) GetLabels() map[string]string {
|
||||||
return r.kunStr.GetLabels()
|
l, err := r.node.GetLabels()
|
||||||
|
if err != nil {
|
||||||
|
return map[string]string{}
|
||||||
|
}
|
||||||
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetName() string {
|
func (r *Resource) GetName() string {
|
||||||
return r.kunStr.GetName()
|
return r.node.GetName()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetSlice(p string) ([]interface{}, error) {
|
func (r *Resource) GetSlice(p string) ([]interface{}, error) {
|
||||||
return r.kunStr.GetSlice(p)
|
//nolint:staticcheck
|
||||||
|
return r.node.GetSlice(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetString(p string) (string, error) {
|
func (r *Resource) GetString(p string) (string, error) {
|
||||||
return r.kunStr.GetString(p)
|
//nolint:staticcheck
|
||||||
|
return r.node.GetString(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) IsEmpty() (bool, error) {
|
func (r *Resource) IsEmpty() bool {
|
||||||
m, err := r.kunStr.Map()
|
return r.node.IsNilOrEmpty()
|
||||||
return len(m) == 0, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) Map() (map[string]interface{}, error) {
|
func (r *Resource) Map() (map[string]interface{}, error) {
|
||||||
return r.kunStr.Map()
|
return r.node.Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) MarshalJSON() ([]byte, error) {
|
func (r *Resource) MarshalJSON() ([]byte, error) {
|
||||||
return r.kunStr.MarshalJSON()
|
return r.node.MarshalJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) MatchesLabelSelector(selector string) (bool, error) {
|
func (r *Resource) MatchesLabelSelector(selector string) (bool, error) {
|
||||||
return r.kunStr.MatchesLabelSelector(selector)
|
return r.node.MatchesLabelSelector(selector)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) MatchesAnnotationSelector(selector string) (bool, error) {
|
func (r *Resource) MatchesAnnotationSelector(selector string) (bool, error) {
|
||||||
return r.kunStr.MatchesAnnotationSelector(selector)
|
return r.node.MatchesAnnotationSelector(selector)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetAnnotations(m map[string]string) {
|
func (r *Resource) SetAnnotations(m map[string]string) {
|
||||||
if len(m) == 0 {
|
if len(m) == 0 {
|
||||||
// Force field erasure.
|
// Force field erasure.
|
||||||
r.kunStr.SetAnnotations(nil)
|
r.node.SetAnnotations(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.kunStr.SetAnnotations(m)
|
r.node.SetAnnotations(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetDataMap(m map[string]string) {
|
func (r *Resource) SetDataMap(m map[string]string) {
|
||||||
r.kunStr.SetDataMap(m)
|
r.node.SetDataMap(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetBinaryDataMap(m map[string]string) {
|
func (r *Resource) SetBinaryDataMap(m map[string]string) {
|
||||||
r.kunStr.SetBinaryDataMap(m)
|
r.node.SetBinaryDataMap(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetGvk(gvk resid.Gvk) {
|
func (r *Resource) SetGvk(gvk resid.Gvk) {
|
||||||
r.kunStr.SetGvk(gvk)
|
r.node.SetMapField(
|
||||||
|
kyaml.NewScalarRNode(gvk.Kind), kyaml.KindField)
|
||||||
|
r.node.SetMapField(
|
||||||
|
kyaml.NewScalarRNode(gvk.ApiVersion()), kyaml.APIVersionField)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetLabels(m map[string]string) {
|
func (r *Resource) SetLabels(m map[string]string) {
|
||||||
if len(m) == 0 {
|
if len(m) == 0 {
|
||||||
// Force field erasure.
|
// Force field erasure.
|
||||||
r.kunStr.SetLabels(nil)
|
r.node.SetLabels(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.kunStr.SetLabels(m)
|
r.node.SetLabels(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetName(n string) {
|
func (r *Resource) SetName(n string) {
|
||||||
r.kunStr.SetName(n)
|
r.node.SetName(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetNamespace(n string) {
|
func (r *Resource) SetNamespace(n string) {
|
||||||
r.kunStr.SetNamespace(n)
|
r.node.SetNamespace(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetKind(k string) {
|
func (r *Resource) SetKind(k string) {
|
||||||
@@ -173,7 +189,7 @@ func (r *Resource) SetKind(k string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) UnmarshalJSON(s []byte) error {
|
func (r *Resource) UnmarshalJSON(s []byte) error {
|
||||||
return r.kunStr.UnmarshalJSON(s)
|
return r.node.UnmarshalJSON(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResCtx is an interface describing the contextual added
|
// ResCtx is an interface describing the contextual added
|
||||||
@@ -193,14 +209,14 @@ type ResCtxMatcher func(ResCtx) bool
|
|||||||
// DeepCopy returns a new copy of resource
|
// DeepCopy returns a new copy of resource
|
||||||
func (r *Resource) DeepCopy() *Resource {
|
func (r *Resource) DeepCopy() *Resource {
|
||||||
rc := &Resource{
|
rc := &Resource{
|
||||||
kunStr: r.Copy(),
|
node: r.node.Copy(),
|
||||||
}
|
}
|
||||||
rc.copyOtherFields(r)
|
rc.copyOtherFields(r)
|
||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyMergeMetaDataFields copies everything but the non-metadata in
|
// CopyMergeMetaDataFields copies everything but the non-metadata in
|
||||||
// the ifc.Kunstructured map, merging labels and annotations.
|
// the resource.
|
||||||
func (r *Resource) CopyMergeMetaDataFieldsFrom(other *Resource) {
|
func (r *Resource) CopyMergeMetaDataFieldsFrom(other *Resource) {
|
||||||
r.SetLabels(mergeStringMaps(other.GetLabels(), r.GetLabels()))
|
r.SetLabels(mergeStringMaps(other.GetLabels(), r.GetLabels()))
|
||||||
r.SetAnnotations(
|
r.SetAnnotations(
|
||||||
@@ -266,8 +282,10 @@ func (r *Resource) ReferencesEqual(other *Resource) bool {
|
|||||||
return len(setSelf) == len(setOther)
|
return len(setSelf) == len(setOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) KunstructEqual(o *Resource) bool {
|
// NodeEqual returns true if the resource's nodes are
|
||||||
return reflect.DeepEqual(r.kunStr, o.kunStr)
|
// equal, ignoring ancillary information like genargs, refby, etc.
|
||||||
|
func (r *Resource) NodeEqual(o *Resource) bool {
|
||||||
|
return reflect.DeepEqual(r.node, o.node)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) copyRefBy() []resid.ResId {
|
func (r *Resource) copyRefBy() []resid.ResId {
|
||||||
@@ -541,11 +559,7 @@ func (r *Resource) ApplySmPatch(patch *Resource) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
empty, err := r.IsEmpty()
|
if r.IsEmpty() {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if empty {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !patch.KindChangeAllowed() {
|
if !patch.KindChangeAllowed() {
|
||||||
@@ -559,15 +573,12 @@ func (r *Resource) ApplySmPatch(patch *Resource) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) ApplyFilter(f kio.Filter) error {
|
func (r *Resource) ApplyFilter(f kio.Filter) error {
|
||||||
if wn, ok := r.kunStr.(*wrappy.WNode); ok {
|
l, err := f.Filter([]*kyaml.RNode{r.node})
|
||||||
l, err := f.Filter([]*kyaml.RNode{wn.AsRNode()})
|
if len(l) == 0 {
|
||||||
if len(l) == 0 {
|
// The node was deleted. The following makes r.IsEmpty() true.
|
||||||
// Hack to deal with deletion.
|
r.node = nil
|
||||||
r.kunStr = wrappy.NewWNode()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return filtersutil.ApplyToJSON(f, r)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeStringMaps(maps ...map[string]string) map[string]string {
|
func mergeStringMaps(maps ...map[string]string) map[string]string {
|
||||||
|
|||||||
@@ -695,7 +695,7 @@ spec:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResource_StorePreviousId(t *testing.T) {
|
func TestResourceStorePreviousId(t *testing.T) {
|
||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
input string
|
input string
|
||||||
newName string
|
newName string
|
||||||
@@ -1079,3 +1079,54 @@ func TestSameEndingSubarray(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetGvk(t *testing.T) {
|
||||||
|
r, err := factory.FromBytes([]byte(`
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: clown
|
||||||
|
spec:
|
||||||
|
numReplicas: 1
|
||||||
|
`))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
gvk := r.GetGvk()
|
||||||
|
expected := "apps"
|
||||||
|
actual := gvk.Group
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
expected = "v1"
|
||||||
|
actual = gvk.Version
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
expected = "Deployment"
|
||||||
|
actual = gvk.Kind
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestSetGvk(t *testing.T) {
|
||||||
|
r, err := factory.FromBytes([]byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: clown
|
||||||
|
spec:
|
||||||
|
numReplicas: 1
|
||||||
|
`))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
r.SetGvk(resid.GvkFromString("grp_ver_knd"))
|
||||||
|
gvk := r.GetGvk()
|
||||||
|
if expected, actual := "grp", gvk.Group; expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
if expected, actual := "ver", gvk.Version; expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
if expected, actual := "knd", gvk.Kind; expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/api/resid"
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FieldSpec completely specifies a kustomizable field in
|
// FieldSpec completely specifies a kustomizable field in a k8s API object.
|
||||||
// an unstructured representation of a k8s API object.
|
|
||||||
// It helps define the operands of transformations.
|
// It helps define the operands of transformations.
|
||||||
//
|
//
|
||||||
// For example, a directive to add a common label to objects
|
// For example, a directive to add a common label to objects
|
||||||
|
|||||||
@@ -46,10 +46,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceList.Items, err = resMap.ToRNodeSlice()
|
resourceList.Items = resMap.ToRNodeSlice()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err := framework.Execute(&processor, nil); err != nil {
|
if err := framework.Execute(&processor, nil); err != nil {
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ See https://sigs.k8s.io/kustomize
|
|||||||
completion.NewCommand(),
|
completion.NewCommand(),
|
||||||
makeBuildCommand(fSys, stdOut),
|
makeBuildCommand(fSys, stdOut),
|
||||||
edit.NewCmdEdit(
|
edit.NewCmdEdit(
|
||||||
fSys, pvd.GetFieldValidator(), pvd.GetKunstructuredFactory()),
|
fSys, pvd.GetFieldValidator(), pvd.GetResourceFactory()),
|
||||||
create.NewCmdCreate(fSys, pvd.GetKunstructuredFactory()),
|
create.NewCmdCreate(fSys, pvd.GetResourceFactory()),
|
||||||
version.NewCmdVersion(stdOut),
|
version.NewCmdVersion(stdOut),
|
||||||
openapi.NewCmdOpenAPI(stdOut),
|
openapi.NewCmdOpenAPI(stdOut),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
"sigs.k8s.io/kustomize/api/konfig"
|
||||||
"sigs.k8s.io/kustomize/api/loader"
|
"sigs.k8s.io/kustomize/api/loader"
|
||||||
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile"
|
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile"
|
||||||
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/util"
|
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/util"
|
||||||
)
|
)
|
||||||
@@ -31,7 +31,7 @@ type createFlags struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewCmdCreate returns an instance of 'create' subcommand.
|
// NewCmdCreate returns an instance of 'create' subcommand.
|
||||||
func NewCmdCreate(fSys filesys.FileSystem, uf ifc.KunstructuredFactory) *cobra.Command {
|
func NewCmdCreate(fSys filesys.FileSystem, rf *resource.Factory) *cobra.Command {
|
||||||
opts := createFlags{path: filesys.SelfDir}
|
opts := createFlags{path: filesys.SelfDir}
|
||||||
c := &cobra.Command{
|
c := &cobra.Command{
|
||||||
Use: "create",
|
Use: "create",
|
||||||
@@ -49,7 +49,7 @@ func NewCmdCreate(fSys filesys.FileSystem, uf ifc.KunstructuredFactory) *cobra.C
|
|||||||
kustomize create --resources deployment.yaml,service.yaml,../base --namespace staging --nameprefix acme-
|
kustomize create --resources deployment.yaml,service.yaml,../base --namespace staging --nameprefix acme-
|
||||||
`,
|
`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runCreate(opts, fSys, uf)
|
return runCreate(opts, fSys, rf)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c.Flags().StringVar(
|
c.Flags().StringVar(
|
||||||
@@ -95,7 +95,7 @@ func NewCmdCreate(fSys filesys.FileSystem, uf ifc.KunstructuredFactory) *cobra.C
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCreate(opts createFlags, fSys filesys.FileSystem, uf ifc.KunstructuredFactory) error {
|
func runCreate(opts createFlags, fSys filesys.FileSystem, rf *resource.Factory) error {
|
||||||
var resources []string
|
var resources []string
|
||||||
var err error
|
var err error
|
||||||
if opts.resources != "" {
|
if opts.resources != "" {
|
||||||
@@ -108,7 +108,7 @@ func runCreate(opts createFlags, fSys filesys.FileSystem, uf ifc.KunstructuredFa
|
|||||||
return fmt.Errorf("kustomization file already exists")
|
return fmt.Errorf("kustomization file already exists")
|
||||||
}
|
}
|
||||||
if opts.detectResources {
|
if opts.detectResources {
|
||||||
detected, err := detectResources(fSys, uf, opts.path, opts.detectRecursive)
|
detected, err := detectResources(fSys, rf, opts.path, opts.detectRecursive)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ func runCreate(opts createFlags, fSys filesys.FileSystem, uf ifc.KunstructuredFa
|
|||||||
return mf.Write(m)
|
return mf.Write(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func detectResources(fSys filesys.FileSystem, uf ifc.KunstructuredFactory, base string, recursive bool) ([]string, error) {
|
func detectResources(fSys filesys.FileSystem, rf *resource.Factory, base string, recursive bool) ([]string, error) {
|
||||||
var paths []string
|
var paths []string
|
||||||
err := fSys.Walk(base, func(path string, info os.FileInfo, err error) error {
|
err := fSys.Walk(base, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -176,7 +176,7 @@ func detectResources(fSys filesys.FileSystem, uf ifc.KunstructuredFactory, base
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := uf.SliceFromBytes(fContents); err != nil {
|
if _, err := rf.SliceFromBytes(fContents); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
paths = append(paths, path)
|
paths = append(paths, path)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile"
|
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
var factory = provider.NewDefaultDepProvider().GetKunstructuredFactory()
|
var factory = provider.NewDefaultDepProvider().GetResourceFactory()
|
||||||
|
|
||||||
func readKustomizationFS(t *testing.T, fSys filesys.FileSystem) *types.Kustomization {
|
func readKustomizationFS(t *testing.T, fSys filesys.FileSystem) *types.Kustomization {
|
||||||
kf, err := kustfile.NewKustomizationFile(fSys)
|
kf, err := kustfile.NewKustomizationFile(fSys)
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewCmdAdd returns an instance of 'add' subcommand.
|
// NewCmdAdd returns an instance of 'add' subcommand.
|
||||||
func NewCmdAdd(
|
func NewCmdAdd(
|
||||||
fSys filesys.FileSystem,
|
fSys filesys.FileSystem,
|
||||||
ldr ifc.KvLoader,
|
ldr ifc.KvLoader,
|
||||||
kf ifc.KunstructuredFactory) *cobra.Command {
|
rf *resource.Factory) *cobra.Command {
|
||||||
c := &cobra.Command{
|
c := &cobra.Command{
|
||||||
Use: "add",
|
Use: "add",
|
||||||
Short: "Adds an item to the kustomization file.",
|
Short: "Adds an item to the kustomization file.",
|
||||||
@@ -53,8 +54,8 @@ func NewCmdAdd(
|
|||||||
newCmdAddResource(fSys),
|
newCmdAddResource(fSys),
|
||||||
newCmdAddPatch(fSys),
|
newCmdAddPatch(fSys),
|
||||||
newCmdAddComponent(fSys),
|
newCmdAddComponent(fSys),
|
||||||
newCmdAddSecret(fSys, ldr, kf),
|
newCmdAddSecret(fSys, ldr, rf),
|
||||||
newCmdAddConfigMap(fSys, ldr, kf),
|
newCmdAddConfigMap(fSys, ldr, rf),
|
||||||
newCmdAddBase(fSys),
|
newCmdAddBase(fSys),
|
||||||
newCmdAddLabel(fSys, ldr.Validator().MakeLabelValidator()),
|
newCmdAddLabel(fSys, ldr.Validator().MakeLabelValidator()),
|
||||||
newCmdAddAnnotation(fSys, ldr.Validator().MakeAnnotationValidator()),
|
newCmdAddAnnotation(fSys, ldr.Validator().MakeAnnotationValidator()),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile"
|
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile"
|
||||||
)
|
)
|
||||||
@@ -15,7 +16,7 @@ import (
|
|||||||
func newCmdAddConfigMap(
|
func newCmdAddConfigMap(
|
||||||
fSys filesys.FileSystem,
|
fSys filesys.FileSystem,
|
||||||
ldr ifc.KvLoader,
|
ldr ifc.KvLoader,
|
||||||
kf ifc.KunstructuredFactory) *cobra.Command {
|
rf *resource.Factory) *cobra.Command {
|
||||||
var flags flagsAndArgs
|
var flags flagsAndArgs
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "configmap NAME [--behavior={create|merge|replace}] [--from-file=[key=]source] [--from-literal=key1=value1]",
|
Use: "configmap NAME [--behavior={create|merge|replace}] [--from-file=[key=]source] [--from-literal=key1=value1]",
|
||||||
@@ -57,7 +58,7 @@ func newCmdAddConfigMap(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the flagsAndArgs map to the kustomization file.
|
// Add the flagsAndArgs map to the kustomization file.
|
||||||
err = addConfigMap(ldr, kustomization, flags, kf)
|
err = addConfigMap(ldr, kustomization, flags, rf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -106,13 +107,13 @@ func newCmdAddConfigMap(
|
|||||||
func addConfigMap(
|
func addConfigMap(
|
||||||
ldr ifc.KvLoader,
|
ldr ifc.KvLoader,
|
||||||
k *types.Kustomization,
|
k *types.Kustomization,
|
||||||
flags flagsAndArgs, kf ifc.KunstructuredFactory) error {
|
flags flagsAndArgs, rf *resource.Factory) error {
|
||||||
args := findOrMakeConfigMapArgs(k, flags.Name)
|
args := findOrMakeConfigMapArgs(k, flags.Name)
|
||||||
mergeFlagsIntoCmArgs(args, flags)
|
mergeFlagsIntoCmArgs(args, flags)
|
||||||
// Validate by trying to create corev1.configmap.
|
// Validate by trying to create corev1.configmap.
|
||||||
args.Options = types.MergeGlobalOptionsIntoLocal(
|
args.Options = types.MergeGlobalOptionsIntoLocal(
|
||||||
args.Options, k.GeneratorOptions)
|
args.Options, k.GeneratorOptions)
|
||||||
_, err := kf.MakeConfigMap(ldr, args)
|
_, err := rf.MakeConfigMap(ldr, args)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile"
|
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile"
|
||||||
)
|
)
|
||||||
@@ -15,7 +16,7 @@ import (
|
|||||||
func newCmdAddSecret(
|
func newCmdAddSecret(
|
||||||
fSys filesys.FileSystem,
|
fSys filesys.FileSystem,
|
||||||
ldr ifc.KvLoader,
|
ldr ifc.KvLoader,
|
||||||
kf ifc.KunstructuredFactory) *cobra.Command {
|
rf *resource.Factory) *cobra.Command {
|
||||||
var flags flagsAndArgs
|
var flags flagsAndArgs
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "secret NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--type=Opaque|kubernetes.io/tls]",
|
Use: "secret NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--type=Opaque|kubernetes.io/tls]",
|
||||||
@@ -54,7 +55,7 @@ func newCmdAddSecret(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the flagsAndArgs map to the kustomization file.
|
// Add the flagsAndArgs map to the kustomization file.
|
||||||
err = addSecret(ldr, kustomization, flags, kf)
|
err = addSecret(ldr, kustomization, flags, rf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -106,13 +107,13 @@ func newCmdAddSecret(
|
|||||||
func addSecret(
|
func addSecret(
|
||||||
ldr ifc.KvLoader,
|
ldr ifc.KvLoader,
|
||||||
k *types.Kustomization,
|
k *types.Kustomization,
|
||||||
flags flagsAndArgs, kf ifc.KunstructuredFactory) error {
|
flags flagsAndArgs, rf *resource.Factory) error {
|
||||||
args := findOrMakeSecretArgs(k, flags.Name, flags.Namespace, flags.Type)
|
args := findOrMakeSecretArgs(k, flags.Name, flags.Namespace, flags.Type)
|
||||||
mergeFlagsIntoGeneratorArgs(&args.GeneratorArgs, flags)
|
mergeFlagsIntoGeneratorArgs(&args.GeneratorArgs, flags)
|
||||||
// Validate by trying to create corev1.secret.
|
// Validate by trying to create corev1.secret.
|
||||||
args.Options = types.MergeGlobalOptionsIntoLocal(
|
args.Options = types.MergeGlobalOptionsIntoLocal(
|
||||||
args.Options, k.GeneratorOptions)
|
args.Options, k.GeneratorOptions)
|
||||||
_, err := kf.MakeSecret(ldr, args)
|
_, err := rf.MakeSecret(ldr, args)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/api/ifc"
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
"sigs.k8s.io/kustomize/api/kv"
|
"sigs.k8s.io/kustomize/api/kv"
|
||||||
"sigs.k8s.io/kustomize/api/loader"
|
"sigs.k8s.io/kustomize/api/loader"
|
||||||
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
"sigs.k8s.io/kustomize/kustomize/v4/commands/edit/add"
|
"sigs.k8s.io/kustomize/kustomize/v4/commands/edit/add"
|
||||||
"sigs.k8s.io/kustomize/kustomize/v4/commands/edit/fix"
|
"sigs.k8s.io/kustomize/kustomize/v4/commands/edit/fix"
|
||||||
"sigs.k8s.io/kustomize/kustomize/v4/commands/edit/listbuiltin"
|
"sigs.k8s.io/kustomize/kustomize/v4/commands/edit/listbuiltin"
|
||||||
@@ -18,7 +19,7 @@ import (
|
|||||||
|
|
||||||
// NewCmdEdit returns an instance of 'edit' subcommand.
|
// NewCmdEdit returns an instance of 'edit' subcommand.
|
||||||
func NewCmdEdit(
|
func NewCmdEdit(
|
||||||
fSys filesys.FileSystem, v ifc.Validator, kf ifc.KunstructuredFactory) *cobra.Command {
|
fSys filesys.FileSystem, v ifc.Validator, rf *resource.Factory) *cobra.Command {
|
||||||
c := &cobra.Command{
|
c := &cobra.Command{
|
||||||
Use: "edit",
|
Use: "edit",
|
||||||
Short: "Edits a kustomization file",
|
Short: "Edits a kustomization file",
|
||||||
@@ -40,7 +41,7 @@ func NewCmdEdit(
|
|||||||
add.NewCmdAdd(
|
add.NewCmdAdd(
|
||||||
fSys,
|
fSys,
|
||||||
kv.NewLoader(loader.NewFileLoaderAtCwd(fSys), v),
|
kv.NewLoader(loader.NewFileLoaderAtCwd(fSys), v),
|
||||||
kf),
|
rf),
|
||||||
set.NewCmdSet(
|
set.NewCmdSet(
|
||||||
fSys,
|
fSys,
|
||||||
kv.NewLoader(loader.NewFileLoaderAtCwd(fSys), v),
|
kv.NewLoader(loader.NewFileLoaderAtCwd(fSys), v),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -336,6 +337,33 @@ func (rn *RNode) SetYNode(node *yaml.Node) {
|
|||||||
*rn.value = *node
|
*rn.value = *node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetKind returns the kind.
|
||||||
|
func (rn *RNode) GetKind() string {
|
||||||
|
node, err := rn.Pipe(FieldMatcher{Name: KindField})
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return GetValue(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName returns the name.
|
||||||
|
func (rn *RNode) GetName() string {
|
||||||
|
f := rn.Field(MetadataField)
|
||||||
|
if f.IsNilOrEmpty() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
f = f.Value.Field(NameField)
|
||||||
|
if f.IsNilOrEmpty() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return f.Value.YNode().Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetName sets the metadata name field.
|
||||||
|
func (rn *RNode) SetName(name string) error {
|
||||||
|
return rn.SetMapField(NewScalarRNode(name), MetadataField, NameField)
|
||||||
|
}
|
||||||
|
|
||||||
// GetNamespace gets the metadata namespace field.
|
// GetNamespace gets the metadata namespace field.
|
||||||
func (rn *RNode) GetNamespace() (string, error) {
|
func (rn *RNode) GetNamespace() (string, error) {
|
||||||
meta, err := rn.GetMeta()
|
meta, err := rn.GetMeta()
|
||||||
@@ -752,7 +780,7 @@ func (rn *RNode) GetValidatedMetadata() (ResourceMeta, error) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchesAnnotationSelector implements ifc.Kunstructured.
|
// MatchesAnnotationSelector returns true on a selector match to annotations.
|
||||||
func (rn *RNode) MatchesAnnotationSelector(selector string) (bool, error) {
|
func (rn *RNode) MatchesAnnotationSelector(selector string) (bool, error) {
|
||||||
s, err := labels.Parse(selector)
|
s, err := labels.Parse(selector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -765,7 +793,7 @@ func (rn *RNode) MatchesAnnotationSelector(selector string) (bool, error) {
|
|||||||
return s.Matches(labels.Set(slice)), nil
|
return s.Matches(labels.Set(slice)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchesLabelSelector implements ifc.Kunstructured.
|
// MatchesLabelSelector returns true on a selector match to labels.
|
||||||
func (rn *RNode) MatchesLabelSelector(selector string) (bool, error) {
|
func (rn *RNode) MatchesLabelSelector(selector string) (bool, error) {
|
||||||
s, err := labels.Parse(selector)
|
s, err := labels.Parse(selector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -780,9 +808,8 @@ func (rn *RNode) MatchesLabelSelector(selector string) (bool, error) {
|
|||||||
|
|
||||||
// HasNilEntryInList returns true if the RNode contains a list which has
|
// HasNilEntryInList returns true if the RNode contains a list which has
|
||||||
// a nil item, along with the path to the missing item.
|
// a nil item, along with the path to the missing item.
|
||||||
// TODO(broken): This was copied from
|
// TODO(broken): This doesn't do what it claims to do.
|
||||||
// api/k8sdeps/kunstruct/factory.go//checkListItemNil
|
// (see TODO in unit test and pr 1513).
|
||||||
// and doesn't do what it claims to do (see TODO in unit test and pr 1513).
|
|
||||||
func (rn *RNode) HasNilEntryInList() (bool, string) {
|
func (rn *RNode) HasNilEntryInList() (bool, string) {
|
||||||
return hasNilEntryInList(rn.value)
|
return hasNilEntryInList(rn.value)
|
||||||
}
|
}
|
||||||
@@ -859,3 +886,123 @@ func checkKey(key string, elems []*Node) bool {
|
|||||||
}
|
}
|
||||||
return count == len(elems)
|
return count == len(elems)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: use pipes instead.
|
||||||
|
// GetSlice returns the contents of the slice field at the given path.
|
||||||
|
func (rn *RNode) GetSlice(path string) ([]interface{}, error) {
|
||||||
|
value, err := rn.GetFieldValue(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if sliceValue, ok := value.([]interface{}); ok {
|
||||||
|
return sliceValue, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("node %s is not a slice", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use pipes instead.
|
||||||
|
// GetString returns the contents of the string field at the given path.
|
||||||
|
func (rn *RNode) GetString(path string) (string, error) {
|
||||||
|
value, err := rn.GetFieldValue(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if v, ok := value.(string); ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("node %s is not a string: %v", path, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use slash paths instead.
|
||||||
|
// GetFieldValue finds period delimited fields.
|
||||||
|
// TODO: When doing kustomize var replacement, which is likely a
|
||||||
|
// a primary use of this function and the reason it returns interface{}
|
||||||
|
// rather than string, we do conversion from Nodes to Go types and back
|
||||||
|
// to nodes. We should figure out how to do replacement using raw nodes,
|
||||||
|
// assuming we keep the var feature in kustomize.
|
||||||
|
// The other end of this is: refvar.go:updateNodeValue.
|
||||||
|
func (rn *RNode) GetFieldValue(path string) (interface{}, error) {
|
||||||
|
fields := convertSliceIndex(strings.Split(path, "."))
|
||||||
|
rn, err := rn.Pipe(Lookup(fields...))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if rn == nil {
|
||||||
|
return nil, NoFieldError{path}
|
||||||
|
}
|
||||||
|
yn := rn.YNode()
|
||||||
|
|
||||||
|
// If this is an alias node, resolve it
|
||||||
|
if yn.Kind == yaml.AliasNode {
|
||||||
|
yn = yn.Alias
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return value as map for DocumentNode and MappingNode kinds
|
||||||
|
if yn.Kind == yaml.DocumentNode || yn.Kind == yaml.MappingNode {
|
||||||
|
var result map[string]interface{}
|
||||||
|
if err := yn.Decode(&result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return value as slice for SequenceNode kind
|
||||||
|
if yn.Kind == yaml.SequenceNode {
|
||||||
|
var result []interface{}
|
||||||
|
if err := yn.Decode(&result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
if yn.Kind != yaml.ScalarNode {
|
||||||
|
return nil, fmt.Errorf("expected ScalarNode, got Kind=%d", yn.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch yn.Tag {
|
||||||
|
case NodeTagString:
|
||||||
|
return yn.Value, nil
|
||||||
|
case NodeTagInt:
|
||||||
|
return strconv.Atoi(yn.Value)
|
||||||
|
case NodeTagFloat:
|
||||||
|
return strconv.ParseFloat(yn.Value, 64)
|
||||||
|
case NodeTagBool:
|
||||||
|
return strconv.ParseBool(yn.Value)
|
||||||
|
default:
|
||||||
|
// Possibly this should be an error or log.
|
||||||
|
return yn.Value, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertSliceIndex traverses the items in `fields` and find
|
||||||
|
// if there is a slice index in the item and change it to a
|
||||||
|
// valid Lookup field path. For example, 'ports[0]' will be
|
||||||
|
// converted to 'ports' and '0'.
|
||||||
|
func convertSliceIndex(fields []string) []string {
|
||||||
|
var res []string
|
||||||
|
for _, s := range fields {
|
||||||
|
if !strings.HasSuffix(s, "]") {
|
||||||
|
res = append(res, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
re := regexp.MustCompile(`^(.*)\[(\d+)\]$`)
|
||||||
|
groups := re.FindStringSubmatch(s)
|
||||||
|
if len(groups) == 0 {
|
||||||
|
// no match, add to result
|
||||||
|
res = append(res, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if groups[1] != "" {
|
||||||
|
res = append(res, groups[1])
|
||||||
|
}
|
||||||
|
res = append(res, groups[2])
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
type NoFieldError struct {
|
||||||
|
Field string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e NoFieldError) Error() string {
|
||||||
|
return fmt.Sprintf("no field named '%s'", e.Field)
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRNodeHasNilEntryInList(t *testing.T) {
|
func TestRNodeHasNilEntryInList(t *testing.T) {
|
||||||
@@ -1130,3 +1131,627 @@ func TestRNodeMatchesLabelSelector(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
deploymentLittleJson = `{"apiVersion":"apps/v1","kind":"Deployment",` +
|
||||||
|
`"metadata":{"name":"homer","namespace":"simpsons"}}`
|
||||||
|
|
||||||
|
deploymentBiggerJson = `
|
||||||
|
{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": {
|
||||||
|
"name": "homer",
|
||||||
|
"namespace": "simpsons",
|
||||||
|
"labels": {
|
||||||
|
"fruit": "apple",
|
||||||
|
"veggie": "carrot"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"area": "51",
|
||||||
|
"greeting": "Take me to your leader."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"template": {
|
||||||
|
"spec": {
|
||||||
|
"containers": [
|
||||||
|
{
|
||||||
|
"env": [
|
||||||
|
{
|
||||||
|
"name": "CM_FOO",
|
||||||
|
"valueFrom": {
|
||||||
|
"configMapKeyRef": {
|
||||||
|
"key": "somekey",
|
||||||
|
"name": "myCm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SECRET_FOO",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "someKey",
|
||||||
|
"name": "mySecret"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"image": "nginx:1.7.9",
|
||||||
|
"name": "nginx"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
bigMapYaml = `Kind: Service
|
||||||
|
complextree:
|
||||||
|
- field1:
|
||||||
|
- boolfield: true
|
||||||
|
floatsubfield: 1.01
|
||||||
|
intsubfield: 1010
|
||||||
|
stringsubfield: idx1010
|
||||||
|
- boolfield: false
|
||||||
|
floatsubfield: 1.011
|
||||||
|
intsubfield: 1011
|
||||||
|
stringsubfield: idx1011
|
||||||
|
field2:
|
||||||
|
- boolfield: true
|
||||||
|
floatsubfield: 1.02
|
||||||
|
intsubfield: 1020
|
||||||
|
stringsubfield: idx1020
|
||||||
|
- boolfield: false
|
||||||
|
floatsubfield: 1.021
|
||||||
|
intsubfield: 1021
|
||||||
|
stringsubfield: idx1021
|
||||||
|
- field1:
|
||||||
|
- boolfield: true
|
||||||
|
floatsubfield: 1.11
|
||||||
|
intsubfield: 1110
|
||||||
|
stringsubfield: idx1110
|
||||||
|
- boolfield: false
|
||||||
|
floatsubfield: 1.111
|
||||||
|
intsubfield: 1111
|
||||||
|
stringsubfield: idx1111
|
||||||
|
field2:
|
||||||
|
- boolfield: true
|
||||||
|
floatsubfield: 1.112
|
||||||
|
intsubfield: 1120
|
||||||
|
stringsubfield: idx1120
|
||||||
|
- boolfield: false
|
||||||
|
floatsubfield: 1.1121
|
||||||
|
intsubfield: 1121
|
||||||
|
stringsubfield: idx1121
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: application-name
|
||||||
|
name: service-name
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
port: 80
|
||||||
|
that:
|
||||||
|
- idx0
|
||||||
|
- idx1
|
||||||
|
- idx2
|
||||||
|
- idx3
|
||||||
|
these:
|
||||||
|
- field1:
|
||||||
|
- idx010
|
||||||
|
- idx011
|
||||||
|
field2:
|
||||||
|
- idx020
|
||||||
|
- idx021
|
||||||
|
- field1:
|
||||||
|
- idx110
|
||||||
|
- idx111
|
||||||
|
field2:
|
||||||
|
- idx120
|
||||||
|
- idx121
|
||||||
|
- field1:
|
||||||
|
- idx210
|
||||||
|
- idx211
|
||||||
|
field2:
|
||||||
|
- idx220
|
||||||
|
- idx221
|
||||||
|
this:
|
||||||
|
is:
|
||||||
|
aBool: true
|
||||||
|
aFloat: 1.001
|
||||||
|
aNilValue: null
|
||||||
|
aNumber: 1000
|
||||||
|
anEmptyMap: {}
|
||||||
|
anEmptySlice: []
|
||||||
|
those:
|
||||||
|
- field1: idx0foo
|
||||||
|
field2: idx0bar
|
||||||
|
- field1: idx1foo
|
||||||
|
field2: idx1bar
|
||||||
|
- field1: idx2foo
|
||||||
|
field2: idx2bar
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetFieldValueReturnsMap(t *testing.T) {
|
||||||
|
rn := NewRNode(nil)
|
||||||
|
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
||||||
|
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
||||||
|
}
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"fruit": "apple",
|
||||||
|
"veggie": "carrot",
|
||||||
|
}
|
||||||
|
actual, err := rn.GetFieldValue("metadata.labels")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting field value: %v", err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(expected, actual); diff != "" {
|
||||||
|
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFieldValueReturnsStuff(t *testing.T) {
|
||||||
|
wn := NewRNode(nil)
|
||||||
|
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
||||||
|
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
||||||
|
}
|
||||||
|
expected := []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"env": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "CM_FOO",
|
||||||
|
"valueFrom": map[string]interface{}{
|
||||||
|
"configMapKeyRef": map[string]interface{}{
|
||||||
|
"key": "somekey",
|
||||||
|
"name": "myCm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "SECRET_FOO",
|
||||||
|
"valueFrom": map[string]interface{}{
|
||||||
|
"secretKeyRef": map[string]interface{}{
|
||||||
|
"key": "someKey",
|
||||||
|
"name": "mySecret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"image": string("nginx:1.7.9"),
|
||||||
|
"name": string("nginx"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
actual, err := wn.GetFieldValue("spec.template.spec.containers")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting field value: %v", err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(expected, actual); diff != "" {
|
||||||
|
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
|
||||||
|
}
|
||||||
|
// Cannot go deeper yet.
|
||||||
|
_, err = wn.GetFieldValue("spec.template.spec.containers.env")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected err %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeBigMap() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"Kind": "Service",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"labels": map[string]interface{}{
|
||||||
|
"app": "application-name",
|
||||||
|
},
|
||||||
|
"name": "service-name",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"ports": map[string]interface{}{
|
||||||
|
"port": int64(80),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"this": map[string]interface{}{
|
||||||
|
"is": map[string]interface{}{
|
||||||
|
"aNumber": int64(1000),
|
||||||
|
"aFloat": float64(1.001),
|
||||||
|
"aNilValue": nil,
|
||||||
|
"aBool": true,
|
||||||
|
"anEmptyMap": map[string]interface{}{},
|
||||||
|
"anEmptySlice": []interface{}{},
|
||||||
|
/*
|
||||||
|
TODO: test for unrecognizable (e.g. a function)
|
||||||
|
"unrecognizable": testing.InternalExample{
|
||||||
|
Name: "fooBar",
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"that": []interface{}{
|
||||||
|
"idx0",
|
||||||
|
"idx1",
|
||||||
|
"idx2",
|
||||||
|
"idx3",
|
||||||
|
},
|
||||||
|
"those": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": "idx0foo",
|
||||||
|
"field2": "idx0bar",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": "idx1foo",
|
||||||
|
"field2": "idx1bar",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": "idx2foo",
|
||||||
|
"field2": "idx2bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"these": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": []interface{}{"idx010", "idx011"},
|
||||||
|
"field2": []interface{}{"idx020", "idx021"},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": []interface{}{"idx110", "idx111"},
|
||||||
|
"field2": []interface{}{"idx120", "idx121"},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": []interface{}{"idx210", "idx211"},
|
||||||
|
"field2": []interface{}{"idx220", "idx221"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"complextree": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1010",
|
||||||
|
"intsubfield": int64(1010),
|
||||||
|
"floatsubfield": float64(1.010),
|
||||||
|
"boolfield": true,
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1011",
|
||||||
|
"intsubfield": int64(1011),
|
||||||
|
"floatsubfield": float64(1.011),
|
||||||
|
"boolfield": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"field2": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1020",
|
||||||
|
"intsubfield": int64(1020),
|
||||||
|
"floatsubfield": float64(1.020),
|
||||||
|
"boolfield": true,
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1021",
|
||||||
|
"intsubfield": int64(1021),
|
||||||
|
"floatsubfield": float64(1.021),
|
||||||
|
"boolfield": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1110",
|
||||||
|
"intsubfield": int64(1110),
|
||||||
|
"floatsubfield": float64(1.110),
|
||||||
|
"boolfield": true,
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1111",
|
||||||
|
"intsubfield": int64(1111),
|
||||||
|
"floatsubfield": float64(1.111),
|
||||||
|
"boolfield": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"field2": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1120",
|
||||||
|
"intsubfield": int64(1120),
|
||||||
|
"floatsubfield": float64(1.1120),
|
||||||
|
"boolfield": true,
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1121",
|
||||||
|
"intsubfield": int64(1121),
|
||||||
|
"floatsubfield": float64(1.1121),
|
||||||
|
"boolfield": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestBasicYamlOperationFromMap(t *testing.T) {
|
||||||
|
bytes, err := Marshal(makeBigMap())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
||||||
|
}
|
||||||
|
if string(bytes) != bigMapYaml {
|
||||||
|
t.Fatalf("unexpected string equality")
|
||||||
|
}
|
||||||
|
rNode, err := Parse(string(bytes))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
||||||
|
}
|
||||||
|
rNodeString := rNode.MustString()
|
||||||
|
// The result from MustString has more indentation
|
||||||
|
// than bigMapYaml.
|
||||||
|
rNodeStrings := strings.Split(rNodeString, "\n")
|
||||||
|
bigMapStrings := strings.Split(bigMapYaml, "\n")
|
||||||
|
if len(rNodeStrings) != len(bigMapStrings) {
|
||||||
|
t.Fatalf("line count mismatch")
|
||||||
|
}
|
||||||
|
for i := range rNodeStrings {
|
||||||
|
s1 := strings.TrimSpace(rNodeStrings[i])
|
||||||
|
s2 := strings.TrimSpace(bigMapStrings[i])
|
||||||
|
if s1 != s2 {
|
||||||
|
t.Fatalf("expected '%s'=='%s'", s1, s2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFieldValueReturnsSlice(t *testing.T) {
|
||||||
|
bytes, err := yaml.Marshal(makeBigMap())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
||||||
|
}
|
||||||
|
rNode, err := Parse(string(bytes))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
||||||
|
}
|
||||||
|
expected := []interface{}{"idx0", "idx1", "idx2", "idx3"}
|
||||||
|
actual, err := rNode.GetFieldValue("that")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting slice: %v", err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(expected, actual); diff != "" {
|
||||||
|
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFieldValueReturnsSliceOfMappings(t *testing.T) {
|
||||||
|
bytes, err := yaml.Marshal(makeBigMap())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
||||||
|
}
|
||||||
|
rn, err := Parse(string(bytes))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
||||||
|
}
|
||||||
|
expected := []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": "idx0foo",
|
||||||
|
"field2": "idx0bar",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": "idx1foo",
|
||||||
|
"field2": "idx1bar",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": "idx2foo",
|
||||||
|
"field2": "idx2bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
actual, err := rn.GetFieldValue("those")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting slice: %v", err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(expected, actual); diff != "" {
|
||||||
|
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFieldValueReturnsString(t *testing.T) {
|
||||||
|
rn := NewRNode(nil)
|
||||||
|
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
||||||
|
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
||||||
|
}
|
||||||
|
actual, err := rn.GetFieldValue("metadata.labels.fruit")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting field value: %v", err)
|
||||||
|
}
|
||||||
|
v, ok := actual.(string)
|
||||||
|
if !ok || v != "apple" {
|
||||||
|
t.Fatalf("unexpected value '%v'", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFieldValueResolvesAlias(t *testing.T) {
|
||||||
|
yamlWithAlias := `
|
||||||
|
foo: &a theValue
|
||||||
|
bar: *a
|
||||||
|
`
|
||||||
|
rn, err := Parse(yamlWithAlias)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected yaml parse error: %v", err)
|
||||||
|
}
|
||||||
|
actual, err := rn.GetFieldValue("bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting field value: %v", err)
|
||||||
|
}
|
||||||
|
v, ok := actual.(string)
|
||||||
|
if !ok || v != "theValue" {
|
||||||
|
t.Fatalf("unexpected value '%v'", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetString(t *testing.T) {
|
||||||
|
rn := NewRNode(nil)
|
||||||
|
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
||||||
|
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
||||||
|
}
|
||||||
|
expected := "carrot"
|
||||||
|
actual, err := rn.GetString("metadata.labels.veggie")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting string: %v", err)
|
||||||
|
}
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSlice(t *testing.T) {
|
||||||
|
bytes, err := yaml.Marshal(makeBigMap())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
||||||
|
}
|
||||||
|
rn, err := Parse(string(bytes))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
||||||
|
}
|
||||||
|
expected := []interface{}{"idx0", "idx1", "idx2", "idx3"}
|
||||||
|
actual, err := rn.GetSlice("that")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting slice: %v", err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(expected, actual); diff != "" {
|
||||||
|
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoundTripJSON(t *testing.T) {
|
||||||
|
rn := NewRNode(nil)
|
||||||
|
err := rn.UnmarshalJSON([]byte(deploymentLittleJson))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected UnmarshalJSON err: %v", err)
|
||||||
|
}
|
||||||
|
data, err := rn.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected MarshalJSON err: %v", err)
|
||||||
|
}
|
||||||
|
actual := string(data)
|
||||||
|
if actual != deploymentLittleJson {
|
||||||
|
t.Fatalf("expected %s, got %s", deploymentLittleJson, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGettingFields(t *testing.T) {
|
||||||
|
rn := NewRNode(nil)
|
||||||
|
err := rn.UnmarshalJSON([]byte(deploymentBiggerJson))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
||||||
|
}
|
||||||
|
expected := "Deployment"
|
||||||
|
actual := rn.GetKind()
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
expected = "homer"
|
||||||
|
actual = rn.GetName()
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
actualMap, err := rn.GetLabels()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected err '%v'", err)
|
||||||
|
}
|
||||||
|
v, ok := actualMap["fruit"]
|
||||||
|
if !ok || v != "apple" {
|
||||||
|
t.Fatalf("unexpected labels '%v'", actualMap)
|
||||||
|
}
|
||||||
|
actualMap, err = rn.GetAnnotations()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected err '%v'", err)
|
||||||
|
}
|
||||||
|
v, ok = actualMap["greeting"]
|
||||||
|
if !ok || v != "Take me to your leader." {
|
||||||
|
t.Fatalf("unexpected annotations '%v'", actualMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapEmpty(t *testing.T) {
|
||||||
|
newNodeMap, err := NewRNode(nil).Map()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(newNodeMap))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMap(t *testing.T) {
|
||||||
|
rn := NewRNode(nil)
|
||||||
|
if err := rn.UnmarshalJSON([]byte(deploymentLittleJson)); err != nil {
|
||||||
|
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "homer",
|
||||||
|
"namespace": "simpsons",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := rn.Map()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if diff := cmp.Diff(expected, actual); diff != "" {
|
||||||
|
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetName(t *testing.T) {
|
||||||
|
rn := NewRNode(nil)
|
||||||
|
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
||||||
|
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
||||||
|
}
|
||||||
|
rn.SetName("marge")
|
||||||
|
if expected, actual := "marge", rn.GetName(); expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetNamespace(t *testing.T) {
|
||||||
|
rn := NewRNode(nil)
|
||||||
|
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
||||||
|
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
||||||
|
}
|
||||||
|
rn.SetNamespace("flanders")
|
||||||
|
meta, _ := rn.GetMeta()
|
||||||
|
if expected, actual := "flanders", meta.Namespace; expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestSetLabels(t *testing.T) {
|
||||||
|
rn := NewRNode(nil)
|
||||||
|
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
||||||
|
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
||||||
|
}
|
||||||
|
rn.SetLabels(map[string]string{
|
||||||
|
"label1": "foo",
|
||||||
|
"label2": "bar",
|
||||||
|
})
|
||||||
|
labels, err := rn.GetLabels()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if expected, actual := 2, len(labels); expected != actual {
|
||||||
|
t.Fatalf("expected '%d', got '%d'", expected, actual)
|
||||||
|
}
|
||||||
|
if expected, actual := "foo", labels["label1"]; expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
if expected, actual := "bar", labels["label2"]; expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAnnotations(t *testing.T) {
|
||||||
|
rn := NewRNode(nil)
|
||||||
|
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
||||||
|
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
||||||
|
}
|
||||||
|
rn.SetAnnotations(map[string]string{
|
||||||
|
"annotation1": "foo",
|
||||||
|
"annotation2": "bar",
|
||||||
|
})
|
||||||
|
annotations, err := rn.GetAnnotations()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if expected, actual := 2, len(annotations); expected != actual {
|
||||||
|
t.Fatalf("expected '%d', got '%d'", expected, actual)
|
||||||
|
}
|
||||||
|
if expected, actual := "foo", annotations["annotation1"]; expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
if expected, actual := "bar", annotations["annotation2"]; expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type plugin struct {
|
type plugin struct {
|
||||||
hasher ifc.KunstructuredHasher
|
hasher ifc.KustHasher
|
||||||
}
|
}
|
||||||
|
|
||||||
//noinspection GoUnusedGlobalVariable
|
//noinspection GoUnusedGlobalVariable
|
||||||
@@ -28,7 +28,7 @@ func (p *plugin) Config(
|
|||||||
func (p *plugin) Transform(m resmap.ResMap) error {
|
func (p *plugin) Transform(m resmap.ResMap) error {
|
||||||
for _, res := range m.Resources() {
|
for _, res := range m.Resources() {
|
||||||
if res.NeedHashSuffix() {
|
if res.NeedHashSuffix() {
|
||||||
h, err := p.hasher.Hash(res)
|
h, err := res.Hash(p.hasher)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,20 +34,15 @@ func (p *plugin) Transform(m resmap.ResMap) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, r := range m.Resources() {
|
for _, r := range m.Resources() {
|
||||||
empty, err := r.IsEmpty()
|
if r.IsEmpty() {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if empty {
|
|
||||||
// Don't mutate empty objects?
|
// Don't mutate empty objects?
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
r.StorePreviousId()
|
r.StorePreviousId()
|
||||||
err = r.ApplyFilter(namespace.Filter{
|
if err := r.ApplyFilter(namespace.Filter{
|
||||||
Namespace: p.Namespace,
|
Namespace: p.Namespace,
|
||||||
FsSlice: p.FieldSpecs,
|
FsSlice: p.FieldSpecs,
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)
|
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)
|
||||||
|
|||||||
Reference in New Issue
Block a user