mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-17 18:25:26 +00:00
Remove the wrappy layer.
This commit is contained in:
@@ -24,7 +24,7 @@ func (p *HashTransformerPlugin) Config(
|
||||
func (p *HashTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
for _, res := range m.Resources() {
|
||||
if res.NeedHashSuffix() {
|
||||
h, err := p.hasher.Hash(res)
|
||||
h, err := res.Hash(p.hasher)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -30,20 +30,15 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
return nil
|
||||
}
|
||||
for _, r := range m.Resources() {
|
||||
empty, err := r.IsEmpty()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if empty {
|
||||
if r.IsEmpty() {
|
||||
// Don't mutate empty objects?
|
||||
continue
|
||||
}
|
||||
r.StorePreviousId()
|
||||
err = r.ApplyFilter(namespace.Filter{
|
||||
if err := r.ApplyFilter(namespace.Filter{
|
||||
Namespace: p.Namespace,
|
||||
FsSlice: p.FieldSpecs,
|
||||
})
|
||||
if err != nil {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)
|
||||
|
||||
@@ -6,13 +6,11 @@ require (
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible
|
||||
github.com/go-errors/errors v1.0.1
|
||||
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/imdario/mergo v0.3.5
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.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/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-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/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
|
||||
@@ -20,12 +20,12 @@ func SortArrayAndComputeHash(s []string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return Encode(Hash(string(data)))
|
||||
return encode(hex256(string(data)))
|
||||
}
|
||||
|
||||
// Copied from https://github.com/kubernetes/kubernetes
|
||||
// /blob/master/pkg/kubectl/util/hash/hash.go
|
||||
func Encode(hex string) (string, error) {
|
||||
func encode(hex string) (string, error) {
|
||||
if len(hex) < 10 {
|
||||
return "", fmt.Errorf(
|
||||
"input length must be at least 10")
|
||||
@@ -48,23 +48,18 @@ func Encode(hex string) (string, error) {
|
||||
return string(enc), nil
|
||||
}
|
||||
|
||||
// Hash returns the hex form of the sha256 of the argument.
|
||||
func Hash(data string) string {
|
||||
// hex256 returns the hex form of the sha256 of the argument.
|
||||
func hex256(data string) string {
|
||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
|
||||
}
|
||||
|
||||
// HashRNode returns the hash value of input RNode
|
||||
func HashRNode(node *yaml.RNode) (string, error) {
|
||||
// get node kind
|
||||
kindNode, err := node.Pipe(yaml.FieldMatcher{Name: "kind"})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
kind := kindNode.YNode().Value
|
||||
// Hasher computes the hash of an RNode.
|
||||
type Hasher struct{}
|
||||
|
||||
// calculate hash for different kinds
|
||||
encoded := ""
|
||||
switch kind {
|
||||
// Hash returns a hash of the argument.
|
||||
func (h *Hasher) Hash(node *yaml.RNode) (r string, err error) {
|
||||
var encoded string
|
||||
switch node.GetKind() {
|
||||
case "ConfigMap":
|
||||
encoded, err = encodeConfigMap(node)
|
||||
case "Secret":
|
||||
@@ -77,10 +72,11 @@ func HashRNode(node *yaml.RNode) (string, error) {
|
||||
if err != nil {
|
||||
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{})
|
||||
for _, p := range paths {
|
||||
vn, err := node.Pipe(yaml.Lookup(p))
|
||||
@@ -117,8 +113,11 @@ func encodeConfigMap(node *yaml.RNode) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
m := map[string]interface{}{"kind": "ConfigMap", "name": values["metadata/name"],
|
||||
"data": values["data"]}
|
||||
m := map[string]interface{}{
|
||||
"kind": "ConfigMap",
|
||||
"name": values["metadata/name"],
|
||||
"data": values["data"],
|
||||
}
|
||||
if _, ok := values["binaryData"].(map[string]interface{}); ok {
|
||||
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
|
||||
expect := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
sum := Hash("")
|
||||
sum := hex256("")
|
||||
if expect != sum {
|
||||
t.Errorf("expected hash %q but got %q", expect, sum)
|
||||
}
|
||||
@@ -93,17 +93,17 @@ data:
|
||||
binaryData:
|
||||
two: ""`, "698h7c7t9m", ""},
|
||||
}
|
||||
|
||||
h := &Hasher{}
|
||||
for _, c := range cases {
|
||||
node, err := yaml.Parse(c.cmYaml)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h, err := HashRNode(node)
|
||||
hashed, err := h.Hash(node)
|
||||
if SkipRest(t, c.desc, err, c.err) {
|
||||
continue
|
||||
}
|
||||
if c.hash != h {
|
||||
if c.hash != hashed {
|
||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||
}
|
||||
}
|
||||
@@ -154,17 +154,17 @@ type: my-type
|
||||
data:
|
||||
one: ""`, "74bd68bm66", ""},
|
||||
}
|
||||
|
||||
h := &Hasher{}
|
||||
for _, c := range cases {
|
||||
node, err := yaml.Parse(c.secretYaml)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h, err := HashRNode(node)
|
||||
hashed, err := h.Hash(node)
|
||||
if SkipRest(t, c.desc, err, c.err) {
|
||||
continue
|
||||
}
|
||||
if c.hash != h {
|
||||
if c.hash != hashed {
|
||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||
}
|
||||
}
|
||||
@@ -191,17 +191,17 @@ spec:
|
||||
foo: 1
|
||||
bar: abc`, "59m2mdccg4", ""},
|
||||
}
|
||||
|
||||
h := &Hasher{}
|
||||
for _, c := range cases {
|
||||
node, err := yaml.Parse(c.unstructured)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h, err := HashRNode(node)
|
||||
hashed, err := h.Hash(node)
|
||||
if SkipRest(t, c.desc, err, c.err) {
|
||||
continue
|
||||
}
|
||||
if c.hash != h {
|
||||
if c.hash != hashed {
|
||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||
}
|
||||
}
|
||||
@@ -334,9 +334,10 @@ data:
|
||||
}
|
||||
}
|
||||
|
||||
// SkipRest returns true if there was a non-nil error or if we expected an 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.
|
||||
// SkipRest returns true if there was a non-nil error or if we expected an
|
||||
// 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.
|
||||
func SkipRest(t *testing.T, desc string, err error, contains string) bool {
|
||||
if err != nil {
|
||||
if len(contains) == 0 {
|
||||
|
||||
@@ -7,6 +7,7 @@ package ifc
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// Validator provides functions to validate annotations and labels
|
||||
@@ -123,7 +124,7 @@ type KunstructuredFactory interface {
|
||||
// KunstructuredHasher returns a hash of the argument
|
||||
// or an error.
|
||||
type KunstructuredHasher interface {
|
||||
Hash(Kunstructured) (string, error)
|
||||
Hash(*yaml.RNode) (string, error)
|
||||
}
|
||||
|
||||
// See core.v1.SecretTypeOpaque
|
||||
|
||||
@@ -37,22 +37,10 @@ func (o *multiTransformer) Transform(m resmap.ResMap) error {
|
||||
|
||||
func (o *multiTransformer) transform(m resmap.ResMap) error {
|
||||
for _, t := range o.transformers {
|
||||
err := t.Transform(m)
|
||||
if err != nil {
|
||||
if err := t.Transform(m); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
m.DropEmpties()
|
||||
}
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||
)
|
||||
|
||||
@@ -35,5 +36,8 @@ spec:
|
||||
- name: PODINFO_UI_COLOR
|
||||
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,10 +4,10 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/hasher"
|
||||
"sigs.k8s.io/kustomize/api/ifc"
|
||||
"sigs.k8s.io/kustomize/api/internal/conflict"
|
||||
"sigs.k8s.io/kustomize/api/internal/validate"
|
||||
"sigs.k8s.io/kustomize/api/internal/wrappy"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
)
|
||||
|
||||
@@ -145,17 +145,14 @@ import (
|
||||
// If you're reading this, plan not done.
|
||||
//
|
||||
type DepProvider struct {
|
||||
kFactory ifc.KunstructuredFactory
|
||||
resourceFactory *resource.Factory
|
||||
conflictDectectorFactory resource.ConflictDetectorFactory
|
||||
fieldValidator ifc.Validator
|
||||
}
|
||||
|
||||
func NewDepProvider() *DepProvider {
|
||||
kf := &wrappy.WNodeFactory{}
|
||||
rf := resource.NewFactory(kf)
|
||||
rf := resource.NewFactory(&hasher.Hasher{})
|
||||
return &DepProvider{
|
||||
kFactory: kf,
|
||||
resourceFactory: rf,
|
||||
conflictDectectorFactory: conflict.NewFactory(),
|
||||
fieldValidator: validate.NewFieldValidator(),
|
||||
@@ -166,10 +163,6 @@ func NewDefaultDepProvider() *DepProvider {
|
||||
return NewDepProvider()
|
||||
}
|
||||
|
||||
func (dp *DepProvider) GetKunstructuredFactory() ifc.KunstructuredFactory {
|
||||
return dp.kFactory
|
||||
}
|
||||
|
||||
func (dp *DepProvider) GetResourceFactory() *resource.Factory {
|
||||
return dp.resourceFactory
|
||||
}
|
||||
|
||||
@@ -36,6 +36,14 @@ func ParseGroupVersion(apiVersion string) (group, version string) {
|
||||
// GvkFromString makes a Gvk from the output of Gvk.String().
|
||||
func GvkFromString(s string) Gvk {
|
||||
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]
|
||||
if g == noGroup {
|
||||
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
|
||||
func (x Gvk) IsNamespaceableKind() bool {
|
||||
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) {
|
||||
for _, hey := range []struct {
|
||||
x Gvk
|
||||
|
||||
@@ -146,18 +146,10 @@ func newResMapFromResourceSlice(
|
||||
}
|
||||
|
||||
// NewResMapFromRNodeSlice returns a ResMap from a slice of RNodes
|
||||
func (rmF *Factory) NewResMapFromRNodeSlice(rnodes []*yaml.RNode) (ResMap, error) {
|
||||
var resources []*resource.Resource
|
||||
for _, rnode := range rnodes {
|
||||
s, err := rnode.String()
|
||||
func (rmF *Factory) NewResMapFromRNodeSlice(s []*yaml.RNode) (ResMap, error) {
|
||||
rs, err := rmF.resF.ResourcesFromRNodes(s)
|
||||
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(),
|
||||
},
|
||||
}
|
||||
for name, tc := range testcases {
|
||||
rnodes := []*yaml.RNode{
|
||||
yaml.MustParse(tc.input),
|
||||
}
|
||||
rm, err := rmF.NewResMapFromRNodeSlice(rnodes)
|
||||
for name := range testcases {
|
||||
tc := testcases[name]
|
||||
t.Run(name, func(t *testing.T) {
|
||||
rm, err := rmF.NewResMapFromRNodeSlice(
|
||||
[]*yaml.RNode{yaml.MustParse(tc.input)})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error in test case [%s]: %v", name, err)
|
||||
}
|
||||
if err = tc.expected.ErrorIfNotEqualLists(rm); err != nil {
|
||||
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
|
||||
// fails or finds too many, it might make sense to then try
|
||||
// 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 {
|
||||
// Size reports the number of resources.
|
||||
Size() int
|
||||
@@ -189,6 +192,9 @@ type ResMap interface {
|
||||
// Clear removes all resources and Ids.
|
||||
Clear()
|
||||
|
||||
// DropEmpties drops empty resources from the ResMap.
|
||||
DropEmpties()
|
||||
|
||||
// SubsetThatCouldBeReferencedByResource returns a ResMap subset
|
||||
// of self with resources that could be referenced by the
|
||||
// resource argument.
|
||||
@@ -231,9 +237,8 @@ type ResMap interface {
|
||||
// are selected by a Selector
|
||||
Select(types.Selector) ([]*resource.Resource, error)
|
||||
|
||||
// ToRNodeSlice converts the resources in the resmp
|
||||
// to a list of RNodes
|
||||
ToRNodeSlice() ([]*yaml.RNode, error)
|
||||
// ToRNodeSlice returns a copy of the resources as RNodes.
|
||||
ToRNodeSlice() []*yaml.RNode
|
||||
|
||||
// ApplySmPatch applies a strategic-merge patch to the
|
||||
// selected set of resources.
|
||||
|
||||
@@ -6,14 +6,12 @@ package resmap
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
kyaml_yaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
"sigs.k8s.io/yaml"
|
||||
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// resWrangler implements ResMap.
|
||||
@@ -38,6 +36,18 @@ func (m *resWrangler) Clear() {
|
||||
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.
|
||||
func (m *resWrangler) Size() int {
|
||||
return len(m.rList)
|
||||
@@ -66,22 +76,27 @@ func (m *resWrangler) Append(res *resource.Resource) error {
|
||||
return fmt.Errorf(
|
||||
"may not add resource with an already registered id: %s", id)
|
||||
}
|
||||
m.rList = append(m.rList, res)
|
||||
m.append(res)
|
||||
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.
|
||||
func (m *resWrangler) Remove(adios resid.ResId) error {
|
||||
tmp := newOne()
|
||||
var rList []*resource.Resource
|
||||
for _, r := range m.rList {
|
||||
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)
|
||||
}
|
||||
m.rList = tmp.rList
|
||||
m.rList = rList
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -118,16 +133,7 @@ func (m *resWrangler) Debug(title string) {
|
||||
} else {
|
||||
fmt.Println("---")
|
||||
}
|
||||
fmt.Printf("# %d %s\n", i, r.OrgId())
|
||||
m, err := r.Map()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
blob, err := yaml.Marshal(m)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(blob))
|
||||
fmt.Printf("# %d %s\n%s\n", i, r.OrgId(), r.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +279,7 @@ func (m *resWrangler) AsYaml() ([]byte, error) {
|
||||
firstObj := true
|
||||
var b []byte
|
||||
buf := bytes.NewBuffer(b)
|
||||
for _, res := range m.Resources() {
|
||||
for _, res := range m.rList {
|
||||
out, err := res.AsYAML()
|
||||
if err != nil {
|
||||
m, _ := res.Map()
|
||||
@@ -297,7 +303,7 @@ func (m *resWrangler) AsYaml() ([]byte, error) {
|
||||
func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
|
||||
m2, ok := other.(*resWrangler)
|
||||
if !ok {
|
||||
panic("bad cast")
|
||||
return fmt.Errorf("bad cast to resWrangler 1")
|
||||
}
|
||||
if m.Size() != m2.Size() {
|
||||
return fmt.Errorf(
|
||||
@@ -334,7 +340,7 @@ func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
|
||||
func (m *resWrangler) ErrorIfNotEqualLists(other ResMap) error {
|
||||
m2, ok := other.(*resWrangler)
|
||||
if !ok {
|
||||
panic("bad cast")
|
||||
return fmt.Errorf("bad cast to resWrangler 2")
|
||||
}
|
||||
if m.Size() != m2.Size() {
|
||||
return fmt.Errorf(
|
||||
@@ -388,7 +394,7 @@ func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
|
||||
}
|
||||
result := newOne()
|
||||
roleBindingNamespaces := getNamespacesForRoleBinding(referrer)
|
||||
for _, possibleTarget := range m.Resources() {
|
||||
for _, possibleTarget := range m.rList {
|
||||
id := possibleTarget.CurId()
|
||||
if !id.IsNamespaceableKind() {
|
||||
// A cluster-scoped resource can be referred to by anything.
|
||||
@@ -435,16 +441,21 @@ func getNamespacesForRoleBinding(r *resource.Resource) map[string]bool {
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *resWrangler) append(res *resource.Resource) {
|
||||
m.rList = append(m.rList, res)
|
||||
}
|
||||
|
||||
// AppendAll implements ResMap.
|
||||
func (m *resWrangler) AppendAll(other ResMap) error {
|
||||
if other == 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 {
|
||||
return err
|
||||
}
|
||||
@@ -457,7 +468,11 @@ func (m *resWrangler) AbsorbAll(other ResMap) error {
|
||||
if other == 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)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -522,7 +537,7 @@ func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range m.Resources() {
|
||||
for _, r := range m.rList {
|
||||
curId := r.CurId()
|
||||
orgId := r.OrgId()
|
||||
|
||||
@@ -567,78 +582,39 @@ func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ToRNodeSlice converts the resources in the resmp
|
||||
// to a list of RNodes
|
||||
func (m *resWrangler) ToRNodeSlice() ([]*kyaml_yaml.RNode, error) {
|
||||
var rnodes []*kyaml_yaml.RNode
|
||||
for _, r := range m.Resources() {
|
||||
s, err := r.AsYAML()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// ToRNodeSlice returns a copy of the resources as RNodes.
|
||||
func (m *resWrangler) ToRNodeSlice() []*kyaml.RNode {
|
||||
result := make([]*kyaml.RNode, len(m.rList))
|
||||
for i := range m.rList {
|
||||
result[i] = m.rList[i].AsRNode()
|
||||
}
|
||||
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(
|
||||
selectedSet *resource.IdSet, patch *resource.Resource) error {
|
||||
newRm := New()
|
||||
for _, res := range m.Resources() {
|
||||
if !selectedSet.Contains(res.CurId()) {
|
||||
newRm.Append(res)
|
||||
continue
|
||||
}
|
||||
var list []*resource.Resource
|
||||
for _, res := range m.rList {
|
||||
if selectedSet.Contains(res.CurId()) {
|
||||
patchCopy := patch.DeepCopy()
|
||||
patchCopy.CopyMergeMetaDataFieldsFrom(patch)
|
||||
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.
|
||||
if err := res.ApplySmPatch(patchCopy); err != nil {
|
||||
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 err != nil {
|
||||
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)
|
||||
if !res.IsEmpty() {
|
||||
list = append(list, res)
|
||||
}
|
||||
}
|
||||
m.Clear()
|
||||
m.AppendAll(newRm)
|
||||
return nil
|
||||
return m.appendAll(list)
|
||||
}
|
||||
|
||||
func (m *resWrangler) RemoveBuildAnnotations() {
|
||||
for _, r := range m.Resources() {
|
||||
for _, r := range m.rList {
|
||||
r.RemoveBuildAnnotations()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -877,13 +877,8 @@ rules:
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
rnodes, err := rm.ToRNodeSlice()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
b := bytes.NewBufferString("")
|
||||
for i, n := range rnodes {
|
||||
for i, n := range rm.ToRNodeSlice() {
|
||||
if i != 0 {
|
||||
b.WriteString("---\n")
|
||||
}
|
||||
|
||||
@@ -10,28 +10,33 @@ import (
|
||||
"strings"
|
||||
|
||||
"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/konfig"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// Factory makes instances of Resource.
|
||||
type Factory struct {
|
||||
kf ifc.KunstructuredFactory
|
||||
hasher ifc.KunstructuredHasher
|
||||
}
|
||||
|
||||
// NewFactory makes an instance of Factory.
|
||||
func NewFactory(kf ifc.KunstructuredFactory) *Factory {
|
||||
return &Factory{kf: kf}
|
||||
func NewFactory(h ifc.KunstructuredHasher) *Factory {
|
||||
return &Factory{hasher: h}
|
||||
}
|
||||
|
||||
// Hasher returns an ifc.KunstructuredHasher
|
||||
func (rf *Factory) Hasher() ifc.KunstructuredHasher {
|
||||
return rf.kf.Hasher()
|
||||
return rf.hasher
|
||||
}
|
||||
|
||||
// FromMap returns a new instance of 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.
|
||||
@@ -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.
|
||||
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())
|
||||
}
|
||||
|
||||
// FromMapAndOption returns a new instance of Resource with given options.
|
||||
func (rf *Factory) FromMapAndOption(
|
||||
m map[string]interface{}, args *types.GeneratorArgs) *Resource {
|
||||
return rf.makeOne(rf.kf.FromMap(m), types.NewGenArgs(args))
|
||||
}
|
||||
|
||||
// FromKunstructured returns a new instance of Resource.
|
||||
func (rf *Factory) FromKunstructured(u ifc.Kunstructured) *Resource {
|
||||
return rf.makeOne(u, nil)
|
||||
n, err := yaml.FromMap(m)
|
||||
if err != nil {
|
||||
// TODO: return err instead of log.
|
||||
log.Fatal(err)
|
||||
}
|
||||
return rf.makeOne(n, types.NewGenArgs(args))
|
||||
}
|
||||
|
||||
// makeOne returns a new instance of Resource.
|
||||
func (rf *Factory) makeOne(
|
||||
u ifc.Kunstructured, o *types.GenArgs) *Resource {
|
||||
if u == nil {
|
||||
log.Fatal("unstruct ifc must not be null")
|
||||
func (rf *Factory) makeOne(rn *yaml.RNode, o *types.GenArgs) *Resource {
|
||||
if rn == nil {
|
||||
log.Fatal("RNode must not be null")
|
||||
}
|
||||
if o == nil {
|
||||
o = types.NewGenArgs(nil)
|
||||
}
|
||||
r := &Resource{
|
||||
kunStr: u,
|
||||
options: o,
|
||||
}
|
||||
return r
|
||||
return &Resource{kunStr: rn, options: o}
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (rf *Factory) SliceFromBytes(in []byte) ([]*Resource, error) {
|
||||
kunStructs, err := rf.kf.SliceFromBytes(in)
|
||||
nodes, err := rf.RNodesFromBytes(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var result []*Resource
|
||||
for len(kunStructs) > 0 {
|
||||
u := kunStructs[0]
|
||||
kunStructs = kunStructs[1:]
|
||||
if strings.HasSuffix(u.GetKind(), "List") {
|
||||
m, err := u.Map()
|
||||
return rf.resourcesFromRNodes(nodes), nil
|
||||
}
|
||||
|
||||
// ResourcesFromRNodes converts RNodes to Resources.
|
||||
func (rf *Factory) ResourcesFromRNodes(
|
||||
nodes []*yaml.RNode) (result []*Resource, err error) {
|
||||
nodes, err = rf.dropBadNodes(nodes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := m["items"]
|
||||
itemsSlice, ok := items.([]interface{})
|
||||
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
|
||||
}
|
||||
return nil, fmt.Errorf("items in List is type %T, expected array", items)
|
||||
return nil, fmt.Errorf(
|
||||
"expected array in %s/items, but found %T", kind, items)
|
||||
}
|
||||
for _, item := range itemsSlice {
|
||||
itemJSON, err := json.Marshal(item)
|
||||
innerNodes, err := rf.convertObjectSliceToNodeSlice(slice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
innerU, err := rf.kf.SliceFromBytes(itemJSON)
|
||||
nodes = append(nodes, innerNodes...)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// convertObjectSlice converts a list of objects to a list of RNode.
|
||||
func (rf *Factory) convertObjectSliceToNodeSlice(
|
||||
objects []interface{}) (result []*yaml.RNode, err error) {
|
||||
var bytes []byte
|
||||
var nodes []*yaml.RNode
|
||||
for _, obj := range objects {
|
||||
bytes, err = json.Marshal(obj)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
// append innerU to kunStructs so nested Lists can be handled
|
||||
kunStructs = append(kunStructs, innerU...)
|
||||
}
|
||||
} else {
|
||||
result = append(result, rf.FromKunstructured(u))
|
||||
if !ignore {
|
||||
result = append(result, n)
|
||||
}
|
||||
}
|
||||
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
|
||||
// name.
|
||||
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
|
||||
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 {
|
||||
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
|
||||
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 {
|
||||
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 (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -151,35 +152,33 @@ spec:
|
||||
for name := range testCases {
|
||||
tc := testCases[name]
|
||||
t.Run(name, func(t *testing.T) {
|
||||
result, err := factory.SliceFromBytes([]byte(tc.input))
|
||||
result, err := factory.RNodesFromBytes([]byte(tc.input))
|
||||
if err != nil {
|
||||
t.Fatalf("%v: fails with err: %v", name, err)
|
||||
}
|
||||
if len(result) != len(tc.expected) {
|
||||
for i := range result {
|
||||
bytes, err := result[i].AsYAML()
|
||||
str, err := result[i].String()
|
||||
if err != nil {
|
||||
t.Fatalf("%v: result to YAML fails with err: %v", name, err)
|
||||
}
|
||||
tmp := string(bytes)
|
||||
t.Logf("--- %d:\n%s", i, tmp)
|
||||
t.Logf("--- %d:\n%s", i, str)
|
||||
}
|
||||
t.Fatalf(
|
||||
"%v: actual len %d != expected len %d",
|
||||
name, len(result), len(tc.expected))
|
||||
}
|
||||
for i := range tc.expected {
|
||||
bytes, err := result[i].AsYAML()
|
||||
str, err := result[i].String()
|
||||
if err != nil {
|
||||
t.Fatalf("%v: result to YAML fails with err: %v", name, err)
|
||||
}
|
||||
tmp := string(bytes)
|
||||
if tmp != tc.expected[i] {
|
||||
if str != tc.expected[i] {
|
||||
t.Fatalf(
|
||||
"%v: string mismatch in item %d\n"+
|
||||
"actual:\n-----\n%s\n-----\n"+
|
||||
"expected:\n-----\n%s\n-----\n",
|
||||
name, i, tmp, tc.expected[i])
|
||||
name, i, str, tc.expected[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -307,38 +306,32 @@ kind: List
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
tests := map[string]struct {
|
||||
input []types.PatchStrategicMerge
|
||||
expectedOut []*Resource
|
||||
expectedErr bool
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
"happy": {
|
||||
input: []types.PatchStrategicMerge{patchGood1, patchGood2},
|
||||
expectedOut: []*Resource{testDeployment, testConfigMap},
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "badFileName",
|
||||
"badFileName": {
|
||||
input: []types.PatchStrategicMerge{patchGood1, "doesNotExist"},
|
||||
expectedOut: []*Resource{},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "badData",
|
||||
"badData": {
|
||||
input: []types.PatchStrategicMerge{patchGood1, patchBad},
|
||||
expectedOut: []*Resource{},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "listOfPatches",
|
||||
"listOfPatches": {
|
||||
input: []types.PatchStrategicMerge{patchList},
|
||||
expectedOut: []*Resource{testDeployment, testConfigMap},
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "listWithAnchorReference",
|
||||
"listWithAnchorReference": {
|
||||
input: []types.PatchStrategicMerge{patchList2},
|
||||
expectedOut: []*Resource{testDeploymentA, testDeploymentB},
|
||||
// The error using kyaml is:
|
||||
@@ -349,27 +342,26 @@ kind: List
|
||||
// TODO(#3271) This shouldn't have an error, but does when kyaml is used.
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "listWithNoEntries",
|
||||
"listWithNoEntries": {
|
||||
input: []types.PatchStrategicMerge{patchList3},
|
||||
expectedOut: []*Resource{},
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "listWithNoItems",
|
||||
"listWithNoItems": {
|
||||
input: []types.PatchStrategicMerge{patchList4},
|
||||
expectedOut: []*Resource{},
|
||||
expectedErr: false,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
for n, test := range tests {
|
||||
t.Run(n, func(t *testing.T) {
|
||||
rs, err := factory.SliceFromPatches(ldr, test.input)
|
||||
if err != nil {
|
||||
assert.True(t, test.expectedErr,
|
||||
fmt.Sprintf("in test %s, got unexpected error: %v", test.name, err))
|
||||
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.False(t, test.expectedErr, "expected no error in "+n)
|
||||
assert.Equal(t, len(test.expectedOut), len(rs))
|
||||
for i := range rs {
|
||||
expYaml, err := test.expectedOut[i].AsYAML()
|
||||
@@ -378,5 +370,312 @@ kind: List
|
||||
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/ifc"
|
||||
"sigs.k8s.io/kustomize/api/internal/wrappy"
|
||||
"sigs.k8s.io/kustomize/api/konfig"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
@@ -26,7 +25,7 @@ import (
|
||||
// paired with metadata used by kustomize.
|
||||
// For more history, see sigs.k8s.io/kustomize/api/ifc.Unstructured
|
||||
type Resource struct {
|
||||
kunStr ifc.Kunstructured
|
||||
kunStr *kyaml.RNode
|
||||
options *types.GenArgs
|
||||
refBy []resid.ResId
|
||||
refVarNames []string
|
||||
@@ -55,23 +54,24 @@ var buildAnnotations = []string{
|
||||
buildAnnotationAllowKindChange,
|
||||
}
|
||||
|
||||
func (r *Resource) AsRNode() *kyaml.RNode {
|
||||
return r.kunStr.Copy()
|
||||
}
|
||||
|
||||
func (r *Resource) ResetPrimaryData(incoming *Resource) {
|
||||
r.kunStr = incoming.Copy()
|
||||
r.kunStr = incoming.kunStr.Copy()
|
||||
}
|
||||
|
||||
func (r *Resource) GetAnnotations() map[string]string {
|
||||
annotations := r.kunStr.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations, err := r.kunStr.GetAnnotations()
|
||||
if err != nil || annotations == nil {
|
||||
return make(map[string]string)
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
|
||||
func (r *Resource) Copy() ifc.Kunstructured {
|
||||
return r.kunStr.Copy()
|
||||
}
|
||||
|
||||
func (r *Resource) GetFieldValue(f string) (interface{}, error) {
|
||||
//nolint:staticcheck
|
||||
return r.kunStr.GetFieldValue(f)
|
||||
}
|
||||
|
||||
@@ -84,7 +84,16 @@ func (r *Resource) GetBinaryDataMap() map[string]string {
|
||||
}
|
||||
|
||||
func (r *Resource) GetGvk() resid.Gvk {
|
||||
return r.kunStr.GetGvk()
|
||||
meta, err := r.kunStr.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.KunstructuredHasher) (string, error) {
|
||||
return h.Hash(r.kunStr)
|
||||
}
|
||||
|
||||
func (r *Resource) GetKind() string {
|
||||
@@ -92,7 +101,11 @@ func (r *Resource) GetKind() string {
|
||||
}
|
||||
|
||||
func (r *Resource) GetLabels() map[string]string {
|
||||
return r.kunStr.GetLabels()
|
||||
l, err := r.kunStr.GetLabels()
|
||||
if err != nil {
|
||||
return map[string]string{}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (r *Resource) GetName() string {
|
||||
@@ -100,16 +113,17 @@ func (r *Resource) GetName() string {
|
||||
}
|
||||
|
||||
func (r *Resource) GetSlice(p string) ([]interface{}, error) {
|
||||
//nolint:staticcheck
|
||||
return r.kunStr.GetSlice(p)
|
||||
}
|
||||
|
||||
func (r *Resource) GetString(p string) (string, error) {
|
||||
//nolint:staticcheck
|
||||
return r.kunStr.GetString(p)
|
||||
}
|
||||
|
||||
func (r *Resource) IsEmpty() (bool, error) {
|
||||
m, err := r.kunStr.Map()
|
||||
return len(m) == 0, err
|
||||
func (r *Resource) IsEmpty() bool {
|
||||
return r.kunStr.IsNilOrEmpty()
|
||||
}
|
||||
|
||||
func (r *Resource) Map() (map[string]interface{}, error) {
|
||||
@@ -146,7 +160,10 @@ func (r *Resource) SetBinaryDataMap(m map[string]string) {
|
||||
}
|
||||
|
||||
func (r *Resource) SetGvk(gvk resid.Gvk) {
|
||||
r.kunStr.SetGvk(gvk)
|
||||
r.kunStr.SetMapField(
|
||||
kyaml.NewScalarRNode(gvk.Kind), kyaml.KindField)
|
||||
r.kunStr.SetMapField(
|
||||
kyaml.NewScalarRNode(gvk.ApiVersion()), kyaml.APIVersionField)
|
||||
}
|
||||
|
||||
func (r *Resource) SetLabels(m map[string]string) {
|
||||
@@ -193,7 +210,7 @@ type ResCtxMatcher func(ResCtx) bool
|
||||
// DeepCopy returns a new copy of resource
|
||||
func (r *Resource) DeepCopy() *Resource {
|
||||
rc := &Resource{
|
||||
kunStr: r.Copy(),
|
||||
kunStr: r.kunStr.Copy(),
|
||||
}
|
||||
rc.copyOtherFields(r)
|
||||
return rc
|
||||
@@ -541,11 +558,7 @@ func (r *Resource) ApplySmPatch(patch *Resource) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
empty, err := r.IsEmpty()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if empty {
|
||||
if r.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
if !patch.KindChangeAllowed() {
|
||||
@@ -559,15 +572,12 @@ func (r *Resource) ApplySmPatch(patch *Resource) error {
|
||||
}
|
||||
|
||||
func (r *Resource) ApplyFilter(f kio.Filter) error {
|
||||
if wn, ok := r.kunStr.(*wrappy.WNode); ok {
|
||||
l, err := f.Filter([]*kyaml.RNode{wn.AsRNode()})
|
||||
l, err := f.Filter([]*kyaml.RNode{r.kunStr})
|
||||
if len(l) == 0 {
|
||||
// Hack to deal with deletion.
|
||||
r.kunStr = wrappy.NewWNode()
|
||||
// The node was deleted. The following makes r.IsEmpty() true.
|
||||
r.kunStr = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return filtersutil.ApplyToJSON(f, r)
|
||||
}
|
||||
|
||||
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 {
|
||||
input 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,10 +46,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
resourceList.Items, err = resMap.ToRNodeSlice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resourceList.Items = resMap.ToRNodeSlice()
|
||||
return nil
|
||||
})
|
||||
if err := framework.Execute(&processor, nil); err != nil {
|
||||
|
||||
@@ -49,8 +49,8 @@ See https://sigs.k8s.io/kustomize
|
||||
completion.NewCommand(),
|
||||
makeBuildCommand(fSys, stdOut),
|
||||
edit.NewCmdEdit(
|
||||
fSys, pvd.GetFieldValidator(), pvd.GetKunstructuredFactory()),
|
||||
create.NewCmdCreate(fSys, pvd.GetKunstructuredFactory()),
|
||||
fSys, pvd.GetFieldValidator(), pvd.GetResourceFactory()),
|
||||
create.NewCmdCreate(fSys, pvd.GetResourceFactory()),
|
||||
version.NewCmdVersion(stdOut),
|
||||
openapi.NewCmdOpenAPI(stdOut),
|
||||
)
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"sigs.k8s.io/kustomize/api/filesys"
|
||||
"sigs.k8s.io/kustomize/api/ifc"
|
||||
"sigs.k8s.io/kustomize/api/konfig"
|
||||
"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/util"
|
||||
)
|
||||
@@ -31,7 +31,7 @@ type createFlags struct {
|
||||
}
|
||||
|
||||
// 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}
|
||||
c := &cobra.Command{
|
||||
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-
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runCreate(opts, fSys, uf)
|
||||
return runCreate(opts, fSys, rf)
|
||||
},
|
||||
}
|
||||
c.Flags().StringVar(
|
||||
@@ -95,7 +95,7 @@ func NewCmdCreate(fSys filesys.FileSystem, uf ifc.KunstructuredFactory) *cobra.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 err error
|
||||
if opts.resources != "" {
|
||||
@@ -108,7 +108,7 @@ func runCreate(opts createFlags, fSys filesys.FileSystem, uf ifc.KunstructuredFa
|
||||
return fmt.Errorf("kustomization file already exists")
|
||||
}
|
||||
if opts.detectResources {
|
||||
detected, err := detectResources(fSys, uf, opts.path, opts.detectRecursive)
|
||||
detected, err := detectResources(fSys, rf, opts.path, opts.detectRecursive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -149,7 +149,7 @@ func runCreate(opts createFlags, fSys filesys.FileSystem, uf ifc.KunstructuredFa
|
||||
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
|
||||
err := fSys.Walk(base, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
@@ -176,7 +176,7 @@ func detectResources(fSys filesys.FileSystem, uf ifc.KunstructuredFactory, base
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := uf.SliceFromBytes(fContents); err != nil {
|
||||
if _, err := rf.SliceFromBytes(fContents); err != nil {
|
||||
return nil
|
||||
}
|
||||
paths = append(paths, path)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"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 {
|
||||
kf, err := kustfile.NewKustomizationFile(fSys)
|
||||
|
||||
@@ -7,13 +7,14 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"sigs.k8s.io/kustomize/api/filesys"
|
||||
"sigs.k8s.io/kustomize/api/ifc"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
)
|
||||
|
||||
// NewCmdAdd returns an instance of 'add' subcommand.
|
||||
func NewCmdAdd(
|
||||
fSys filesys.FileSystem,
|
||||
ldr ifc.KvLoader,
|
||||
kf ifc.KunstructuredFactory) *cobra.Command {
|
||||
rf *resource.Factory) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "add",
|
||||
Short: "Adds an item to the kustomization file.",
|
||||
@@ -53,8 +54,8 @@ func NewCmdAdd(
|
||||
newCmdAddResource(fSys),
|
||||
newCmdAddPatch(fSys),
|
||||
newCmdAddComponent(fSys),
|
||||
newCmdAddSecret(fSys, ldr, kf),
|
||||
newCmdAddConfigMap(fSys, ldr, kf),
|
||||
newCmdAddSecret(fSys, ldr, rf),
|
||||
newCmdAddConfigMap(fSys, ldr, rf),
|
||||
newCmdAddBase(fSys),
|
||||
newCmdAddLabel(fSys, ldr.Validator().MakeLabelValidator()),
|
||||
newCmdAddAnnotation(fSys, ldr.Validator().MakeAnnotationValidator()),
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"sigs.k8s.io/kustomize/api/filesys"
|
||||
"sigs.k8s.io/kustomize/api/ifc"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile"
|
||||
)
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
func newCmdAddConfigMap(
|
||||
fSys filesys.FileSystem,
|
||||
ldr ifc.KvLoader,
|
||||
kf ifc.KunstructuredFactory) *cobra.Command {
|
||||
rf *resource.Factory) *cobra.Command {
|
||||
var flags flagsAndArgs
|
||||
cmd := &cobra.Command{
|
||||
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.
|
||||
err = addConfigMap(ldr, kustomization, flags, kf)
|
||||
err = addConfigMap(ldr, kustomization, flags, rf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -106,13 +107,13 @@ func newCmdAddConfigMap(
|
||||
func addConfigMap(
|
||||
ldr ifc.KvLoader,
|
||||
k *types.Kustomization,
|
||||
flags flagsAndArgs, kf ifc.KunstructuredFactory) error {
|
||||
flags flagsAndArgs, rf *resource.Factory) error {
|
||||
args := findOrMakeConfigMapArgs(k, flags.Name)
|
||||
mergeFlagsIntoCmArgs(args, flags)
|
||||
// Validate by trying to create corev1.configmap.
|
||||
args.Options = types.MergeGlobalOptionsIntoLocal(
|
||||
args.Options, k.GeneratorOptions)
|
||||
_, err := kf.MakeConfigMap(ldr, args)
|
||||
_, err := rf.MakeConfigMap(ldr, args)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"sigs.k8s.io/kustomize/api/filesys"
|
||||
"sigs.k8s.io/kustomize/api/ifc"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile"
|
||||
)
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
func newCmdAddSecret(
|
||||
fSys filesys.FileSystem,
|
||||
ldr ifc.KvLoader,
|
||||
kf ifc.KunstructuredFactory) *cobra.Command {
|
||||
rf *resource.Factory) *cobra.Command {
|
||||
var flags flagsAndArgs
|
||||
cmd := &cobra.Command{
|
||||
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.
|
||||
err = addSecret(ldr, kustomization, flags, kf)
|
||||
err = addSecret(ldr, kustomization, flags, rf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -106,13 +107,13 @@ func newCmdAddSecret(
|
||||
func addSecret(
|
||||
ldr ifc.KvLoader,
|
||||
k *types.Kustomization,
|
||||
flags flagsAndArgs, kf ifc.KunstructuredFactory) error {
|
||||
flags flagsAndArgs, rf *resource.Factory) error {
|
||||
args := findOrMakeSecretArgs(k, flags.Name, flags.Namespace, flags.Type)
|
||||
mergeFlagsIntoGeneratorArgs(&args.GeneratorArgs, flags)
|
||||
// Validate by trying to create corev1.secret.
|
||||
args.Options = types.MergeGlobalOptionsIntoLocal(
|
||||
args.Options, k.GeneratorOptions)
|
||||
_, err := kf.MakeSecret(ldr, args)
|
||||
_, err := rf.MakeSecret(ldr, args)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"sigs.k8s.io/kustomize/api/ifc"
|
||||
"sigs.k8s.io/kustomize/api/kv"
|
||||
"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/fix"
|
||||
"sigs.k8s.io/kustomize/kustomize/v4/commands/edit/listbuiltin"
|
||||
@@ -18,7 +19,7 @@ import (
|
||||
|
||||
// NewCmdEdit returns an instance of 'edit' subcommand.
|
||||
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{
|
||||
Use: "edit",
|
||||
Short: "Edits a kustomization file",
|
||||
@@ -40,7 +41,7 @@ func NewCmdEdit(
|
||||
add.NewCmdAdd(
|
||||
fSys,
|
||||
kv.NewLoader(loader.NewFileLoaderAtCwd(fSys), v),
|
||||
kf),
|
||||
rf),
|
||||
set.NewCmdSet(
|
||||
fSys,
|
||||
kv.NewLoader(loader.NewFileLoaderAtCwd(fSys), v),
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -336,6 +337,33 @@ func (rn *RNode) SetYNode(node *yaml.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.
|
||||
func (rn *RNode) GetNamespace() (string, error) {
|
||||
meta, err := rn.GetMeta()
|
||||
@@ -752,7 +780,7 @@ func (rn *RNode) GetValidatedMetadata() (ResourceMeta, error) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// MatchesAnnotationSelector implements ifc.Kunstructured.
|
||||
// MatchesAnnotationSelector returns true on a selector match to annotations.
|
||||
func (rn *RNode) MatchesAnnotationSelector(selector string) (bool, error) {
|
||||
s, err := labels.Parse(selector)
|
||||
if err != nil {
|
||||
@@ -765,7 +793,7 @@ func (rn *RNode) MatchesAnnotationSelector(selector string) (bool, error) {
|
||||
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) {
|
||||
s, err := labels.Parse(selector)
|
||||
if err != nil {
|
||||
@@ -859,3 +887,123 @@ func checkKey(key string, elems []*Node) bool {
|
||||
}
|
||||
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/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func (p *plugin) Config(
|
||||
func (p *plugin) Transform(m resmap.ResMap) error {
|
||||
for _, res := range m.Resources() {
|
||||
if res.NeedHashSuffix() {
|
||||
h, err := p.hasher.Hash(res)
|
||||
h, err := res.Hash(p.hasher)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -34,20 +34,15 @@ func (p *plugin) Transform(m resmap.ResMap) error {
|
||||
return nil
|
||||
}
|
||||
for _, r := range m.Resources() {
|
||||
empty, err := r.IsEmpty()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if empty {
|
||||
if r.IsEmpty() {
|
||||
// Don't mutate empty objects?
|
||||
continue
|
||||
}
|
||||
r.StorePreviousId()
|
||||
err = r.ApplyFilter(namespace.Filter{
|
||||
if err := r.ApplyFilter(namespace.Filter{
|
||||
Namespace: p.Namespace,
|
||||
FsSlice: p.FieldSpecs,
|
||||
})
|
||||
if err != nil {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)
|
||||
|
||||
Reference in New Issue
Block a user