This commit is contained in:
Eyob Tefera
2020-08-10 18:26:16 +00:00
139 changed files with 3470 additions and 1231 deletions

View File

@@ -29,10 +29,11 @@ func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
func(node *yaml.RNode) (*yaml.RNode, error) {
for _, k := range keys {
if err := node.PipeE(fsslice.Filter{
FsSlice: f.FsSlice,
SetValue: filtersutil.SetEntry(k, f.Annotations[k], yaml.StringTag),
FsSlice: f.FsSlice,
SetValue: filtersutil.SetEntry(
k, f.Annotations[k], yaml.NodeTagString),
CreateKind: yaml.MappingNode, // Annotations are MappingNodes.
CreateTag: "!!map", // TODO: change to yaml.NodeTagMap
CreateTag: yaml.NodeTagMap,
}); err != nil {
return nil, err
}

View File

@@ -86,7 +86,7 @@ func (fltr Filter) field(obj *yaml.RNode) error {
// create the field if it is missing: must be a mapping node
lookupField = yaml.LookupCreate(yaml.MappingNode, fieldName)
kind = yaml.MappingNode
tag = "!!map" // TODO: change to yaml.NodeTagMap
tag = yaml.NodeTagMap
}
// locate (or maybe create) the field
@@ -97,7 +97,7 @@ func (fltr Filter) field(obj *yaml.RNode) error {
// if the value exists, but is null, then change it to the creation type
// TODO: update yaml.LookupCreate to support this
if field.YNode().Tag == "!!null" { // TODO: change to yaml.NodeTagNull
if field.YNode().Tag == yaml.NodeTagNull {
field.YNode().Kind = kind
field.YNode().Tag = tag
}

View File

@@ -21,7 +21,7 @@ func SetEntry(key, value, tag string) SetFn {
Value: value,
Tag: tag,
}
if tag == yaml.StringTag && yaml.IsYaml1_1NonString(n) {
if tag == yaml.NodeTagString && yaml.IsYaml1_1NonString(n) {
n.Style = yaml.DoubleQuotedStyle
}
return func(node *yaml.RNode) error {

View File

@@ -30,10 +30,11 @@ func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
func(node *yaml.RNode) (*yaml.RNode, error) {
for _, k := range keys {
if err := node.PipeE(fsslice.Filter{
FsSlice: f.FsSlice,
SetValue: filtersutil.SetEntry(k, f.Labels[k], yaml.StringTag),
FsSlice: f.FsSlice,
SetValue: filtersutil.SetEntry(
k, f.Labels[k], yaml.NodeTagString),
CreateKind: yaml.MappingNode, // Labels are MappingNodes.
CreateTag: "!!map", // TODO: change to yaml.NodeTagMap
CreateTag: yaml.NodeTagMap,
}); err != nil {
return nil, err
}

View File

@@ -0,0 +1,3 @@
// Package nameref contains a kio.Filter implementation of the kustomize
// name reference transformer.
package nameref

View File

@@ -0,0 +1,214 @@
package nameref
import (
"fmt"
"sigs.k8s.io/kustomize/api/filters/fieldspec"
"sigs.k8s.io/kustomize/api/filters/filtersutil"
"sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Filter will update the name reference
type Filter struct {
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"`
Referrer *resource.Resource
Target resid.Gvk
ReferralCandidates resmap.ResMap
}
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
return kio.FilterAll(yaml.FilterFunc(f.run)).Filter(nodes)
}
func (f Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
err := node.PipeE(fieldspec.Filter{
FieldSpec: f.FieldSpec,
SetValue: f.set,
})
return node, err
}
func (f Filter) set(node *yaml.RNode) error {
if yaml.IsMissingOrNull(node) {
return nil
}
switch node.YNode().Kind {
case yaml.ScalarNode:
return f.setScalar(node)
case yaml.MappingNode:
// Kind: ValidatingWebhookConfiguration
// FieldSpec is webhooks/clientConfig/service
return f.setMapping(node)
case yaml.SequenceNode:
return f.setSequence(node)
default:
return fmt.Errorf(
"node is expected to be either a string or a slice of string or a map of string")
}
}
func (f Filter) setSequence(node *yaml.RNode) error {
return applyFilterToSeq(seqFilter{
setScalarFn: f.setScalar,
setMappingFn: f.setMapping,
}, node)
}
func (f Filter) setMapping(node *yaml.RNode) error {
return setNameAndNs(
node,
f.Referrer,
f.Target,
f.ReferralCandidates,
)
}
func (f Filter) setScalar(node *yaml.RNode) error {
newValue, err := getSimpleNameField(
node.YNode().Value,
f.Referrer,
f.Target,
f.ReferralCandidates,
f.ReferralCandidates.Resources(),
)
if err != nil {
return err
}
err = filtersutil.SetScalar(newValue)(node)
if err != nil {
return err
}
return nil
}
// selectReferral picks the referral among a subset of candidates.
// It returns the current name and namespace of the selected candidate.
// Note that the content of the referricalCandidateSubset slice is most of the time
// identical to the referralCandidates resmap. Still in some cases, such
// as ClusterRoleBinding, the subset only contains the resources of a specific
// namespace.
func selectReferral(
oldName string,
referrer *resource.Resource,
target resid.Gvk,
referralCandidates resmap.ResMap,
referralCandidateSubset []*resource.Resource) (string, string, error) {
for _, res := range referralCandidateSubset {
id := res.OrgId()
if id.IsSelected(&target) && res.GetOriginalName() == oldName {
matches := referralCandidates.GetMatchingResourcesByOriginalId(id.Equals)
// If there's more than one match, there's no way
// to know which one to pick, so emit error.
if len(matches) > 1 {
return "", "", fmt.Errorf(
"multiple matches for %s:\n %v",
id, getIds(matches))
}
// In the resource, note that it is referenced
// by the referrer.
res.AppendRefBy(referrer.CurId())
// Return transformed name of the object,
// complete with prefixes, hashes, etc.
return res.GetName(), res.GetNamespace(), nil
}
}
return oldName, "", nil
}
// utility function to replace a simple string by the new name
func getSimpleNameField(
oldName string,
referrer *resource.Resource,
target resid.Gvk,
referralCandidates resmap.ResMap,
referralCandidateSubset []*resource.Resource) (string, error) {
newName, _, err := selectReferral(oldName, referrer, target,
referralCandidates, referralCandidateSubset)
return newName, err
}
func getIds(rs []*resource.Resource) []string {
var result []string
for _, r := range rs {
result = append(result, r.CurId().String()+"\n")
}
return result
}
// utility function to replace name field within a map RNode
// and leverage the namespace field.
func setNameAndNs(
in *yaml.RNode,
referrer *resource.Resource,
target resid.Gvk,
referralCandidates resmap.ResMap) error {
if in.YNode().Kind != yaml.MappingNode {
return fmt.Errorf("expect a mapping node")
}
// Get name field
nameNode, err := in.Pipe(yaml.FieldMatcher{
Name: "name",
})
if err != nil || nameNode == nil {
return fmt.Errorf("cannot find field 'name' in node")
}
// Get namespace field
namespaceNode, err := in.Pipe(yaml.FieldMatcher{
Name: "namespace",
})
if err != nil {
return fmt.Errorf("error when find field 'namespace'")
}
// check is namespace matched
// name will bot be updated if the namespace doesn't match
subset := referralCandidates.Resources()
if namespaceNode != nil {
namespace := namespaceNode.YNode().Value
bynamespace := referralCandidates.GroupedByOriginalNamespace()
if _, ok := bynamespace[namespace]; !ok {
return nil
}
subset = bynamespace[namespace]
}
oldName := nameNode.YNode().Value
newname, newnamespace, err := selectReferral(oldName, referrer, target,
referralCandidates, subset)
if err != nil {
return err
}
if (newname == oldName) && (newnamespace == "") {
// no candidate found.
return nil
}
// set name
in.Pipe(yaml.FieldSetter{
Name: "name",
StringValue: newname,
})
if newnamespace != "" {
// We don't want value "" to replace value "default" since
// the empty string is handled as a wild card here not default namespace
// by kubernetes.
in.Pipe(yaml.FieldSetter{
Name: "namespace",
StringValue: newnamespace,
})
}
return nil
}

View File

@@ -0,0 +1,333 @@
package nameref
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
"sigs.k8s.io/kustomize/api/types"
)
func TestNamerefFilter(t *testing.T) {
testCases := map[string]struct {
input string
candidates string
expected string
filter Filter
originalNames []string
}{
"simple scalar": {
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: dep
ref:
name: oldName
`,
candidates: `
apiVersion: apps/v1
kind: Secret
metadata:
name: newName
---
apiVersion: apps/v1
kind: NotSecret
metadata:
name: newName2
`,
originalNames: []string{"oldName", ""},
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: dep
ref:
name: newName
`,
filter: Filter{
FieldSpec: types.FieldSpec{Path: "ref/name"},
Target: resid.Gvk{
Group: "apps",
Version: "v1",
Kind: "Secret",
},
},
},
"sequence": {
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: dep
seq:
- oldName1
- oldName2
`,
candidates: `
apiVersion: apps/v1
kind: Secret
metadata:
name: newName
---
apiVersion: apps/v1
kind: NotSecret
metadata:
name: newName2
`,
originalNames: []string{"oldName1", ""},
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: dep
seq:
- newName
- oldName2
`,
filter: Filter{
FieldSpec: types.FieldSpec{Path: "seq"},
Target: resid.Gvk{
Group: "apps",
Version: "v1",
Kind: "Secret",
},
},
},
"mapping": {
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: dep
map:
name: oldName
`,
candidates: `
apiVersion: apps/v1
kind: Secret
metadata:
name: newName
---
apiVersion: apps/v1
kind: NotSecret
metadata:
name: newName2
`,
originalNames: []string{"oldName", ""},
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: dep
map:
name: newName
`,
filter: Filter{
FieldSpec: types.FieldSpec{Path: "map"},
Target: resid.Gvk{
Group: "apps",
Version: "v1",
Kind: "Secret",
},
},
},
"mapping with namespace": {
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: dep
map:
name: oldName
namespace: oldNs
`,
candidates: `
apiVersion: apps/v1
kind: Secret
metadata:
name: newName
namespace: oldNs
---
apiVersion: apps/v1
kind: NotSecret
metadata:
name: newName2
`,
originalNames: []string{"oldName", ""},
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: dep
map:
name: newName
namespace: oldNs
`,
filter: Filter{
FieldSpec: types.FieldSpec{Path: "map"},
Target: resid.Gvk{
Group: "apps",
Version: "v1",
Kind: "Secret",
},
},
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
factory := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())
referrer, err := factory.FromBytes([]byte(tc.input))
if err != nil {
t.Fatal(err)
}
tc.filter.Referrer = referrer
resMapFactory := resmap.NewFactory(factory, nil)
candidatesRes, err := factory.SliceFromBytesWithNames(
tc.originalNames, []byte(tc.candidates))
if err != nil {
t.Fatal(err)
}
candidates := resMapFactory.FromResourceSlice(candidatesRes)
tc.filter.ReferralCandidates = candidates
if !assert.Equal(t,
strings.TrimSpace(tc.expected),
strings.TrimSpace(
filtertest_test.RunFilter(t, tc.input, tc.filter))) {
t.FailNow()
}
})
}
}
func TestNamerefFilterUnhappy(t *testing.T) {
testCases := map[string]struct {
input string
candidates string
expected string
filter Filter
originalNames []string
}{
"invalid node type": {
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: dep
ref:
name: null
`,
candidates: "",
originalNames: []string{},
expected: "obj '' at path 'ref/name': node is expected to be either a string or a slice of string or a map of string",
filter: Filter{
FieldSpec: types.FieldSpec{Path: "ref/name"},
Target: resid.Gvk{
Group: "apps",
Version: "v1",
Kind: "Secret",
},
},
},
"multiple match": {
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: dep
ref:
name: oldName
`,
candidates: `
apiVersion: apps/v1
kind: Secret
metadata:
name: newName
---
apiVersion: apps/v1
kind: Secret
metadata:
name: newName2
`,
originalNames: []string{"oldName", "oldName"},
expected: "",
filter: Filter{
FieldSpec: types.FieldSpec{Path: "ref/name"},
Target: resid.Gvk{
Group: "apps",
Version: "v1",
Kind: "Secret",
},
},
},
"no name": {
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: dep
ref:
notName: oldName
`,
candidates: `
apiVersion: apps/v1
kind: Secret
metadata:
name: newName
---
apiVersion: apps/v1
kind: Secret
metadata:
name: newName2
`,
originalNames: []string{"oldName", "oldName"},
expected: "",
filter: Filter{
FieldSpec: types.FieldSpec{Path: "ref"},
Target: resid.Gvk{
Group: "apps",
Version: "v1",
Kind: "Secret",
},
},
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
factory := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())
referrer, err := factory.FromBytes([]byte(tc.input))
if err != nil {
t.Fatal(err)
}
tc.filter.Referrer = referrer
resMapFactory := resmap.NewFactory(factory, nil)
candidatesRes, err := factory.SliceFromBytesWithNames(
tc.originalNames, []byte(tc.candidates))
if err != nil {
t.Fatal(err)
}
candidates := resMapFactory.FromResourceSlice(candidatesRes)
tc.filter.ReferralCandidates = candidates
_, err = filtertest_test.RunFilterE(t, tc.input, tc.filter)
if err == nil {
t.Fatalf("expect an error")
}
if tc.expected != "" && !assert.EqualError(t, err, tc.expected) {
t.FailNow()
}
})
}
}

View File

@@ -0,0 +1,57 @@
package nameref
import (
"fmt"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
type setFn func(*yaml.RNode) error
type seqFilter struct {
setScalarFn setFn
setMappingFn setFn
}
func (sf seqFilter) Filter(node *yaml.RNode) (*yaml.RNode, error) {
if yaml.IsMissingOrNull(node) {
return node, nil
}
switch node.YNode().Kind {
case yaml.ScalarNode:
// Kind: Role/ClusterRole
// FieldSpec is rules.resourceNames
err := sf.setScalarFn(node)
return node, err
case yaml.MappingNode:
// Kind: RoleBinding/ClusterRoleBinding
// FieldSpec is subjects
// Note: The corresponding fieldSpec had been changed from
// from path: subjects/name to just path: subjects. This is
// what get mutatefield to request the mapping of the whole
// map containing namespace and name instead of just a simple
// string field containing the name
err := sf.setMappingFn(node)
return node, err
default:
return node, fmt.Errorf(
"%#v is expected to be either a string or a map of string", node)
}
}
// applyFilterToSeq will apply the filter to each element in the sequence node
func applyFilterToSeq(filter yaml.Filter, node *yaml.RNode) error {
if node.YNode().Kind != yaml.SequenceNode {
return fmt.Errorf("expect a sequence node but got %v", node.YNode().Kind)
}
for _, elem := range node.Content() {
rnode := yaml.NewRNode(elem)
err := rnode.PipeE(filter)
if err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,80 @@
package nameref
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func SeqFilter(node *yaml.RNode) (*yaml.RNode, error) {
if node.YNode().Value == "aaa" {
node.YNode().SetString("ccc")
}
return node, nil
}
func TestApplyFilterToSeq(t *testing.T) {
fltr := yaml.FilterFunc(SeqFilter)
testCases := map[string]struct {
input string
expect string
}{
"replace in seq": {
input: `
- aaa
- bbb`,
expect: `
- ccc
- bbb`,
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
node, err := yaml.Parse(tc.input)
if err != nil {
t.Fatal(err)
}
err = applyFilterToSeq(fltr, node)
if err != nil {
t.Fatal(err)
}
if !assert.Equal(t,
strings.TrimSpace(tc.expect),
strings.TrimSpace(node.MustString())) {
t.Fatalf("expect:\n%s\nactual:\n%s",
strings.TrimSpace(tc.expect),
strings.TrimSpace(node.MustString()))
}
})
}
}
func TestApplyFilterToSeqUnhappy(t *testing.T) {
fltr := yaml.FilterFunc(SeqFilter)
testCases := map[string]struct {
input string
}{
"replace in seq": {
input: `
aaa`,
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
node, err := yaml.Parse(tc.input)
if err != nil {
t.Fatal(err)
}
err = applyFilterToSeq(fltr, node)
if err == nil {
t.Fatalf("expect an error")
}
})
}
}

View File

@@ -46,7 +46,7 @@ func (ns Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
FsSlice: ns.FsSlice,
SetValue: filtersutil.SetScalar(ns.Namespace),
CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
CreateTag: yaml.StringTag,
CreateTag: yaml.NodeTagString,
})
return node, err
}
@@ -112,7 +112,7 @@ func (ns Filter) roleBindingHack(obj *yaml.RNode, meta yaml.ResourceMeta) error
// Lookup the namespace field on all elements.
// We should change the fieldspec so this isn't necessary.
obj, err := obj.Pipe(yaml.Lookup(subjectsField))
if err != nil || yaml.IsEmpty(obj) {
if err != nil || yaml.IsMissingOrNull(obj) {
return err
}
@@ -125,7 +125,7 @@ func (ns Filter) roleBindingHack(obj *yaml.RNode, meta yaml.ResourceMeta) error
name, err := o.Pipe(
yaml.Lookup("name"), yaml.Match("default"),
)
if err != nil || yaml.IsEmpty(name) {
if err != nil || yaml.IsMissingOrNull(name) {
return err
}

View File

@@ -32,7 +32,7 @@ func (f Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
FieldSpec: f.FieldSpec,
SetValue: f.evaluateField,
CreateKind: yaml.ScalarNode, // Name is a ScalarNode
CreateTag: yaml.StringTag,
CreateTag: yaml.NodeTagString,
})
return node, err
}

View File

@@ -66,8 +66,7 @@ func updateNodeValue(node *yaml.Node, newValue interface{}) {
}
func (f Filter) setScalar(node *yaml.RNode) error {
if node.YNode().Kind != yaml.ScalarNode || node.YNode().Tag != yaml.StringTag {
// Only process string values
if !yaml.IsYNodeString(node.YNode()) {
return nil
}
v := expansion2.Expand(node.YNode().Value, f.MappingFunc)
@@ -78,11 +77,10 @@ func (f Filter) setScalar(node *yaml.RNode) error {
func (f Filter) setMap(node *yaml.RNode) error {
contents := node.YNode().Content
for i := 0; i < len(contents); i += 2 {
if contents[i].Kind != yaml.ScalarNode || contents[i].Tag != yaml.StringTag {
if !yaml.IsYNodeString(contents[i]) {
return fmt.Errorf("invalid map key: %s, type: %s", contents[i].Value, contents[i].Tag)
}
if contents[i+1].Kind != yaml.ScalarNode || contents[i+1].Tag != yaml.StringTag {
// value is not a string
if !yaml.IsYNodeString(contents[i+1]) {
continue
}
newValue := expansion2.Expand(contents[i+1].Value, f.MappingFunc)
@@ -93,8 +91,7 @@ func (f Filter) setMap(node *yaml.RNode) error {
func (f Filter) setSeq(node *yaml.RNode) error {
for _, item := range node.YNode().Content {
if item.Kind != yaml.ScalarNode || item.Tag != yaml.StringTag {
// value is not a string
if !yaml.IsYNodeString(item) {
return fmt.Errorf("invalid value type expect a string")
}
newValue := expansion2.Expand(item.Value, f.MappingFunc)

View File

@@ -8,6 +8,7 @@ import (
expansion2 "sigs.k8s.io/kustomize/api/internal/accumulator/expansion"
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func TestFilter(t *testing.T) {
@@ -251,7 +252,7 @@ metadata:
config.kubernetes.io/index: '0'
data:
1: str
' at path 'data': invalid map key: 1, type: !!int`,
' at path 'data': invalid map key: 1, type: ` + yaml.NodeTagInt,
filter: Filter{
MappingFunc: expansion2.MappingFuncFor(replacementCounts, map[string]interface{}{
"VAR": int64(5),

View File

@@ -27,7 +27,7 @@ func (rc Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
FieldSpec: rc.FieldSpec,
SetValue: rc.set,
CreateKind: yaml.ScalarNode, // replicas is a ScalarNode
CreateTag: yaml.IntTag, // yaml.NodeTagInt
CreateTag: yaml.NodeTagInt,
})
return node, err
}

View File

@@ -16,8 +16,8 @@ require (
k8s.io/apimachinery v0.17.0
k8s.io/client-go v0.17.0
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
sigs.k8s.io/kustomize/kyaml v0.4.2
sigs.k8s.io/kustomize/kyaml v0.5.0
sigs.k8s.io/yaml v1.2.0
)
replace sigs.k8s.io/kustomize/kyaml v0.4.2 => ../kyaml
replace sigs.k8s.io/kustomize/kyaml v0.5.0 => ../kyaml

View File

@@ -466,9 +466,8 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
@@ -558,8 +557,8 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC7ya0fuo7cSJ3UCKYmM=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVBZUmHSvLbMjRj6NUUKo=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=

View File

@@ -8,6 +8,8 @@ import (
"encoding/json"
"fmt"
"sort"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// SortArrayAndComputeHash sorts a string array and
@@ -50,3 +52,105 @@ func Encode(hex string) (string, error) {
func Hash(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
// calculate hash for different kinds
encoded := ""
switch kind {
case "ConfigMap":
encoded, err = encodeConfigMap(node)
case "Secret":
encoded, err = encodeSecret(node)
default:
var encodedBytes []byte
encodedBytes, err = json.Marshal(node.YNode())
encoded = string(encodedBytes)
}
if err != nil {
return "", err
}
return Encode(Hash(encoded))
}
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))
if err != nil {
return map[string]interface{}{}, err
}
if vn == nil {
values[p] = ""
continue
}
if vn.YNode().Kind != yaml.ScalarNode {
vs, err := vn.MarshalJSON()
if err != nil {
return map[string]interface{}{}, err
}
// data, binaryData and stringData are all maps
var v map[string]interface{}
json.Unmarshal(vs, &v)
values[p] = v
} else {
values[p] = vn.YNode().Value
}
}
return values, nil
}
// encodeConfigMap encodes a ConfigMap.
// Data, Kind, and Name are taken into account.
// BinaryData is included if it's not empty to avoid useless key in output.
func encodeConfigMap(node *yaml.RNode) (string, error) {
// get fields
paths := []string{"metadata/name", "data", "binaryData"}
values, err := getNodeValues(node, paths)
if err != nil {
return "", err
}
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"]
}
// json.Marshal sorts the keys in a stable order in the encoding
data, err := json.Marshal(m)
if err != nil {
return "", err
}
return string(data), nil
}
// encodeSecret encodes a Secret.
// Data, Kind, Name, and Type are taken into account.
// StringData is included if it's not empty to avoid useless key in output.
func encodeSecret(node *yaml.RNode) (string, error) {
// get fields
paths := []string{"type", "metadata/name", "data", "stringData"}
values, err := getNodeValues(node, paths)
if err != nil {
return "", err
}
m := map[string]interface{}{"kind": "Secret", "type": values["type"],
"name": values["metadata/name"], "data": values["data"]}
if _, ok := values["stringData"].(map[string]interface{}); ok {
m["stringData"] = values["stringData"]
}
// json.Marshal sorts the keys in a stable order in the encoding
data, err := json.Marshal(m)
if err != nil {
return "", err
}
return string(data), nil
}

View File

@@ -1,12 +1,13 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package hasher_test
package hasher
import (
"strings"
"testing"
. "sigs.k8s.io/kustomize/api/hasher"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func TestSortArrayAndComputeHash(t *testing.T) {
@@ -39,3 +40,314 @@ func TestHash(t *testing.T) {
t.Errorf("expected hash %q but got %q", expect, sum)
}
}
func TestConfigMapHash(t *testing.T) {
cases := []struct {
desc string
cmYaml string
hash string
err string
}{
// empty map
{"empty data", `
apiVersion: v1
kind: ConfigMap`, "6ct58987ht", ""},
// one key
{"one key", `
apiVersion: v1
kind: ConfigMap
data:
one: ""`, "9g67k2htb6", ""},
// three keys (tests sorting order)
{"three keys", `
apiVersion: v1
kind: ConfigMap
data:
two: 2
one: ""
three: 3`, "7757f9kkct", ""},
// empty binary data map
{"empty binary data", `
apiVersion: v1
kind: ConfigMap`, "6ct58987ht", ""},
// one key with binary data
{"one key with binary data", `
apiVersion: v1
kind: ConfigMap
binaryData:
one: ""`, "6mtk2m274t", ""},
// three keys with binary data (tests sorting order)
{"three keys with binary data", `
apiVersion: v1
kind: ConfigMap
binaryData:
two: 2
one: ""
three: 3`, "9th7kc28dg", ""},
// two keys, one with string and another with binary data
{"two keys with one each", `
apiVersion: v1
kind: ConfigMap
data:
one: ""
binaryData:
two: ""`, "698h7c7t9m", ""},
}
for _, c := range cases {
node, err := yaml.Parse(c.cmYaml)
if err != nil {
t.Fatal(err)
}
h, err := HashRNode(node)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if c.hash != h {
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
}
}
}
func TestSecretHash(t *testing.T) {
cases := []struct {
desc string
secretYaml string
hash string
err string
}{
// empty map
{"empty data", `
apiVersion: v1
kind: Secret
type: my-type`, "5gmgkf8578", ""},
// one key
{"one key", `
apiVersion: v1
kind: Secret
type: my-type
data:
one: ""`, "74bd68bm66", ""},
// three keys (tests sorting order)
{"three keys", `
apiVersion: v1
kind: Secret
type: my-type
data:
two: 2
one: ""
three: 3`, "4gf75c7476", ""},
// with stringdata
{"stringdata", `
apiVersion: v1
kind: Secret
type: my-type
data:
one: ""
stringData:
two: 2`, "c4h4264gdb", ""},
// empty stringdata
{"empty stringdata", `
apiVersion: v1
kind: Secret
type: my-type
data:
one: ""`, "74bd68bm66", ""},
}
for _, c := range cases {
node, err := yaml.Parse(c.secretYaml)
if err != nil {
t.Fatal(err)
}
h, err := HashRNode(node)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if c.hash != h {
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
}
}
}
func TestUnstructuredHash(t *testing.T) {
cases := []struct {
desc string
unstructured string
hash string
err string
}{
{"minimal", `
apiVersion: test/v1
kind: TestResource
metadata:
name: my-resource`, "244782mkb7", ""},
{"with spec", `
apiVersion: test/v1
kind: TestResource
metadata:
name: my-resource
spec:
foo: 1
bar: abc`, "59m2mdccg4", ""},
}
for _, c := range cases {
node, err := yaml.Parse(c.unstructured)
if err != nil {
t.Fatal(err)
}
h, err := HashRNode(node)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if c.hash != h {
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
}
}
}
func TestEncodeConfigMap(t *testing.T) {
cases := []struct {
desc string
cmYaml string
expect string
err string
}{
// empty map
{"empty data", `
apiVersion: v1
kind: ConfigMap`, `{"data":"","kind":"ConfigMap","name":""}`, ""},
// one key
{"one key", `
apiVersion: v1
kind: ConfigMap
data:
one: ""`, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
// three keys (tests sorting order)
{"three keys", `
apiVersion: v1
kind: ConfigMap
data:
two: 2
one: ""
three: 3`, `{"data":{"one":"","three":3,"two":2},"kind":"ConfigMap","name":""}`, ""},
// empty binary map
{"empty data", `
apiVersion: v1
kind: ConfigMap`, `{"data":"","kind":"ConfigMap","name":""}`, ""},
// one key with binary data
{"one key", `
apiVersion: v1
kind: ConfigMap
binaryData:
one: ""`, `{"binaryData":{"one":""},"data":"","kind":"ConfigMap","name":""}`, ""},
// three keys with binary data (tests sorting order)
{"three keys", `
apiVersion: v1
kind: ConfigMap
binaryData:
two: 2
one: ""
three: 3`, `{"binaryData":{"one":"","three":3,"two":2},"data":"","kind":"ConfigMap","name":""}`, ""},
// two keys, one string and one binary values
{"two keys with one each", `
apiVersion: v1
kind: ConfigMap
data:
one: ""
binaryData:
two: ""`, `{"binaryData":{"two":""},"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
}
for _, c := range cases {
node, err := yaml.Parse(c.cmYaml)
if err != nil {
t.Fatal(err)
}
s, err := encodeConfigMap(node)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if s != c.expect {
t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.cmYaml)
}
}
}
func TestEncodeSecret(t *testing.T) {
cases := []struct {
desc string
secretYaml string
expect string
err string
}{
// empty map
{"empty data", `
apiVersion: v1
kind: Secret
type: my-type`, `{"data":"","kind":"Secret","name":"","type":"my-type"}`, ""},
// one key
{"one key", `
apiVersion: v1
kind: Secret
type: my-type
data:
one: ""`, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""},
// three keys (tests sorting order) - note json.Marshal base64 encodes the values because they come in as []byte
{"three keys", `
apiVersion: v1
kind: Secret
type: my-type
data:
two: 2
one: ""
three: 3`, `{"data":{"one":"","three":3,"two":2},"kind":"Secret","name":"","type":"my-type"}`, ""},
// with stringdata
{"stringdata", `
apiVersion: v1
kind: Secret
type: my-type
data:
one: ""
stringData:
two: 2`, `{"data":{"one":""},"kind":"Secret","name":"","stringData":{"two":2},"type":"my-type"}`, ""},
// empty stringdata
{"empty stringdata", `
apiVersion: v1
kind: Secret
type: my-type
data:
one: ""`, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""},
}
for _, c := range cases {
node, err := yaml.Parse(c.secretYaml)
if err != nil {
t.Fatal(err)
}
s, err := encodeSecret(node)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if s != c.expect {
t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.secretYaml)
}
}
}
// 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 {
t.Errorf("case %q, expect nil error but got %q", desc, err.Error())
} else if !strings.Contains(err.Error(), contains) {
t.Errorf("case %q, expect error to contain %q but got %q", desc, contains, err.Error())
}
return true
} else if len(contains) > 0 {
t.Errorf("case %q, expect error to contain %q but got nil error", desc, contains)
return true
}
return false
}

View File

@@ -4,14 +4,12 @@
package accumulator
import (
"fmt"
"log"
"sigs.k8s.io/kustomize/api/filters/nameref"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
"sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/transform"
"sigs.k8s.io/kustomize/kyaml/filtersutil"
)
type nameReferenceTransformer struct {
@@ -86,16 +84,12 @@ func (o *nameReferenceTransformer) Transform(m resmap.ResMap) error {
if candidates == nil {
candidates = m.SubsetThatCouldBeReferencedByResource(referrer)
}
err := transform.MutateField(
referrer.Map(),
fSpec.PathSlice(),
fSpec.CreateIfNotPresent,
o.getNewNameFunc(
// referrer could be an HPA instance,
// target could be Gvk for Deployment,
// candidate a list of resources "reachable"
// from the HPA.
referrer, target.Gvk, candidates))
err := filtersutil.ApplyToJSON(nameref.Filter{
FieldSpec: fSpec,
Referrer: referrer,
Target: target.Gvk,
ReferralCandidates: candidates,
}, referrer)
if err != nil {
return err
}
@@ -105,165 +99,3 @@ func (o *nameReferenceTransformer) Transform(m resmap.ResMap) error {
}
return nil
}
// selectReferral picks the referral among a subset of candidates.
// It returns the current name and namespace of the selected candidate.
// Note that the content of the referricalCandidateSubset slice is most of the time
// identical to the referralCandidates resmap. Still in some cases, such
// as ClusterRoleBinding, the subset only contains the resources of a specific
// namespace.
func (o *nameReferenceTransformer) selectReferral(
oldName string,
referrer *resource.Resource,
target resid.Gvk,
referralCandidates resmap.ResMap,
referralCandidateSubset []*resource.Resource) (interface{}, interface{}, error) {
for _, res := range referralCandidateSubset {
id := res.OrgId()
if id.IsSelected(&target) && res.GetOriginalName() == oldName {
matches := referralCandidates.GetMatchingResourcesByOriginalId(id.Equals)
// If there's more than one match, there's no way
// to know which one to pick, so emit error.
if len(matches) > 1 {
return nil, nil, fmt.Errorf(
"multiple matches for %s:\n %v",
id, getIds(matches))
}
// In the resource, note that it is referenced
// by the referrer.
res.AppendRefBy(referrer.CurId())
// Return transformed name of the object,
// complete with prefixes, hashes, etc.
return res.GetName(), res.GetNamespace(), nil
}
}
return oldName, nil, nil
}
// utility function to replace a simple string by the new name
func (o *nameReferenceTransformer) getSimpleNameField(
oldName string,
referrer *resource.Resource,
target resid.Gvk,
referralCandidates resmap.ResMap,
referralCandidateSubset []*resource.Resource) (interface{}, error) {
newName, _, err := o.selectReferral(oldName, referrer, target,
referralCandidates, referralCandidateSubset)
return newName, err
}
// utility function to replace name field within a map[string]interface{}
// and leverage the namespace field.
func (o *nameReferenceTransformer) getNameAndNsStruct(
inMap map[string]interface{},
referrer *resource.Resource,
target resid.Gvk,
referralCandidates resmap.ResMap) (interface{}, error) {
// Example:
if _, ok := inMap["name"]; !ok {
return nil, fmt.Errorf(
"%#v is expected to contain a name field", inMap)
}
oldName, ok := inMap["name"].(string)
if !ok {
return nil, fmt.Errorf(
"%#v is expected to contain a name field of type string", oldName)
}
subset := referralCandidates.Resources()
if namespacevalue, ok := inMap["namespace"]; ok {
namespace := namespacevalue.(string)
bynamespace := referralCandidates.GroupedByOriginalNamespace()
if _, ok := bynamespace[namespace]; !ok {
return inMap, nil
}
subset = bynamespace[namespace]
}
newname, newnamespace, err := o.selectReferral(oldName, referrer, target,
referralCandidates, subset)
if err != nil {
return nil, err
}
if (newname == oldName) && (newnamespace == nil) {
// no candidate found.
return inMap, nil
}
inMap["name"] = newname
if newnamespace != "" {
// We don't want value "" to replace value "default" since
// the empty string is handled as a wild card here not default namespace
// by kubernetes.
inMap["namespace"] = newnamespace
}
return inMap, nil
}
func (o *nameReferenceTransformer) getNewNameFunc(
referrer *resource.Resource,
target resid.Gvk,
referralCandidates resmap.ResMap) func(in interface{}) (interface{}, error) {
return func(in interface{}) (interface{}, error) {
switch thing := in.(type) {
case string:
return o.getSimpleNameField(thing, referrer, target,
referralCandidates, referralCandidates.Resources())
case map[string]interface{}:
// Kind: ValidatingWebhookConfiguration
// FieldSpec is webhooks/clientConfig/service
return o.getNameAndNsStruct(thing, referrer, target,
referralCandidates)
case []interface{}:
for idx, item := range thing {
switch value := item.(type) {
case string:
// Kind: Role/ClusterRole
// FieldSpec is rules.resourceNames
newName, err := o.getSimpleNameField(value, referrer, target,
referralCandidates, referralCandidates.Resources())
if err != nil {
return nil, err
}
thing[idx] = newName
case map[string]interface{}:
// Kind: RoleBinding/ClusterRoleBinding
// FieldSpec is subjects
// Note: The corresponding fieldSpec had been changed from
// from path: subjects/name to just path: subjects. This is
// what get mutatefield to request the mapping of the whole
// map containing namespace and name instead of just a simple
// string field containing the name
newMap, err := o.getNameAndNsStruct(value, referrer, target,
referralCandidates)
if err != nil {
return nil, err
}
thing[idx] = newMap
default:
return nil, fmt.Errorf(
"%#v is expected to be either a []string or a []map[string]interface{}", in)
}
}
return in, nil
default:
return nil, fmt.Errorf(
"%#v is expected to be either a string or a []interface{}", in)
}
}
}
func getIds(rs []*resource.Resource) []string {
var result []string
for _, r := range rs {
result = append(result, r.CurId().String()+"\n")
}
return result
}

View File

@@ -520,7 +520,7 @@ func TestNameReferenceUnhappyRun(t *testing.T) {
},
},
}).ResMap(),
expectedErr: "is expected to contain a name field"},
expectedErr: "cannot find field 'name' in node"},
}
nrt := newNameReferenceTransformer(builtinconfig.MakeDefaultConfig().NameReference)

View File

@@ -16,8 +16,10 @@ github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmU
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
@@ -72,6 +74,7 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
@@ -91,11 +94,13 @@ github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
@@ -110,6 +115,7 @@ github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsd
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/spec v0.19.5 h1:Xm0Ao53uqnk9QE/LlYV5DEU09UAgpliA85QoT9LzqPw=
github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
@@ -120,6 +126,7 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
@@ -242,6 +249,7 @@ github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -353,6 +361,7 @@ github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOV
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI=
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yujunz/go-getter v1.4.1-lite h1:FhvNc94AXMZkfqUwfMKhnQEC9phkphSGdPTL7tIdhOM=
@@ -404,8 +413,6 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -494,7 +501,8 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVBZUmHSvLbMjRj6NUUKo=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
@@ -515,7 +523,8 @@ k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
sigs.k8s.io/kustomize/kyaml v0.4.1/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw=
sigs.k8s.io/kustomize/kyaml v0.5.0 h1:xufpSxgpugQxtd0aN1ZsWnr3Kj0fpAi7GN4dnEs4oPg=
sigs.k8s.io/kustomize/kyaml v0.5.0/go.mod h1:bEzbO5pN9OvlEeCLvFHo8Pu7SA26Herc2m60UeWZBdI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@@ -201,7 +201,7 @@ metadata:
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "foo-literalConfigMap-bar-8d2dkb8k24",
"name": "foo-literalConfigMap-bar-g5f6t456f5",
"namespace": "ns1",
"labels": map[string]interface{}{
"app": "nginx",
@@ -220,7 +220,7 @@ metadata:
"apiVersion": "v1",
"kind": "Secret",
"metadata": map[string]interface{}{
"name": "foo-secret-bar-9btc7bt4kb",
"name": "foo-secret-bar-82c2g5f8f6",
"namespace": "ns1",
"labels": map[string]interface{}{
"app": "nginx",

View File

@@ -4,12 +4,9 @@
package kunstruct
import (
"encoding/json"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/kustomize/api/hasher"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/kyaml/filtersutil"
)
// kustHash computes a hash of an unstructured object.
@@ -22,119 +19,9 @@ func NewKustHash() *kustHash {
// Hash returns a hash of the given object
func (h *kustHash) Hash(m ifc.Kunstructured) (string, error) {
u := unstructured.Unstructured{
Object: m.Map(),
}
kind := u.GetKind()
switch kind {
case "ConfigMap":
cm, err := unstructuredToConfigmap(u)
if err != nil {
return "", err
}
return configMapHash(cm)
case "Secret":
sec, err := unstructuredToSecret(u)
if err != nil {
return "", err
}
return secretHash(sec)
default:
return unstructuredHash(&u)
}
}
// configMapHash returns a hash of the ConfigMap.
// The Data, Kind, and Name are taken into account.
func configMapHash(cm *corev1.ConfigMap) (string, error) {
encoded, err := encodeConfigMap(cm)
node, err := filtersutil.GetRNode(m)
if err != nil {
return "", err
}
h, err := hasher.Encode(hasher.Hash(encoded))
if err != nil {
return "", err
}
return h, nil
}
// SecretHash returns a hash of the Secret.
// The Data, Kind, Name, and Type are taken into account.
func secretHash(sec *corev1.Secret) (string, error) {
encoded, err := encodeSecret(sec)
if err != nil {
return "", err
}
h, err := hasher.Encode(hasher.Hash(encoded))
if err != nil {
return "", err
}
return h, nil
}
// unstructuredHash creates a hash for an arbitrary type.
// All fields of the object are taken into account when generating the hash.
// This is a fallback for when a specalised hash for the type is unavailable.
func unstructuredHash(u *unstructured.Unstructured) (string, error) {
encoded, err := json.Marshal(u.Object)
if err != nil {
return "", err
}
h, err := hasher.Encode(hasher.Hash(string(encoded)))
if err != nil {
return "", err
}
return h, nil
}
// encodeConfigMap encodes a ConfigMap.
// Data, Kind, and Name are taken into account.
// BinaryData is included if it's not empty to avoid useless key in output.
func encodeConfigMap(cm *corev1.ConfigMap) (string, error) {
// json.Marshal sorts the keys in a stable order in the encoding
m := map[string]interface{}{"kind": "ConfigMap", "name": cm.Name, "data": cm.Data}
if len(cm.BinaryData) > 0 {
m["binaryData"] = cm.BinaryData
}
data, err := json.Marshal(m)
if err != nil {
return "", err
}
return string(data), nil
}
// encodeSecret encodes a Secret.
// Data, Kind, Name, and Type are taken into account.
// StringData is included if it's not empty to avoid useless key in output.
func encodeSecret(sec *corev1.Secret) (string, error) {
// json.Marshal sorts the keys in a stable order in the encoding
m := map[string]interface{}{"kind": "Secret", "type": sec.Type, "name": sec.Name, "data": sec.Data}
if len(sec.StringData) > 0 {
m["stringData"] = sec.StringData
}
data, err := json.Marshal(m)
if err != nil {
return "", err
}
return string(data), nil
}
func unstructuredToConfigmap(u unstructured.Unstructured) (*corev1.ConfigMap, error) {
marshaled, err := json.Marshal(u.Object)
if err != nil {
return nil, err
}
var out corev1.ConfigMap
err = json.Unmarshal(marshaled, &out)
return &out, err
}
func unstructuredToSecret(u unstructured.Unstructured) (*corev1.Secret, error) {
marshaled, err := json.Marshal(u.Object)
if err != nil {
return nil, err
}
var out corev1.Secret
err = json.Unmarshal(marshaled, &out)
return &out, err
return hasher.HashRNode(node)
}

View File

@@ -1,224 +1,34 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package kunstruct
import (
"reflect"
"strings"
"testing"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func TestConfigMapHash(t *testing.T) {
cases := []struct {
desc string
cm *corev1.ConfigMap
hash string
err string
}{
// empty map
{"empty data", &corev1.ConfigMap{Data: map[string]string{}, BinaryData: map[string][]byte{}}, "42745tchd9", ""},
// one key
{"one key", &corev1.ConfigMap{Data: map[string]string{"one": ""}}, "9g67k2htb6", ""},
// three keys (tests sorting order)
{"three keys", &corev1.ConfigMap{Data: map[string]string{"two": "2", "one": "", "three": "3"}}, "f5h7t85m9b", ""},
// empty binary data map
{"empty binary data", &corev1.ConfigMap{BinaryData: map[string][]byte{}}, "dk855m5d49", ""},
// one key with binary data
{"one key with binary data", &corev1.ConfigMap{BinaryData: map[string][]byte{"one": []byte("")}}, "mk79584b8c", ""},
// three keys with binary data (tests sorting order)
{"three keys with binary data", &corev1.ConfigMap{BinaryData: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, "t458mc6db2", ""},
// two keys, one with string and another with binary data
{"two keys with one each", &corev1.ConfigMap{Data: map[string]string{"one": ""}, BinaryData: map[string][]byte{"two": []byte("")}}, "698h7c7t9m", ""},
}
for _, c := range cases {
h, err := configMapHash(c.cm)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if c.hash != h {
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
}
}
}
func TestSecretHash(t *testing.T) {
cases := []struct {
desc string
secret *corev1.Secret
hash string
err string
}{
// empty map
{"empty data", &corev1.Secret{Type: "my-type", Data: map[string][]byte{}}, "t75bgf6ctb", ""},
// one key
{"one key", &corev1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}}, "74bd68bm66", ""},
// three keys (tests sorting order)
{"three keys", &corev1.Secret{Type: "my-type", Data: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, "dgcb6h9tmk", ""},
// with stringdata
{"stringdata", &corev1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}, StringData: map[string]string{"two": "2"}}, "ckm7f798g2", ""},
// empty stringdata
{"empty stringdata", &corev1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}, StringData: map[string]string{}}, "74bd68bm66", ""},
}
for _, c := range cases {
h, err := secretHash(c.secret)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if c.hash != h {
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
}
}
}
func TestUnstructuredHash(t *testing.T) {
cases := []struct {
desc string
unstructured *unstructured.Unstructured
hash string
err string
}{
{"minimal", &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "test/v1",
"kind": "TestResource",
"metadata": map[string]string{"name": "my-resource"}},
}, "2tt46d7f79", ""},
{"with spec", &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "test/v1",
"kind": "TestResource",
"metadata": map[string]string{"name": "my-resource"},
"spec": map[string]interface{}{"foo": 1, "bar": "abc"}},
}, "6gc62g4m6k", ""},
}
for _, c := range cases {
h, err := unstructuredHash(c.unstructured)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if c.hash != h {
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
}
}
}
func TestEncodeConfigMap(t *testing.T) {
cases := []struct {
desc string
cm *corev1.ConfigMap
expect string
err string
}{
// empty map
{"empty data", &corev1.ConfigMap{Data: map[string]string{}}, `{"data":{},"kind":"ConfigMap","name":""}`, ""},
// one key
{"one key", &corev1.ConfigMap{Data: map[string]string{"one": ""}}, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
// three keys (tests sorting order)
{"three keys", &corev1.ConfigMap{Data: map[string]string{"two": "2", "one": "", "three": "3"}},
`{"data":{"one":"","three":"3","two":"2"},"kind":"ConfigMap","name":""}`, ""},
// empty binary map
{"empty data", &corev1.ConfigMap{BinaryData: map[string][]byte{}}, `{"data":null,"kind":"ConfigMap","name":""}`, ""},
// one key with binary data
{"one key", &corev1.ConfigMap{BinaryData: map[string][]byte{"one": []byte("")}},
`{"binaryData":{"one":""},"data":null,"kind":"ConfigMap","name":""}`, ""},
// three keys with binary data (tests sorting order)
{"three keys", &corev1.ConfigMap{BinaryData: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}},
`{"binaryData":{"one":"","three":"Mw==","two":"Mg=="},"data":null,"kind":"ConfigMap","name":""}`, ""},
// two keys, one string and one binary values
{"two keys with one each", &corev1.ConfigMap{Data: map[string]string{"one": ""}, BinaryData: map[string][]byte{"two": []byte("")}},
`{"binaryData":{"two":""},"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
}
for _, c := range cases {
s, err := encodeConfigMap(c.cm)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if s != c.expect {
t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.cm)
}
}
}
func TestEncodeSecret(t *testing.T) {
cases := []struct {
desc string
secret *corev1.Secret
expect string
err string
}{
// empty map
{"empty data", &corev1.Secret{Type: "my-type", Data: map[string][]byte{}}, `{"data":{},"kind":"Secret","name":"","type":"my-type"}`, ""},
// one key
{"one key", &corev1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}}, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""},
// three keys (tests sorting order) - note json.Marshal base64 encodes the values because they come in as []byte
{"three keys", &corev1.Secret{
Type: "my-type",
Data: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")},
},
`{"data":{"one":"","three":"Mw==","two":"Mg=="},"kind":"Secret","name":"","type":"my-type"}`, ""},
// with stringdata
{"stringdata", &corev1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}, StringData: map[string]string{"two": "2"}},
`{"data":{"one":""},"kind":"Secret","name":"","stringData":{"two":"2"},"type":"my-type"}`, ""},
// empty stringdata
{"empty stringdata", &corev1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}, StringData: map[string]string{}},
`{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""},
}
for _, c := range cases {
s, err := encodeSecret(c.secret)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if s != c.expect {
t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.secret)
}
}
}
// warn devs who change types that they might have to update a hash function
// not perfect, as it only checks the number of top-level fields
func TestTypeStability(t *testing.T) {
errfmt := `case %q, expected %d fields but got %d
Depending on the field(s) you added, you may need to modify the hash function for this type.
To guide you: the hash function targets fields that comprise the contents of objects,
not their metadata (e.g. the Data of a ConfigMap, but nothing in ObjectMeta).
func TestHasher(t *testing.T) {
input := `
apiVersion: v1
kind: ConfigMap
metadata:
name: foo
data:
one: ""
binaryData:
two: ""
`
cases := []struct {
typeName string
obj interface{}
expect int
}{
{"ConfigMap", corev1.ConfigMap{}, 4},
{"Secret", corev1.Secret{}, 5},
}
for _, c := range cases {
val := reflect.ValueOf(c.obj)
if num := val.NumField(); c.expect != num {
t.Errorf(errfmt, c.typeName, c.expect, num)
}
}
}
expect := "698h7c7t9m"
// 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 {
factory := NewKunstructuredFactoryImpl()
k, err := factory.SliceFromBytes([]byte(input))
if err != nil {
if len(contains) == 0 {
t.Errorf("case %q, expect nil error but got %q", desc, err.Error())
} else if !strings.Contains(err.Error(), contains) {
t.Errorf("case %q, expect error to contain %q but got %q", desc, contains, err.Error())
}
return true
} else if len(contains) > 0 {
t.Errorf("case %q, expect error to contain %q but got nil error", desc, contains)
return true
t.Fatal(err)
}
hasher := NewKustHash()
result, err := hasher.Hash(k[0])
if err != nil {
t.Fatal(err)
}
if result != expect {
t.Fatalf("expect %s but got %s", expect, result)
}
return false
}

View File

@@ -231,7 +231,7 @@ spec:
valueFrom:
configMapKeyRef:
key: somekey
name: test-infra-app-env-ffmd9b969m
name: test-infra-app-env-8h5mh7f7ch
image: nginx:1.8.0
name: nginx
ports:
@@ -240,7 +240,7 @@ spec:
- configMapRef:
name: someConfigMap
- configMapRef:
name: test-infra-app-env-ffmd9b969m
name: test-infra-app-env-8h5mh7f7ch
image: busybox
name: busybox
volumeMounts:
@@ -248,7 +248,7 @@ spec:
name: app-env
volumes:
- configMap:
name: test-infra-app-env-ffmd9b969m
name: test-infra-app-env-8h5mh7f7ch
name: app-env
---
apiVersion: v1
@@ -288,7 +288,7 @@ metadata:
app: mungebot
org: kubernetes
repo: test-infra
name: test-infra-app-env-ffmd9b969m
name: test-infra-app-env-8h5mh7f7ch
---
apiVersion: v1
data:
@@ -301,6 +301,6 @@ metadata:
app: mungebot
org: kubernetes
repo: test-infra
name: test-infra-app-config-f462h769f9
name: test-infra-app-config-49d6f5h7b5
`)
}

View File

@@ -132,7 +132,7 @@ kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: comp-my-configmap-ct5bgtbccd
name: comp-my-configmap-kc6k2kmkh9
---
apiVersion: v1
kind: Deployment
@@ -186,7 +186,7 @@ kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: comp-my-configmap-dgf97tmg6h
name: comp-my-configmap-55249mf5kb
---
apiVersion: v1
kind: Deployment
@@ -241,7 +241,7 @@ kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: comp-my-configmap-dgf97tmg6h
name: comp-my-configmap-55249mf5kb
---
apiVersion: v1
kind: Deployment
@@ -283,7 +283,7 @@ data:
testValue: "1"
kind: ConfigMap
metadata:
name: my-configmap-7k9t4h74f8
name: my-configmap-2g9c94mhb8
---
apiVersion: v1
kind: Deployment
@@ -301,7 +301,7 @@ kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: comp-my-configmap-ct5bgtbccd
name: comp-my-configmap-kc6k2kmkh9
---
apiVersion: v1
kind: Deployment
@@ -349,7 +349,7 @@ kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: my-configmap-96dt22k28h
name: my-configmap-kc6k2kmkh9
`,
},
"missing-optional-component-api-version": {
@@ -380,7 +380,7 @@ kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: my-configmap-72cfg2mg5d
name: my-configmap-5g7gh5mgt5
---
apiVersion: v1
kind: Deployment
@@ -427,7 +427,7 @@ data:
testValue: "1"
kind: ConfigMap
metadata:
name: my-configmap-a-b-tfb7c5t69m
name: my-configmap-a-b-2g9c94mhb8
---
apiVersion: v1
kind: Deployment
@@ -442,7 +442,7 @@ data:
testValue: "1"
kind: ConfigMap
metadata:
name: my-configmap-b-8h7b8862bb
name: my-configmap-b-2g9c94mhb8
`,
},

View File

@@ -81,7 +81,7 @@ data:
vegetable: broccoli
kind: ConfigMap
metadata:
name: blah-bob-k772g5db55
name: blah-bob-d87t8m8tgm
---
apiVersion: v1
data:
@@ -89,7 +89,7 @@ data:
v2: '[{"path": "var/druid/segment-cache"}]'
kind: ConfigMap
metadata:
name: blah-json-9gtcc2fgb4
name: blah-json-5298bc8g99
---
apiVersion: v1
data:
@@ -101,7 +101,7 @@ data:
vegetable: YnJvY2NvbGk=
kind: Secret
metadata:
name: blah-bob-gmc2824f4b
name: blah-bob-ftht6hfgmb
type: Opaque
`)
}
@@ -155,7 +155,7 @@ data:
vegetable: broccoli
kind: ConfigMap
metadata:
name: blah-bob-gfkcbk5ckf
name: blah-bob-db529cg5bk
`)
}
@@ -217,7 +217,7 @@ kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: p1-com1-dhbbm922gd
name: p1-com1-8tc62428t2
---
apiVersion: v1
data:
@@ -226,7 +226,7 @@ kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: p2-com2-c4b8md75k9
name: p2-com2-87mcggf7d7
`)
}
@@ -274,7 +274,7 @@ kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: o1-cm-28g596k77k
name: o1-cm-ft9mmdc8c6
---
apiVersion: v1
data:
@@ -284,6 +284,6 @@ kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: cm-o2-gfcc59fg5m
name: cm-o2-5k95kd76ft
`)
}

View File

@@ -83,7 +83,7 @@ metadata:
if secret == nil {
t.Errorf("Expected to find a Secret")
}
if secret.GetName() != "foo-secret-bar-9btc7bt4kb" {
if secret.GetName() != "foo-secret-bar-82c2g5f8f6" {
t.Errorf("unexpected secret resource name: %s", secret.GetName())
}
@@ -140,7 +140,7 @@ secretGenerator:
if secret == nil {
t.Errorf("Expected to find a Secret")
}
if secret.GetName() != "yeshash-mcgcmdcm69" {
if secret.GetName() != "yeshash-82c2g5f8f6" {
t.Errorf("unexpected secret resource name: %s", secret.GetName())
}
}

View File

@@ -156,7 +156,8 @@ spec:
- mountPath: /tmp/ps
name: busybox-persistent-storage
volumes:
- name: busybox-persistent-storage
- emptyDir: {}
name: busybox-persistent-storage
- configMap:
name: configmap-in-base
name: configmap-in-base
@@ -232,7 +233,8 @@ spec:
- mountPath: /tmp/ps
name: nginx-persistent-storage
volumes:
- name: nginx-persistent-storage
- emptyDir: {}
name: nginx-persistent-storage
- configMap:
name: configmap-in-base
name: configmap-in-base
@@ -258,7 +260,8 @@ spec:
- mountPath: /tmp/ps
name: busybox-persistent-storage
volumes:
- name: busybox-persistent-storage
- emptyDir: {}
name: busybox-persistent-storage
- configMap:
name: configmap-in-base
name: configmap-in-base
@@ -332,7 +335,8 @@ spec:
- mountPath: /tmp/ps
name: nginx-persistent-storage
volumes:
- name: nginx-persistent-storage
- emptyDir: {}
name: nginx-persistent-storage
- configMap:
name: configmap-in-base
name: configmap-in-base
@@ -459,7 +463,8 @@ spec:
- mountPath: /tmp/ps
name: busybox-persistent-storage
volumes:
- name: busybox-persistent-storage
- emptyDir: {}
name: busybox-persistent-storage
- configMap:
name: configmap-in-base
name: configmap-in-base
@@ -559,7 +564,8 @@ spec:
- mountPath: /tmp/ps
name: busybox-persistent-storage
volumes:
- name: busybox-persistent-storage
- emptyDir: {}
name: busybox-persistent-storage
- configMap:
name: configmap-in-base
name: configmap-in-base
@@ -661,7 +667,8 @@ spec:
- mountPath: /tmp/ps
name: busybox-persistent-storage
volumes:
- name: busybox-persistent-storage
- emptyDir: {}
name: busybox-persistent-storage
- configMap:
name: configmap-in-base
name: configmap-in-base
@@ -762,7 +769,8 @@ spec:
- mountPath: /tmp/ps
name: busybox-persistent-storage
volumes:
- name: busybox-persistent-storage
- emptyDir: {}
name: busybox-persistent-storage
- configMap:
name: configmap-in-base
name: configmap-in-base
@@ -957,7 +965,8 @@ spec:
- mountPath: /tmp/ps
name: busybox-persistent-storage
volumes:
- name: busybox-persistent-storage
- emptyDir: {}
name: busybox-persistent-storage
- configMap:
name: configmap-in-base
name: configmap-in-base
@@ -1171,7 +1180,8 @@ spec:
- mountPath: /tmp/ps
name: busybox-persistent-storage
volumes:
- name: busybox-persistent-storage
- emptyDir: {}
name: busybox-persistent-storage
- configMap:
name: configmap-in-base
name: configmap-in-base

View File

@@ -251,7 +251,7 @@ spec:
- emptyDir: {}
name: nginx-persistent-storage
- configMap:
name: team-foo-configmap-in-base-bbdmdh7m8t
name: team-foo-configmap-in-base-798k5k7g9f
name: configmap-in-base
---
apiVersion: v1
@@ -283,7 +283,7 @@ metadata:
app: mynginx
org: example.com
team: foo
name: team-foo-configmap-in-base-bbdmdh7m8t
name: team-foo-configmap-in-base-798k5k7g9f
---
apiVersion: v1
data:
@@ -297,7 +297,7 @@ metadata:
app: mynginx
org: example.com
team: foo
name: team-foo-secret-in-base-tkm7hhtf8d
name: team-foo-secret-in-base-bgd6bkgdm2
type: Opaque
`)
}
@@ -386,10 +386,10 @@ spec:
pdName: nginx-persistent-storage
name: nginx-persistent-storage
- configMap:
name: staging-team-foo-configmap-in-base-gh9d7t85gb
name: staging-team-foo-configmap-in-base-hc6g9dk6g9
name: configmap-in-base
- configMap:
name: staging-configmap-in-overlay-k7cbc75tg8
name: staging-configmap-in-overlay-dc6fm46dhm
name: configmap-in-overlay
---
apiVersion: v1
@@ -424,7 +424,7 @@ metadata:
env: staging
org: example.com
team: override-foo
name: staging-team-foo-configmap-in-base-gh9d7t85gb
name: staging-team-foo-configmap-in-base-hc6g9dk6g9
---
apiVersion: v1
data:
@@ -440,7 +440,7 @@ metadata:
env: staging
org: example.com
team: override-foo
name: staging-team-foo-secret-in-base-c8db7gk2m2
name: staging-team-foo-secret-in-base-k2k4692t9g
type: Opaque
---
apiVersion: v1
@@ -451,7 +451,7 @@ metadata:
labels:
env: staging
team: override-foo
name: staging-configmap-in-overlay-k7cbc75tg8
name: staging-configmap-in-overlay-dc6fm46dhm
`)
}
@@ -486,7 +486,7 @@ data:
key: value
kind: ConfigMap
metadata:
name: test-t5t4md8fdm
name: test-t757gk2bmf
namespace: default
---
apiVersion: v1
@@ -494,7 +494,7 @@ data:
key: value
kind: ConfigMap
metadata:
name: test-t5t4md8fdm
name: test-t757gk2bmf
namespace: kube-system
---
apiVersion: v1
@@ -503,7 +503,7 @@ data:
username: YWRtaW4=
kind: Secret
metadata:
name: test-h65t9hg6kc
name: test-bgd6bkgdm2
namespace: default
type: Opaque
---
@@ -513,7 +513,7 @@ data:
username: YWRtaW4=
kind: Secret
metadata:
name: test-h65t9hg6kc
name: test-bgd6bkgdm2
namespace: kube-system
type: Opaque
`)

View File

@@ -40,7 +40,7 @@ data:
passphrase: ZGF0IHBocmFzZQ==
kind: Secret
metadata:
name: bob-kf5c9fccbt
name: bob-bh645k7tmg
type: Opaque
`)
}
@@ -91,6 +91,6 @@ kind: ConfigMap
metadata:
labels:
fruit: apple
name: shouldHaveHash-2k9hc848ff
name: shouldHaveHash-c9867f8446
`)
}

View File

@@ -79,7 +79,8 @@ spec:
- mountPath: /tmp/ps
name: nginx-persistent-storage
volumes:
- name: nginx-persistent-storage
- emptyDir: {}
name: nginx-persistent-storage
- configMap:
name: configmap-in-base
name: configmap-in-base
@@ -222,7 +223,8 @@ spec:
- mountPath: /tmp/ps
name: nginx-persistent-storage
volumes:
- name: nginx-persistent-storage
- emptyDir: {}
name: nginx-persistent-storage
- configMap:
name: configmap-in-base
name: configmap-in-base

View File

@@ -147,10 +147,10 @@ spec:
pdName: nginx-persistent-storage
name: nginx-persistent-storage
- configMap:
name: a-b-configmap-in-base-fm96mhk4dt
name: a-b-configmap-in-base-798k5k7g9f
name: configmap-in-base
- configMap:
name: a-configmap-in-overlay-ffm9hf78mc
name: a-configmap-in-overlay-dc6fm46dhm
name: configmap-in-overlay
---
apiVersion: v1
@@ -175,7 +175,7 @@ metadata:
labels:
env: staging
team: foo
name: a-b-configmap-in-base-fm96mhk4dt
name: a-b-configmap-in-base-798k5k7g9f
---
apiVersion: v1
data:
@@ -184,7 +184,7 @@ kind: ConfigMap
metadata:
labels:
env: staging
name: a-configmap-in-overlay-ffm9hf78mc
name: a-configmap-in-overlay-dc6fm46dhm
`)
}
@@ -352,10 +352,10 @@ spec:
pdName: nginx-persistent-storage
name: nginx-persistent-storage
- configMap:
name: staging-team-foo-configmap-in-base-g7k6gt2889
name: staging-team-foo-configmap-in-base-798k5k7g9f
name: configmap-in-base
- configMap:
name: staging-configmap-in-overlay-k7cbc75tg8
name: staging-configmap-in-overlay-dc6fm46dhm
name: configmap-in-overlay
---
apiVersion: v1
@@ -390,7 +390,7 @@ metadata:
env: staging
org: example.com
team: foo
name: staging-team-foo-configmap-in-base-g7k6gt2889
name: staging-team-foo-configmap-in-base-798k5k7g9f
---
apiVersion: v1
data:
@@ -399,7 +399,7 @@ kind: ConfigMap
metadata:
labels:
env: staging
name: staging-configmap-in-overlay-k7cbc75tg8
name: staging-configmap-in-overlay-dc6fm46dhm
`)
}
@@ -540,9 +540,10 @@ spec:
- mountPath: /tmp/ps
name: nginx-persistent-storage
volumes:
- name: nginx-persistent-storage
- emptyDir: {}
name: nginx-persistent-storage
- configMap:
name: staging-team-foo-configmap-in-base-g7k6gt2889
name: staging-team-foo-configmap-in-base-798k5k7g9f
name: configmap-in-base
---
apiVersion: v1
@@ -577,7 +578,7 @@ metadata:
env: staging
org: example.com
team: foo
name: staging-team-foo-configmap-in-base-g7k6gt2889
name: staging-team-foo-configmap-in-base-798k5k7g9f
---
apiVersion: v1
data:
@@ -586,7 +587,7 @@ kind: ConfigMap
metadata:
labels:
env: staging
name: staging-configmap-in-overlay-k7cbc75tg8
name: staging-configmap-in-overlay-dc6fm46dhm
`)
})
}

View File

@@ -0,0 +1,44 @@
package krusty_test
import (
"testing"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
)
func TestEmptyFieldSpecValue(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("/app", `
generators:
- generators.yaml
configurations:
- kustomizeconfig.yaml
`)
th.WriteF("/app/generators.yaml", `
apiVersion: builtin
kind: ConfigMapGenerator
metadata:
name: secret-example
labels:
app.kubernetes.io/name: secret-example
literals:
- this_is_a_secret_name=
`)
th.WriteF("/app/kustomizeconfig.yaml", `
nameReference:
- kind: Secret
version: v1
fieldSpecs:
- path: data/this_is_a_secret_name
kind: ConfigMap
`)
m := th.Run("/app", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: v1
data:
this_is_a_secret_name: ""
kind: ConfigMap
metadata:
name: secret-example-7hf4fh868h
`)
}

View File

@@ -42,7 +42,7 @@ data:
enableRisky: "false"
kind: ConfigMap
metadata:
name: the-non-default-namespace-map-b6h49k7mt8
name: the-non-default-namespace-map-64b2md8tth
namespace: non-default
---
apiVersion: v1
@@ -51,14 +51,14 @@ data:
enableRisky: "false"
kind: ConfigMap
metadata:
name: the-map-4959m5tm6c
name: the-map-tg7t5hk8bk
---
apiVersion: v1
data:
password.txt: dmVyeVNlY3JldA==
kind: Secret
metadata:
name: the-non-default-namespace-secret-h8d9hkgtb9
name: the-non-default-namespace-secret-8tc9gdd76t
namespace: non-default
type: Opaque
---
@@ -67,7 +67,7 @@ data:
password.txt: YW5vdGhlclNlY3JldA==
kind: Secret
metadata:
name: the-secret-fgb45h45bh
name: the-secret-6557m7fcg8
type: Opaque
`)
}
@@ -104,7 +104,7 @@ kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: testCase-4g75kbk6gm
name: testCase-bcbmmg48hd
namespace: overlay
`)
}

View File

@@ -96,7 +96,7 @@ data:
FOO: foo
kind: ConfigMap
metadata:
name: test-k4bkhftttd
name: test-6bc28fff49
`)
}

View File

@@ -895,7 +895,7 @@ spec:
- command:
- echo
- dev-base-cockroachdb
- dev-base-test-config-map-b2g2dmd64b
- dev-base-test-config-map-6b85g79g7g
env:
- name: CDB_PUBLIC_SVC
value: dev-base-cockroachdb-public
@@ -921,7 +921,7 @@ data:
foo: bar
kind: ConfigMap
metadata:
name: dev-base-test-config-map-b2g2dmd64b
name: dev-base-test-config-map-6b85g79g7g
`)
}

View File

@@ -95,6 +95,8 @@ var orderFirst = []string{
"Service",
"LimitRange",
"PriorityClass",
"PersistentVolume",
"PersistentVolumeClaim",
"Deployment",
"StatefulSet",
"CronJob",

View File

@@ -40,6 +40,15 @@ func (rmF *Factory) FromResource(res *resource.Resource) ResMap {
return m
}
// FromResourceSlice returns a ResMap with a slice of resources.
func (rmF *Factory) FromResourceSlice(ress []*resource.Resource) ResMap {
m, err := newResMapFromResourceSlice(ress)
if err != nil {
panic(err)
}
return m
}
// FromFile returns a ResMap given a resource path.
func (rmF *Factory) FromFile(
loader ifc.Loader, path string) (ResMap, error) {

View File

@@ -146,6 +146,22 @@ func (rf *Factory) SliceFromBytes(in []byte) ([]*Resource, error) {
return result, nil
}
// SliceFromBytesWithNames unmarshals bytes into a Resource slice with specified original
// name.
func (rf *Factory) SliceFromBytesWithNames(names []string, in []byte) ([]*Resource, error) {
result, err := rf.SliceFromBytes(in)
if err != nil {
return nil, err
}
if len(names) != len(result) {
return nil, fmt.Errorf("number of names doesn't match number of resources")
}
for i, res := range result {
res.originalName = names[i]
}
return result, nil
}
// 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)

View File

@@ -15,7 +15,5 @@ require (
k8s.io/client-go v0.17.3
k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd
sigs.k8s.io/cli-utils v0.17.0
sigs.k8s.io/kustomize/kyaml v0.4.2
sigs.k8s.io/kustomize/kyaml v0.5.0
)
replace sigs.k8s.io/kustomize/kyaml => ../../kyaml

View File

@@ -452,6 +452,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -623,8 +624,8 @@ sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZw
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/kustomize/kyaml v0.4.0/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw=
sigs.k8s.io/kustomize/kyaml v0.4.2 h1:9/Tb90gnThv4vgUldZOLnrT+9Esdh7+Og2UIq024Ykg=
sigs.k8s.io/kustomize/kyaml v0.4.2/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw=
sigs.k8s.io/kustomize/kyaml v0.5.0 h1:xufpSxgpugQxtd0aN1ZsWnr3Kj0fpAi7GN4dnEs4oPg=
sigs.k8s.io/kustomize/kyaml v0.5.0/go.mod h1:bEzbO5pN9OvlEeCLvFHo8Pu7SA26Herc2m60UeWZBdI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=

View File

@@ -100,12 +100,16 @@ func (r *CreateSetterRunner) preRunE(c *cobra.Command, args []string) error {
if setterVersion == "" {
if len(args) == 2 && r.Set.SetPartialField.Type == "array" && c.Flag("field").Changed {
setterVersion = "v2"
} else if len(args) < 2 || !c.Flag("value").Changed && len(args) < 3 {
setterVersion = "v1"
} else if err := initSetterVersion(c, args); err != nil {
return err
}
}
if r.Set.SetPartialField.Type != "array" && !c.Flag("value").Changed && len(args) < 3 {
return errors.Errorf("setter name and value must be provided, " +
"value can either be an argument or can be passed as a flag --value")
}
if setterVersion == "v2" {
var err error
r.OpenAPIFile, err = ext.GetOpenAPIFile(args)
@@ -130,6 +134,19 @@ func (r *CreateSetterRunner) preRunE(c *cobra.Command, args []string) error {
"substitution and setter can't have same name", r.CreateSetter.Name)
}
// check if setter with same name exists and throw error
ref, err = spec.NewRef(fieldmeta.DefinitionsPrefix + fieldmeta.SetterDefinitionPrefix + r.CreateSetter.Name)
if err != nil {
return err
}
setter, _ := openapi.Resolve(&ref)
// if setter already exists with the input setter name, throw error
if setter != nil {
return errors.Errorf("setter with name %s already exists, "+
"if you want to modify it, please delete the existing setter and recreate it", r.CreateSetter.Name)
}
r.CreateSetter.Description = r.Set.SetPartialField.Description
r.CreateSetter.SetBy = r.Set.SetPartialField.SetBy
r.CreateSetter.Type = r.Set.SetPartialField.Type

View File

@@ -125,6 +125,24 @@ openAPI:
err: "substitution with name my-image already exists, substitution and setter can't have same name",
},
{
name: "error if setter with same name exists",
args: []string{
"my-image", "ubuntu"},
inputOpenAPI: `
apiVersion: v1alpha1
kind: Example
openAPI:
definitions:
io.k8s.cli.setters.my-image:
x-k8s-cli:
setter:
name: my-image
value: "nginx"
`,
err: "setter with name my-image already exists, if you want to modify it, please delete the existing setter and recreate it",
},
{
name: "add replicas with schema",
args: []string{"replicas", "3", "--description", "hello world", "--set-by", "me"},
@@ -166,6 +184,36 @@ spec:
`,
},
{
name: "add replicas not enough arguments",
args: []string{"replicas", "--description", "hello world", "--set-by", "me"},
err: `setter name and value must be provided, value can either be an argument or can be passed as a flag --value`,
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
`,
inputOpenAPI: `
apiVersion: v1alpha1
kind: Example
`,
expectedOpenAPI: `
apiVersion: v1alpha1
kind: Example
`,
expectedResources: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
`,
},
{
name: "list values with schema",
args: []string{"list", "--description", "hello world", "--set-by", "me", "--type", "array", "--field", "spec.list"},

View File

@@ -5,8 +5,6 @@ package commands
import (
"fmt"
"regexp"
"strings"
"github.com/go-openapi/spec"
"github.com/spf13/cobra"
@@ -14,7 +12,6 @@ import (
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/fieldmeta"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/setters2"
"sigs.k8s.io/kustomize/kyaml/setters2/settersutil"
)
@@ -71,8 +68,20 @@ func (r *CreateSubstitutionRunner) preRunE(c *cobra.Command, args []string) erro
return err
}
// check if substitution with same name exists and throw error
ref, err := spec.NewRef(fieldmeta.DefinitionsPrefix + fieldmeta.SubstitutionDefinitionPrefix + r.CreateSubstitution.Name)
if err != nil {
return err
}
subst, _ := openapi.Resolve(&ref)
// if substitution already exists with the input substitution name, throw error
if subst != nil {
return errors.Errorf("substitution with name %s already exists", r.CreateSubstitution.Name)
}
// check if setter with same name exists and throw error
ref, err := spec.NewRef(fieldmeta.DefinitionsPrefix + fieldmeta.SetterDefinitionPrefix + r.CreateSubstitution.Name)
ref, err = spec.NewRef(fieldmeta.DefinitionsPrefix + fieldmeta.SetterDefinitionPrefix + r.CreateSubstitution.Name)
if err != nil {
return err
}
@@ -84,40 +93,5 @@ func (r *CreateSubstitutionRunner) preRunE(c *cobra.Command, args []string) erro
"substitution and setter can't have same name", r.CreateSubstitution.Name))
}
// extract setter name tokens from pattern enclosed in ${}
re := regexp.MustCompile(`\$\{([^}]*)\}`)
markers := re.FindAllString(r.CreateSubstitution.Pattern, -1)
if len(markers) == 0 {
return errors.Errorf("unable to find setter or substitution names in pattern, " +
"setter names must be enclosed in ${}")
}
for _, marker := range markers {
name := strings.TrimSuffix(strings.TrimPrefix(marker, "${"), "}")
if name == r.CreateSubstitution.Name {
return fmt.Errorf("setters must have different name than the substitution: %s", name)
}
ref, err := spec.NewRef(fieldmeta.DefinitionsPrefix + fieldmeta.SubstitutionDefinitionPrefix + name)
if err != nil {
return err
}
var markerRef string
subst, _ := openapi.Resolve(&ref)
// check if the substitution exists with the marker name or fall back to creating setter
// ref with the name
if subst != nil {
markerRef = fieldmeta.DefinitionsPrefix + fieldmeta.SubstitutionDefinitionPrefix + name
} else {
markerRef = fieldmeta.DefinitionsPrefix + fieldmeta.SetterDefinitionPrefix + name
}
r.CreateSubstitution.Values = append(
r.CreateSubstitution.Values,
setters2.Value{Marker: marker, Ref: markerRef},
)
}
return nil
}

View File

@@ -104,6 +104,27 @@ spec:
image: sidecar:1.7.9
`,
},
{
name: "error if substitution with same name exists",
args: []string{"my-image", "--field-value", "some:image", "--pattern", "some:${image}"},
inputOpenAPI: `
apiVersion: v1alpha1
kind: Example
openAPI:
definitions:
io.k8s.cli.substitutions.my-image:
x-k8s-cli:
substitution:
name: my-image
pattern: something/${my-image-setter}::${my-tag-setter}/nginxotherthing
values:
- marker: ${my-image-setter}
ref: '#/definitions/io.k8s.cli.setters.my-image-setter'
- marker: ${my-tag-setter}
ref: '#/definitions/io.k8s.cli.setters.my-tag-setter'
`,
err: "substitution with name my-image already exists",
},
{
name: "error if setter with same name exists",
args: []string{
@@ -289,125 +310,6 @@ spec:
image: nginx::1.7.9 # {"$openapi":"my-image-subst"}
`,
},
{
name: "nested cyclic substitution",
args: []string{"my-nested-subst", "--field-value", "something/nginx::1.7.9/nginxotherthing",
"--pattern", "something/${my-image-subst}/${my-other-setter}"},
inputOpenAPI: `
apiVersion: v1alpha1
kind: Example
openAPI:
definitions:
io.k8s.cli.setters.my-image-setter:
x-k8s-cli:
setter:
name: my-image-setter
value: nginx
io.k8s.cli.setters.my-tag-setter:
x-k8s-cli:
setter:
name: my-tag-setter
value: 1.7.9
io.k8s.cli.substitutions.my-image-subst:
x-k8s-cli:
substitution:
name: my-image-subst
pattern: ${my-nested-subst}::${my-tag-setter}
values:
- marker: ${my-nested-subst}
ref: '#/definitions/io.k8s.cli.substitutions.my-nested-subst'
- marker: ${my-tag-setter}
ref: '#/definitions/io.k8s.cli.setters.my-tag-setter'
io.k8s.cli.setters.my-other-setter:
x-k8s-cli:
setter:
name: my-other-setter
value: nginxotherthing
io.k8s.cli.substitutions.my-nested-subst:
x-k8s-cli:
substitution:
name: my-nested-subst
pattern: something/${my-image-subst}/${my-other-setter}
values:
- marker: ${my-image-subst}
ref: '#/definitions/io.k8s.cli.substitutions.my-image-subst'
- marker: ${my-other-setter}
ref: '#/definitions/io.k8s.cli.setters.my-other-setter'
`,
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
spec:
containers:
- name: nginx
image: something/nginx::1.7.9/nginxotherthing # {"$openapi":"my-nested-subst"}
- name: sidecar
image: nginx::1.7.9 # {"$openapi":"my-image-subst"}
`,
expectedOpenAPI: `
apiVersion: v1alpha1
kind: Example
openAPI:
definitions:
io.k8s.cli.setters.my-image-setter:
x-k8s-cli:
setter:
name: my-image-setter
value: nginx
io.k8s.cli.setters.my-tag-setter:
x-k8s-cli:
setter:
name: my-tag-setter
value: 1.7.9
io.k8s.cli.substitutions.my-image-subst:
x-k8s-cli:
substitution:
name: my-image-subst
pattern: ${my-nested-subst}::${my-tag-setter}
values:
- marker: ${my-nested-subst}
ref: '#/definitions/io.k8s.cli.substitutions.my-nested-subst'
- marker: ${my-tag-setter}
ref: '#/definitions/io.k8s.cli.setters.my-tag-setter'
io.k8s.cli.setters.my-other-setter:
x-k8s-cli:
setter:
name: my-other-setter
value: nginxotherthing
io.k8s.cli.substitutions.my-nested-subst:
x-k8s-cli:
substitution:
name: my-nested-subst
pattern: something/${my-image-subst}/${my-other-setter}
values:
- marker: ${my-image-subst}
ref: '#/definitions/io.k8s.cli.substitutions.my-image-subst'
- marker: ${my-other-setter}
ref: '#/definitions/io.k8s.cli.setters.my-other-setter'
`,
expectedResources: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
spec:
containers:
- name: nginx
image: something/nginx::1.7.9/nginxotherthing # {"$openapi":"my-nested-subst"}
- name: sidecar
image: nginx::1.7.9 # {"$openapi":"my-image-subst"}
`,
err: "cyclic substitution detected with name my-nested-subst",
},
{
name: "substitution with non-existing setter with same name",
args: []string{
@@ -484,7 +386,7 @@ spec:
if !assert.NotNil(t, err) {
t.FailNow()
}
assert.Equal(t, err.Error(), test.err)
assert.Equal(t, test.err, err.Error())
return
}
if !assert.NoError(t, err) {

View File

@@ -159,11 +159,11 @@ The configMap name is suffixed by _-v1_, per the
The suffix to the configMap name is generated from a
hash of the maps content - in this case the name suffix
is _k25m8k5k5m_:
is _5276h4th55_:
<!-- @grepStagingHash @testAgainstLatestRelease -->
<!-- @grepStagingHash -->
```
kustomize build $OVERLAYS/staging | grep k25m8k5k5m
kustomize build $OVERLAYS/staging | grep 5276h4th55
```
Now modify the map patch, to change the greeting
@@ -190,20 +190,20 @@ kustomize build $OVERLAYS/staging |\
```
Confirm that the change in configMap content resulted
in three new names ending in _cd7kdh48fd_ - one in the
in three new names ending in _c2g8fcbf88_ - one in the
configMap name itself, and two in the deployment that
uses the map:
<!-- @countHashes @testAgainstLatestRelease -->
<!-- @countHashes -->
```
test 3 == \
$(kustomize build $OVERLAYS/staging | grep cd7kdh48fd | wc -l); \
$(kustomize build $OVERLAYS/staging | grep c2g8fcbf88 | wc -l); \
echo $?
```
Applying these resources to the cluster will result in
a rolling update of the deployments pods, retargetting
them from the _k25m8k5k5m_ maps to the _cd7kdh48fd_
them from the _5276h4th55_ maps to the _c2g8fcbf88_
maps. The system will later garbage collect the
unused maps.

View File

@@ -139,11 +139,11 @@ kustomize build $OVERLAYS/staging |\
根据 `$OVERLAYS/staging/kustomization.yaml` 中的 `nameSuffix` 字段configMap 名称以 _-v1_ 为后缀。
configMap 名称的后缀是由 map 内容的哈希生成的 - 在这种情况下,名称后缀是 _k25m8k5k5m_
configMap 名称的后缀是由 map 内容的哈希生成的 - 在这种情况下,名称后缀是 _5276h4th55_
<!-- @grepStagingHash @testAgainstLatestRelease -->
<!-- @grepStagingHash -->
```
kustomize build $OVERLAYS/staging | grep k25m8k5k5m
kustomize build $OVERLAYS/staging | grep 5276h4th55
```
现在修改 map patch ,更改该服务将使用的问候消息:
@@ -168,16 +168,16 @@ kustomize build $OVERLAYS/staging |\
grep -B 8 -A 1 staging-the-map
```
确认 configMap 内容的更改将会生成以 _cd7kdh48fd_ 结尾的三个新名称 - 一个在 configMap 的名称中,另两个在使用 ConfigMap 的 deployment 中:
确认 configMap 内容的更改将会生成以 _c2g8fcbf88_ 结尾的三个新名称 - 一个在 configMap 的名称中,另两个在使用 ConfigMap 的 deployment 中:
<!-- @countHashes @testAgainstLatestRelease -->
<!-- @countHashes -->
```
test 3 == \
$(kustomize build $OVERLAYS/staging | grep cd7kdh48fd | wc -l); \
$(kustomize build $OVERLAYS/staging | grep c2g8fcbf88 | wc -l); \
echo $?
```
将这些资源应用于群集将导致 deployment pod 的滚动更新,将它们从 _k25m8k5k5m_ map 重新定位到 _cd7kdh48fd_ map 。系统稍后将垃圾收集未使用的 map。
将这些资源应用于群集将导致 deployment pod 的滚动更新,将它们从 _5276h4th55_ map 重新定位到 _c2g8fcbf88_ map 。系统稍后将垃圾收集未使用的 map。
## 回滚

View File

@@ -5,6 +5,6 @@ go 1.14
require (
k8s.io/apimachinery v0.18.3
sigs.k8s.io/application v0.8.2
sigs.k8s.io/kustomize/kyaml v0.4.2
sigs.k8s.io/kustomize/kyaml v0.5.0
sigs.k8s.io/yaml v1.2.0
)

View File

@@ -406,9 +406,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
@@ -511,8 +510,8 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC7ya0fuo7cSJ3UCKYmM=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVBZUmHSvLbMjRj6NUUKo=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -559,8 +558,8 @@ sigs.k8s.io/application v0.8.2 h1:XB7C33f7eW+1MbCJXoZa0+nP+R+S/VbAvYfCd3ufP1I=
sigs.k8s.io/application v0.8.2/go.mod h1:Mv+ht9RE/QNtITYCzRbt3XTIN6t6so6cInmiyg6wOIg=
sigs.k8s.io/controller-runtime v0.4.0 h1:wATM6/m+3w8lj8FXNaO6Fs/rq/vqoOjO1Q116Z9NPsg=
sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns=
sigs.k8s.io/kustomize/kyaml v0.4.1 h1:NEqA/35upoAjb+I5vh1ODUqxoX4DOrezeQa9BhhG5Co=
sigs.k8s.io/kustomize/kyaml v0.4.1/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw=
sigs.k8s.io/kustomize/kyaml v0.5.0 h1:xufpSxgpugQxtd0aN1ZsWnr3Kj0fpAi7GN4dnEs4oPg=
sigs.k8s.io/kustomize/kyaml v0.5.0/go.mod h1:bEzbO5pN9OvlEeCLvFHo8Pu7SA26Herc2m60UeWZBdI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=

View File

@@ -2,4 +2,4 @@ module sigs.k8s.io/kustomize/functions/examples/injection-tshirt-sizes
go 1.14
require sigs.k8s.io/kustomize/kyaml v0.4.2
require sigs.k8s.io/kustomize/kyaml v0.5.0

View File

@@ -169,6 +169,8 @@ github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHN
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -212,9 +214,8 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -258,8 +259,8 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC7ya0fuo7cSJ3UCKYmM=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVBZUmHSvLbMjRj6NUUKo=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/kustomize/kyaml v0.4.1 h1:NEqA/35upoAjb+I5vh1ODUqxoX4DOrezeQa9BhhG5Co=
sigs.k8s.io/kustomize/kyaml v0.4.1/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw=
sigs.k8s.io/kustomize/kyaml v0.5.0 h1:xufpSxgpugQxtd0aN1ZsWnr3Kj0fpAi7GN4dnEs4oPg=
sigs.k8s.io/kustomize/kyaml v0.5.0/go.mod h1:bEzbO5pN9OvlEeCLvFHo8Pu7SA26Herc2m60UeWZBdI=

View File

@@ -2,4 +2,4 @@ module sigs.k8s.io/kustomize/functions/examples/template-go-nginx
go 1.14
require sigs.k8s.io/kustomize/kyaml v0.4.2
require sigs.k8s.io/kustomize/kyaml v0.5.0

View File

@@ -170,6 +170,8 @@ github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHN
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -213,9 +215,8 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -259,8 +260,8 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC7ya0fuo7cSJ3UCKYmM=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVBZUmHSvLbMjRj6NUUKo=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/kustomize/kyaml v0.4.1 h1:NEqA/35upoAjb+I5vh1ODUqxoX4DOrezeQa9BhhG5Co=
sigs.k8s.io/kustomize/kyaml v0.4.1/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw=
sigs.k8s.io/kustomize/kyaml v0.5.0 h1:xufpSxgpugQxtd0aN1ZsWnr3Kj0fpAi7GN4dnEs4oPg=
sigs.k8s.io/kustomize/kyaml v0.5.0/go.mod h1:bEzbO5pN9OvlEeCLvFHo8Pu7SA26Herc2m60UeWZBdI=

View File

@@ -4,5 +4,5 @@ go 1.14
require (
github.com/instrumenta/kubeval v0.0.0-20190918223246-8d013ec9fc56
sigs.k8s.io/kustomize/kyaml v0.4.2
sigs.k8s.io/kustomize/kyaml v0.5.0
)

View File

@@ -189,6 +189,8 @@ github.com/spf13/pflag v0.0.0-20180821114517-d929dcbb1086 h1:iU+nPfqRqK8ShQqnpZL
github.com/spf13/pflag v0.0.0-20180821114517-d929dcbb1086/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.1.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -239,9 +241,8 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -289,10 +290,10 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC7ya0fuo7cSJ3UCKYmM=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVBZUmHSvLbMjRj6NUUKo=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/kustomize/kyaml v0.4.1 h1:NEqA/35upoAjb+I5vh1ODUqxoX4DOrezeQa9BhhG5Co=
sigs.k8s.io/kustomize/kyaml v0.4.1/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw=
sigs.k8s.io/kustomize/kyaml v0.5.0 h1:xufpSxgpugQxtd0aN1ZsWnr3Kj0fpAi7GN4dnEs4oPg=
sigs.k8s.io/kustomize/kyaml v0.5.0/go.mod h1:bEzbO5pN9OvlEeCLvFHo8Pu7SA26Herc2m60UeWZBdI=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@@ -2,4 +2,4 @@ module sigs.k8s.io/kustomize/functions/examples/validator-resource-requests
go 1.14
require sigs.k8s.io/kustomize/kyaml v0.4.2
require sigs.k8s.io/kustomize/kyaml v0.5.0

View File

@@ -164,6 +164,7 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -207,9 +208,8 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -253,8 +253,8 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC7ya0fuo7cSJ3UCKYmM=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVBZUmHSvLbMjRj6NUUKo=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/kustomize/kyaml v0.4.1 h1:NEqA/35upoAjb+I5vh1ODUqxoX4DOrezeQa9BhhG5Co=
sigs.k8s.io/kustomize/kyaml v0.4.1/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw=
sigs.k8s.io/kustomize/kyaml v0.5.0 h1:xufpSxgpugQxtd0aN1ZsWnr3Kj0fpAi7GN4dnEs4oPg=
sigs.k8s.io/kustomize/kyaml v0.5.0/go.mod h1:bEzbO5pN9OvlEeCLvFHo8Pu7SA26Herc2m60UeWZBdI=

View File

@@ -8,13 +8,17 @@ require (
github.com/spf13/pflag v1.0.5
k8s.io/client-go v0.17.3
sigs.k8s.io/kustomize/api v0.5.1
sigs.k8s.io/kustomize/cmd/config v0.2.0
sigs.k8s.io/kustomize/cmd/config v0.5.0
sigs.k8s.io/yaml v1.2.0
)
replace sigs.k8s.io/kustomize/api v0.5.1 => ../api
replace (
sigs.k8s.io/kustomize/api v0.5.1 => ../api
sigs.k8s.io/kustomize/cmd/config v0.5.0 => ../cmd/config
)
exclude (
github.com/russross/blackfriday v2.0.0+incompatible
sigs.k8s.io/kustomize/api v0.2.0
sigs.k8s.io/kustomize/cmd/config v0.2.0
)

View File

@@ -670,7 +670,6 @@ gopkg.in/yaml.v2 v2.0.0/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
@@ -744,18 +743,15 @@ modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
sigs.k8s.io/cli-utils v0.12.0 h1:+CvYwQAEtKvcx/NaUVF9rDKvY91VwJj+i7D2lWBMYc0=
sigs.k8s.io/cli-utils v0.12.0/go.mod h1:H35YA5iJIM7EVNgqDTjX2dgt4wE23zmnXOTSTlyD+PE=
sigs.k8s.io/cli-utils v0.17.0 h1:iQ0nhgU8DZiRphHTErI1IHcHYp2fZaULrrFN4NF3dCc=
sigs.k8s.io/cli-utils v0.17.0/go.mod h1:9Jqm9K2W6ShhCxsEuaz6HSRKKOXigPUx3ZfypGgxBLY=
sigs.k8s.io/controller-runtime v0.4.0 h1:wATM6/m+3w8lj8FXNaO6Fs/rq/vqoOjO1Q116Z9NPsg=
sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns=
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/kustomize/cmd/config v0.2.0 h1:VNAWKb1JLl7dFjMAD5MwdZpGjreN3qL63C8DKtsUYck=
sigs.k8s.io/kustomize/cmd/config v0.2.0/go.mod h1:oXzY7QJS6JlmWgusEjra2O3cW7GSIICXa59/3DvjBfE=
sigs.k8s.io/kustomize/kyaml v0.1.4/go.mod h1:461i94nj0h0ylJ6w83jLkR4SqqVhn1iY6fjD0JSTQeE=
sigs.k8s.io/kustomize/kyaml v0.2.0/go.mod h1:72/rLkSi+L/pHM1oCjwrf3ClU+tH5kZQvvdLSqIHwWU=
sigs.k8s.io/kustomize/kyaml v0.4.1 h1:NEqA/35upoAjb+I5vh1ODUqxoX4DOrezeQa9BhhG5Co=
sigs.k8s.io/kustomize/kyaml v0.4.1/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw=
sigs.k8s.io/kustomize/kyaml v0.4.0/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw=
sigs.k8s.io/kustomize/kyaml v0.5.0 h1:xufpSxgpugQxtd0aN1ZsWnr3Kj0fpAi7GN4dnEs4oPg=
sigs.k8s.io/kustomize/kyaml v0.5.0/go.mod h1:bEzbO5pN9OvlEeCLvFHo8Pu7SA26Herc2m60UeWZBdI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=

View File

@@ -47,7 +47,7 @@ The argument can be a URL resolving to a directory
with a kustomization.yaml file, e.g.
kustomize build \
github.com/kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6
github.com/kubernetes-sigs/kustomize/examples/multibases/dev/?ref=v1.0.6
The URL should be formulated as described at
https://github.com/hashicorp/go-getter#url-format

View File

@@ -0,0 +1,74 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package fixsetters
import (
"encoding/json"
"fmt"
"strings"
"github.com/go-openapi/spec"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/fieldmeta"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// FieldMeta contains metadata that may be attached to fields as comments
type FieldMetaV1 struct {
Schema spec.Schema
Extensions XKustomize
}
type XKustomize struct {
SetBy string `yaml:"setBy,omitempty" json:"setBy,omitempty"`
PartialFieldSetters []PartialFieldSetter `yaml:"partialSetters,omitempty" json:"partialSetters,omitempty"`
FieldSetter *PartialFieldSetter `yaml:"setter,omitempty" json:"setter,omitempty"`
}
// PartialFieldSetter defines how to set part of a field rather than the full field
// value. e.g. the tag part of an image field
type PartialFieldSetter struct {
// Name is the name of this setter.
Name string `yaml:"name" json:"name"`
// Value is the current value that has been set.
Value string `yaml:"value" json:"value"`
}
// UpgradeV1SetterComment reads the FieldMeta from a node and upgrade the
// setters comment to latest
func (fm *FieldMetaV1) UpgradeV1SetterComment(n *yaml.RNode) error {
// check for metadata on head and line comments
comments := []string{n.YNode().LineComment, n.YNode().HeadComment}
for _, c := range comments {
if c == "" {
continue
}
c := strings.TrimLeft(c, "#")
if err := fm.Schema.UnmarshalJSON([]byte(c)); err != nil {
// note: don't return an error if the comment isn't a fieldmeta struct
return nil
}
fe := fm.Schema.VendorExtensible.Extensions["x-kustomize"]
if fe == nil {
return nil
}
b, err := json.Marshal(fe)
if err != nil {
return errors.Wrap(err)
}
// delete line comment after parsing info into fieldmeta
n.YNode().HeadComment = ""
n.YNode().LineComment = ""
err = json.Unmarshal(b, &fm.Extensions)
if fm.Extensions.FieldSetter != nil {
n.YNode().LineComment = fmt.Sprintf(`{"%s":"%s"}`, fieldmeta.ShortHandRef(), fm.Extensions.FieldSetter.Name)
}
return err
}
return nil
}

View File

@@ -0,0 +1,141 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package fixsetters
import (
"os"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/setters2"
"sigs.k8s.io/kustomize/kyaml/setters2/settersutil"
)
// SetterFixer fixes setters in the input package
type SetterFixer struct {
// PkgPath is path to the resource package
PkgPath string
// OpenAPIPath is path to the openAPI file in the package
OpenAPIPath string
// DryRun only displays the actions without performing
DryRun bool
}
// SetterFixerV1Result holds the results of V1 setters fix
type SetterFixerV1Result struct {
// NeedFix indicates if the resource in pkgPath are on V1 version of setters
// and need to be fixed
NeedFix bool
// CreatedSetters are setters created as part of this fix
CreatedSetters []string
// CreatedSubst are substitutions created as part of this fix
CreatedSubst []string
// FailedSetters are setters failed to create from current V1 setters
FailedSetters map[string]error
// FailedSubst are substitutions failed to be created from V1 partial setters
FailedSubst map[string]error
}
// FixSettersV1 reads the package and upgrades v1 version of setters
// to latest
func (f *SetterFixer) FixV1Setters() (SetterFixerV1Result, error) {
sfr := SetterFixerV1Result{
FailedSetters: make(map[string]error),
FailedSubst: make(map[string]error),
}
// KrmFile need not exist for dryRun
if !f.DryRun {
_, err := os.Stat(f.OpenAPIPath)
if err != nil {
return sfr, err
}
}
var err error
// lookup for all setters and partial setters in v1 format
// delete the v1 format comments after lookup
l := UpgradeV1Setters{}
if f.DryRun {
err = applyReadFilter(&l, f.PkgPath)
} else {
err = applyWriteFilter(&l, f.PkgPath)
}
if err != nil {
return sfr, err
}
if len(l.SetterCounts) > 0 {
sfr.NeedFix = true
} else {
return sfr, nil
}
// for each v1 setter create the equivalent in v2,
for _, setter := range l.SetterCounts {
sd := setters2.SetterDefinition{
Name: setter.Name,
Value: setter.Value,
Description: setter.Description,
SetBy: setter.SetBy,
Type: setter.Type,
}
var err error
if !f.DryRun {
err = sd.AddToFile(f.OpenAPIPath)
}
if err != nil {
sfr.FailedSetters[setter.Name] = err
} else {
sfr.CreatedSetters = append(sfr.CreatedSetters, setter.Name)
}
}
// for each group of partial setters, create equivalent substitution
for _, subst := range l.Substitutions {
sc := settersutil.SubstitutionCreator{
Name: subst.Name,
FieldValue: subst.FieldVale,
Pattern: subst.Pattern,
ResourcesPath: f.PkgPath,
OpenAPIPath: f.OpenAPIPath,
}
var err error
if !f.DryRun {
err = applyWriteFilter(&sc, f.PkgPath)
}
if err != nil {
sfr.FailedSubst[subst.Name] = err
} else {
sfr.CreatedSubst = append(sfr.CreatedSubst, subst.Name)
}
}
return sfr, nil
}
func applyWriteFilter(f kio.Filter, pkgPath string) error {
rw := &kio.LocalPackageReadWriter{
PackagePath: pkgPath,
}
return kio.Pipeline{
Inputs: []kio.Reader{rw},
Filters: []kio.Filter{f},
Outputs: []kio.Writer{rw},
}.Execute()
}
func applyReadFilter(f kio.Filter, pkgPath string) error {
rw := &kio.LocalPackageReader{
PackagePath: pkgPath,
}
return kio.Pipeline{
Inputs: []kio.Reader{rw},
Filters: []kio.Filter{f},
}.Execute()
}

View File

@@ -0,0 +1,466 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package fixsetters
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFixSettersV1(t *testing.T) {
var tests = []struct {
name string
input string
err string
dryRun bool
openAPIFile string
expectedOutput string
expectedOpenAPI string
needFix bool
createdSetters []string
createdSubst []string
failedSetters map[string]error
failedSubst map[string]error
}{
{
name: "upgrade-delete-partial-setters",
input: `
apiVersion: install.istio.io/v1alpha2
kind: IstioControlPlane
metadata:
cluster: "someproj/someclus" # {"type":"string","x-kustomize":{"partialSetters":[{"name":"project","value":"someproj"},{"name":"cluster","value":"someclus"}]}}
spec:
profile: asm # {"type":"string","x-kustomize":{"setter":{"name":"profile","value":"asm"}}}
cluster: "someproj/someclus" # {"type":"string","x-kustomize":{"partialSetters":[{"name":"project","value":"someproj"},{"name":"cluster","value":"someclus"}]}}
`,
openAPIFile: `apiVersion: kustomization.dev/v1alpha1
kind: Kustomization`,
needFix: true,
createdSetters: []string{"cluster", "profile", "project"},
createdSubst: []string{"project-cluster-54235872"},
failedSetters: map[string]error{},
failedSubst: map[string]error{},
expectedOutput: `apiVersion: install.istio.io/v1alpha2
kind: IstioControlPlane
metadata:
cluster: "someproj/someclus" # {"$openapi":"project-cluster-54235872"}
spec:
profile: asm # {"$openapi":"profile"}
cluster: "someproj/someclus" # {"$openapi":"project-cluster-54235872"}
`,
expectedOpenAPI: `apiVersion: kustomization.dev/v1alpha1
kind: Kustomization
openAPI:
definitions:
io.k8s.cli.setters.cluster:
type: string
x-k8s-cli:
setter:
name: cluster
value: someclus
io.k8s.cli.setters.profile:
type: string
x-k8s-cli:
setter:
name: profile
value: asm
io.k8s.cli.setters.project:
type: string
x-k8s-cli:
setter:
name: project
value: someproj
io.k8s.cli.substitutions.project-cluster-54235872:
x-k8s-cli:
substitution:
name: project-cluster-54235872
pattern: ${project}/${cluster}
values:
- marker: ${project}
ref: '#/definitions/io.k8s.cli.setters.project'
- marker: ${cluster}
ref: '#/definitions/io.k8s.cli.setters.cluster'
`,
},
{
name: "upgrade-delete-partial-setters-dryRun",
dryRun: true,
input: `apiVersion: install.istio.io/v1alpha2
kind: IstioControlPlane
metadata:
cluster: "someproj/someclus" # {"type":"string","x-kustomize":{"partialSetters":[{"name":"project","value":"someproj"},{"name":"cluster","value":"someclus"}]}}
spec:
profile: asm # {"type":"string","x-kustomize":{"setter":{"name":"profile","value":"asm"}}}
cluster: "someproj/someclus" # {"type":"string","x-kustomize":{"partialSetters":[{"name":"project","value":"someproj"},{"name":"cluster","value":"someclus"}]}}
`,
needFix: true,
createdSetters: []string{"cluster", "profile", "project"},
createdSubst: []string{"project-cluster-54235872"},
failedSetters: map[string]error{},
failedSubst: map[string]error{},
expectedOutput: `apiVersion: install.istio.io/v1alpha2
kind: IstioControlPlane
metadata:
cluster: "someproj/someclus" # {"type":"string","x-kustomize":{"partialSetters":[{"name":"project","value":"someproj"},{"name":"cluster","value":"someclus"}]}}
spec:
profile: asm # {"type":"string","x-kustomize":{"setter":{"name":"profile","value":"asm"}}}
cluster: "someproj/someclus" # {"type":"string","x-kustomize":{"partialSetters":[{"name":"project","value":"someproj"},{"name":"cluster","value":"someclus"}]}}
`,
},
{
name: "partial-setters-same-value",
input: `
apiVersion: install.istio.io/v1alpha2
kind: IstioControlPlane
spec:
profile: asm # {"type":"string","x-kustomize":{"setter":{"name":"profile","value":"asm"}}}
team: asm # {"type":"string","x-kustomize":{"setter":{"name":"team","value":"asm"}}}
profile-team: asm/asm # {"type":"string","x-kustomize":{"partialSetters":[{"name":"profile","value":"asm"},{"name":"team","value":"asm"}]}}
`,
openAPIFile: `apiVersion: kustomization.dev/v1alpha1
kind: Kustomization`,
needFix: true,
createdSetters: []string{"profile", "team"},
createdSubst: []string{"profile-team-1851878264"},
failedSetters: map[string]error{},
failedSubst: map[string]error{},
expectedOutput: `apiVersion: install.istio.io/v1alpha2
kind: IstioControlPlane
spec:
profile: asm # {"$openapi":"profile"}
team: asm # {"$openapi":"team"}
profile-team: asm/asm # {"$openapi":"profile-team-1851878264"}
`,
expectedOpenAPI: `apiVersion: kustomization.dev/v1alpha1
kind: Kustomization
openAPI:
definitions:
io.k8s.cli.setters.profile:
type: string
x-k8s-cli:
setter:
name: profile
value: asm
io.k8s.cli.setters.team:
type: string
x-k8s-cli:
setter:
name: team
value: asm
io.k8s.cli.substitutions.profile-team-1851878264:
x-k8s-cli:
substitution:
name: profile-team-1851878264
pattern: ${profile}/${team}
values:
- marker: ${profile}
ref: '#/definitions/io.k8s.cli.setters.profile'
- marker: ${team}
ref: '#/definitions/io.k8s.cli.setters.team'
`,
},
{
name: "partial-setters-suffix-subst",
input: `
apiVersion: install.istio.io/v1alpha2
kind: IstioControlPlane
spec:
profile: asm-profile # {"type":"string","x-kustomize":{"partialSetters":[{"name":"asm","value":"asm"}]}}
team: asm-team # {"type":"string","x-kustomize":{"partialSetters":[{"name":"asm","value":"asm"}]}}
`,
openAPIFile: `apiVersion: kustomization.dev/v1alpha1
kind: Kustomization`,
needFix: true,
createdSetters: []string{"asm"},
createdSubst: []string{"asm-3472570278", "asm-3647054792"},
failedSetters: map[string]error{},
failedSubst: map[string]error{},
expectedOutput: `apiVersion: install.istio.io/v1alpha2
kind: IstioControlPlane
spec:
profile: asm-profile # {"$openapi":"asm-3647054792"}
team: asm-team # {"$openapi":"asm-3472570278"}
`,
expectedOpenAPI: `apiVersion: kustomization.dev/v1alpha1
kind: Kustomization
openAPI:
definitions:
io.k8s.cli.setters.asm:
type: string
x-k8s-cli:
setter:
name: asm
value: asm
io.k8s.cli.substitutions.asm-3472570278:
x-k8s-cli:
substitution:
name: asm-3472570278
pattern: ${asm}-team
values:
- marker: ${asm}
ref: '#/definitions/io.k8s.cli.setters.asm'
io.k8s.cli.substitutions.asm-3647054792:
x-k8s-cli:
substitution:
name: asm-3647054792
pattern: ${asm}-profile
values:
- marker: ${asm}
ref: '#/definitions/io.k8s.cli.setters.asm'
`,
},
{
name: "upgrade-with-both-versions",
input: `
apiVersion: install.istio.io/v1alpha2
kind: IstioControlPlane
metadata:
clusterName: "project-id/us-east1-d/cluster-name"
spec:
profile: asm # {"type":"string","x-kustomize":{"setter":{"name":"profilesetter","value":"asm"}}}
hub: gcr.io/asm-testing # {"$openapi":"hubsetter"}
`,
openAPIFile: `apiVersion: kustomization.dev/v1alpha1
kind: Kustomization
openAPI:
definitions:
io.k8s.cli.setters.hubsetter:
type: string
x-k8s-cli:
setter:
name: hubsetter
value: gcr.io/asm-testing`,
needFix: true,
createdSetters: []string{"profilesetter"},
failedSetters: map[string]error{},
failedSubst: map[string]error{},
expectedOutput: `apiVersion: install.istio.io/v1alpha2
kind: IstioControlPlane
metadata:
clusterName: "project-id/us-east1-d/cluster-name"
spec:
profile: asm # {"$openapi":"profilesetter"}
hub: gcr.io/asm-testing # {"$openapi":"hubsetter"}
`,
expectedOpenAPI: `apiVersion: kustomization.dev/v1alpha1
kind: Kustomization
openAPI:
definitions:
io.k8s.cli.setters.hubsetter:
type: string
x-k8s-cli:
setter:
name: hubsetter
value: gcr.io/asm-testing
io.k8s.cli.setters.profilesetter:
type: string
x-k8s-cli:
setter:
name: profilesetter
value: asm
`,
},
{
name: "setter-already-exists",
input: `
apiVersion: install.istio.io/v1alpha2
kind: IstioControlPlane
metadata:
clusterName: "project-id/us-east1-d/cluster-name"
spec:
profile: asm # {"type":"string","x-kustomize":{"setter":{"name":"profilesetter","value":"asm"}}}
hub: asm # {"$openapi":"profilesetter"}
`,
openAPIFile: `apiVersion: kustomization.dev/v1alpha1
kind: Kustomization
openAPI:
definitions:
io.k8s.cli.setters.profilesetter:
type: string
x-k8s-cli:
setter:
name: profilesetter
value: asm
`,
needFix: true,
createdSetters: []string{"profilesetter"},
failedSetters: map[string]error{},
failedSubst: map[string]error{},
expectedOutput: `apiVersion: install.istio.io/v1alpha2
kind: IstioControlPlane
metadata:
clusterName: "project-id/us-east1-d/cluster-name"
spec:
profile: asm # {"$openapi":"profilesetter"}
hub: asm # {"$openapi":"profilesetter"}
`,
expectedOpenAPI: `apiVersion: kustomization.dev/v1alpha1
kind: Kustomization
openAPI:
definitions:
io.k8s.cli.setters.profilesetter:
type: string
x-k8s-cli:
setter:
name: profilesetter
value: asm
`,
},
{
name: "do-not-delete-latest setters",
input: `
apiVersion: install.istio.io/v1alpha2
kind: IstioControlPlane
metadata:
clusterName: "project-id/us-east1-d/cluster-name"
spec:
profile: asm # {"$openapi":"profilesetter"}
hub: gcr.io/asm-testing
`,
failedSetters: map[string]error{},
failedSubst: map[string]error{},
openAPIFile: `apiVersion: kustomization.dev/v1alpha1
kind: Kustomization
openAPI:
definitions:
io.k8s.cli.setters.profilesetter:
type: string
x-k8s-cli:
setter:
name: profilesetter
value: asm
`,
expectedOutput: `apiVersion: install.istio.io/v1alpha2
kind: IstioControlPlane
metadata:
clusterName: "project-id/us-east1-d/cluster-name"
spec:
profile: asm # {"$openapi":"profilesetter"}
hub: gcr.io/asm-testing
`,
expectedOpenAPI: `apiVersion: kustomization.dev/v1alpha1
kind: Kustomization
openAPI:
definitions:
io.k8s.cli.setters.profilesetter:
type: string
x-k8s-cli:
setter:
name: profilesetter
value: asm
`,
},
{
name: "no-openAPI-file-error",
input: `
apiVersion: install.istio.io/v1alpha2
kind: IstioControlPlane
metadata:
clusterName: "project-id/us-east1-d/cluster-name"
spec:
profile: asm # {"type":"string","x-kustomize":{"setter":{"name":"profilesetter","value":"asm"}}}
hub: gcr.io/asm-testing
`,
err: "Krmfile:",
},
}
for i := range tests {
test := tests[i]
t.Run(test.name, func(t *testing.T) {
openAPIFileName := "Krmfile"
dir, err := ioutil.TempDir("", "")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.RemoveAll(dir)
err = ioutil.WriteFile(filepath.Join(dir, "deploy.yaml"), []byte(test.input), 0600)
if !assert.NoError(t, err) {
t.FailNow()
}
if test.openAPIFile != "" {
err = ioutil.WriteFile(filepath.Join(dir, openAPIFileName), []byte(test.openAPIFile), 0600)
if !assert.NoError(t, err) {
t.FailNow()
}
}
sf := SetterFixer{
PkgPath: dir,
DryRun: test.dryRun,
OpenAPIPath: filepath.Join(dir, "Krmfile"),
}
sfr, err := sf.FixV1Setters()
if test.err == "" {
if !assert.NoError(t, err) {
t.FailNow()
}
} else {
if !assert.Contains(t, err.Error(), test.err) {
t.FailNow()
}
return
}
actualOutput, err := ioutil.ReadFile(filepath.Join(dir, "deploy.yaml"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.Equal(t, test.expectedOutput, string(actualOutput))
if test.expectedOpenAPI != "" {
actualOpenAPI, err := ioutil.ReadFile(filepath.Join(dir, openAPIFileName))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.Equal(t, test.expectedOpenAPI, string(actualOpenAPI))
}
assert.Equal(t, test.needFix, sfr.NeedFix)
assert.Equal(t, test.createdSetters, sfr.CreatedSetters)
assert.Equal(t, test.createdSubst, sfr.CreatedSubst)
assert.Equal(t, test.failedSubst, sfr.FailedSubst)
assert.Equal(t, test.failedSetters, sfr.FailedSetters)
})
}
}

View File

@@ -0,0 +1,134 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package fixsetters
import (
"fmt"
"hash/fnv"
"strings"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
var _ yaml.Filter = &upgradeV1Setters{}
// upgradeV1Setters looks up v1 setters in a Resource and upgrades
// all the setters related comments
type upgradeV1Setters struct {
// Name of the setter to lookup. Optional
Name string
// Setters is a list of setters that were found
Setters []setter
// Substitutions is a list of substitutions that were found
Substitutions []substitution
}
type substitution struct {
Name string
FieldVale string
Pattern string
}
type setter struct {
PartialFieldSetter
Description string
Type string
SetBy string
SubstName string
}
func (ls *upgradeV1Setters) Filter(object *yaml.RNode) (*yaml.RNode, error) {
switch object.YNode().Kind {
case yaml.DocumentNode:
// skip the document node
return ls.Filter(yaml.NewRNode(object.YNode().Content[0]))
case yaml.MappingNode:
return object, object.VisitFields(func(node *yaml.MapNode) error {
return node.Value.PipeE(ls)
})
case yaml.SequenceNode:
return object, object.VisitElements(func(node *yaml.RNode) error {
return node.PipeE(ls)
})
case yaml.ScalarNode:
return object, ls.lookupAndUpgrade(object)
default:
return object, nil
}
}
// lookupAndUpgrade finds any setters for a field and upgrades the setters comment
func (ls *upgradeV1Setters) lookupAndUpgrade(field *yaml.RNode) error {
// check if there is a substitution for this field
var fm = FieldMetaV1{}
if err := fm.UpgradeV1SetterComment(field); err != nil {
return err
}
if fm.Extensions.FieldSetter != nil {
if ls.Name != "" && ls.Name != fm.Extensions.FieldSetter.Name {
// skip this setter, it doesn't match the specified setter
return nil
}
// full setter
ls.Setters = append(ls.Setters, setter{
PartialFieldSetter: *fm.Extensions.FieldSetter,
Description: fm.Schema.Description,
Type: fm.Schema.Type[0],
SetBy: fm.Extensions.SetBy,
})
return nil
}
if len(fm.Extensions.PartialFieldSetters) > 0 {
fieldValue := field.YNode().Value
pattern := fieldValue
var substName string
// derive substitution pattern from partial setters
for i := range fm.Extensions.PartialFieldSetters {
substName += fm.Extensions.PartialFieldSetters[i].Name + "-"
pattern = strings.Replace(pattern, fm.Extensions.PartialFieldSetters[i].Value, `${`+fm.Extensions.PartialFieldSetters[i].Name+"}", 1)
}
fvHash, err := FNV32aHash(fieldValue)
if err != nil {
return err
}
substName += fvHash
ls.Substitutions = append(ls.Substitutions, substitution{
Name: substName,
FieldVale: fieldValue,
Pattern: pattern,
})
}
for i := range fm.Extensions.PartialFieldSetters {
if ls.Name != "" && ls.Name != fm.Extensions.PartialFieldSetters[i].Name {
// skip this setter
continue
}
ls.Setters = append(ls.Setters, setter{
PartialFieldSetter: fm.Extensions.PartialFieldSetters[i],
Description: fm.Schema.Description,
Type: fm.Schema.Type[0],
SetBy: fm.Extensions.SetBy,
})
}
return nil
}
// FNV32aHash generates 32-bit FNV-1a hash for input string
func FNV32aHash(text string) (string, error) {
algorithm := fnv.New32a()
_, err := algorithm.Write([]byte(text))
if err != nil {
return "", errors.Wrap(err)
}
return fmt.Sprint(algorithm.Sum32()), nil
}

View File

@@ -0,0 +1,86 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package fixsetters
import (
"sort"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
var _ kio.Filter = &UpgradeV1Setters{}
// UpgradeV1Setters identifies setters for a collection of Resources to upgrade
type UpgradeV1Setters struct {
// Name is the name of the setter to match. Optional.
Name string
// SetterCounts is populated by Filter and contains the count of fields matching each setter.
SetterCounts []setterCount
// Substitutions are groups of partial setters
Substitutions []substitution
}
// setterCount records the identified setters and number of fields matching those setters
type setterCount struct {
// Count is the number of substitutions possible to perform
Count int
// setter is the substitution found
setter
}
// Filter implements kio.Filter
func (l *UpgradeV1Setters) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
setters := map[string]*setterCount{}
substitutions := map[string]*substitution{}
for i := range input {
// lookup substitutions for this object
ls := &upgradeV1Setters{Name: l.Name}
if err := input[i].PipeE(ls); err != nil {
return nil, err
}
// aggregate counts for each setter by name. takes the description and value from
// the first setter for each name encountered.
for j := range ls.Setters {
setter := ls.Setters[j]
curr, found := setters[setter.Name]
if !found {
curr = &setterCount{setter: setter}
setters[setter.Name] = curr
}
curr.Count++
}
for j := range ls.Substitutions {
subst := ls.Substitutions[j]
_, found := substitutions[subst.Name]
if !found {
substitutions[subst.Name] = &subst
}
}
}
// pull out and sort the results by setter name
for _, v := range setters {
l.SetterCounts = append(l.SetterCounts, *v)
}
for _, subst := range substitutions {
l.Substitutions = append(l.Substitutions, *subst)
}
sort.Slice(l.Substitutions, func(i, j int) bool {
return l.Substitutions[i].Name < l.Substitutions[j].Name
})
sort.Slice(l.SetterCounts, func(i, j int) bool {
return l.SetterCounts[i].Name < l.SetterCounts[j].Name
})
return input, nil
}

View File

@@ -162,6 +162,8 @@ func (c *Filter) setupExec() {
c.Exec.Args = args
}
var tmpDirEnvKey string = "TMPDIR"
// getArgs returns the command + args to run to spawn the container
func (c *Filter) getCommand() (string, []string) {
// run the container using docker. this is simpler than using the docker
@@ -189,13 +191,15 @@ func (c *Filter) getCommand() (string, []string) {
args = append(args, "--mount", storageMount.String())
}
// TODO: put these env processes into a separate function and call it in the outside of
// getCommand
os.Setenv("LOG_TO_STDERR", "true")
os.Setenv("STRUCTURED_RESULTS", "true")
// export the local environment vars to the container
for _, pair := range os.Environ() {
items := strings.Split(pair, "=")
if items[0] == "" || items[1] == "" {
if items[0] == "" || items[1] == "" || items[0] == tmpDirEnvKey {
continue
}
args = append(args, "-e", items[0])

View File

@@ -106,7 +106,7 @@ metadata:
for _, e := range os.Environ() {
// the process env
parts := strings.Split(e, "=")
if parts[0] == "" || parts[1] == "" {
if parts[0] == "" || parts[1] == "" || parts[0] == tmpDirEnvKey {
continue
}
tt.expectedArgs = append(tt.expectedArgs, "-e", parts[0])
@@ -210,3 +210,15 @@ func TestFilter_ExitCode(t *testing.T) {
t.FailNow()
}
}
func TestIgnoreEnv(t *testing.T) {
os.Setenv(tmpDirEnvKey, "")
fltr := Filter{Image: "example.com:version"}
_, args := fltr.getCommand()
for _, arg := range args {
if arg == tmpDirEnvKey {
t.Fatalf("%s should not be exported to container", tmpDirEnvKey)
}
}
}

View File

@@ -138,7 +138,7 @@ func getFunctionSpecFromAnnotation(n *yaml.RNode, meta yaml.ResourceMeta) *Funct
}
}
n, err := n.Pipe(yaml.Lookup("metadata", "configFn"))
if err != nil || yaml.IsEmpty(n) {
if err != nil || yaml.IsMissingOrNull(n) {
return nil
}
s, err := n.String()

View File

@@ -11,11 +11,10 @@ require (
github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d
github.com/sergi/go-diff v1.1.0
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.3
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.4.0
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect
gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71
)

View File

@@ -180,6 +180,8 @@ github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHN
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -226,9 +228,8 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -270,6 +271,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC7ya0fuo7cSJ3UCKYmM=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVBZUmHSvLbMjRj6NUUKo=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -176,12 +176,6 @@ func (r *ByteReader) Read() ([]*yaml.RNode, error) {
return output, nil
}
func isEmptyDocument(node *yaml.Node) bool {
// node is a Document with no content -- e.g. "---\n---"
return node.Kind == yaml.DocumentNode &&
node.Content[0].Tag == yaml.NullNodeTag
}
func (r *ByteReader) decode(index int, decoder *yaml.Decoder) (*yaml.RNode, error) {
node := &yaml.Node{}
err := decoder.Decode(node)
@@ -192,7 +186,7 @@ func (r *ByteReader) decode(index int, decoder *yaml.Decoder) (*yaml.RNode, erro
return nil, errors.Wrap(err)
}
if isEmptyDocument(node) {
if yaml.IsYNodeEmptyDoc(node) {
return nil, nil
}

View File

@@ -238,10 +238,10 @@ func ownerToString(node *yaml.RNode) (string, error) {
owner := elements[0]
var kind, name string
if value := owner.Field("kind"); !yaml.IsFieldEmpty(value) {
if value := owner.Field("kind"); !value.IsNilOrEmpty() {
kind = value.Value.YNode().Value
}
if value := owner.Field("name"); !yaml.IsFieldEmpty(value) {
if value := owner.Field("name"); !value.IsNilOrEmpty() {
name = value.Value.YNode().Value
}

View File

@@ -36,7 +36,7 @@ type ResourceSchema struct {
}
// IsEmpty returns true if the ResourceSchema is empty
func (rs *ResourceSchema) IsEmpty() bool {
func (rs *ResourceSchema) IsMissingOrNull() bool {
if rs == nil || rs.Schema == nil {
return true
}
@@ -84,7 +84,7 @@ func AddSchemaFromFileUsingField(path, field string) error {
if field != "" {
// get the field containing the openAPI
m := y.Field(field)
if yaml.IsFieldEmpty(m) {
if m.IsNilOrEmpty() {
// doesn't contain openAPI definitions
return nil
}

View File

@@ -97,7 +97,7 @@ func (a *Add) visitMapping(object *yaml.RNode, p string, _ *openapi.ResourceSche
// visitScalar implements visitor
// visitScalar will set the field metadata on each scalar field whose name + value match
func (a *Add) visitScalar(object *yaml.RNode, p string, _ *openapi.ResourceSchema) error {
func (a *Add) visitScalar(object *yaml.RNode, p string, _, _ *openapi.ResourceSchema) error {
// check if the field matches
if a.Type == "array" {
return nil

View File

@@ -45,7 +45,7 @@ func (d *Delete) visitMapping(_ *yaml.RNode, _ string, _ *openapi.ResourceSchema
// visitScalar implements visitor
// visitScalar will remove the reference on each scalar field whose name matches.
func (d *Delete) visitScalar(object *yaml.RNode, p string, _ *openapi.ResourceSchema) error {
func (d *Delete) visitScalar(object *yaml.RNode, p string, _, _ *openapi.ResourceSchema) error {
// check if the field matches
if d.FieldName != "" && !strings.HasSuffix(p, d.FieldName) {
return nil

View File

@@ -53,7 +53,7 @@ func (l *List) listSetters(object *yaml.RNode, resourcePath string) error {
if err != nil {
return err
}
if yaml.IsEmpty(def) {
if yaml.IsMissingOrNull(def) {
return nil
}
@@ -73,7 +73,7 @@ func (l *List) listSetters(object *yaml.RNode, resourcePath string) error {
if err != nil {
return err
}
if yaml.IsEmpty(setterNode) {
if yaml.IsMissingOrNull(setterNode) {
// has the setter prefix, but missing the setter extension
return errors.Errorf("missing x-k8s-cli.setter for %s", key)
}
@@ -126,7 +126,7 @@ func (l *List) listSubst(object *yaml.RNode) error {
if err != nil {
return err
}
if yaml.IsEmpty(def) {
if yaml.IsMissingOrNull(def) {
return nil
}
@@ -146,7 +146,7 @@ func (l *List) listSubst(object *yaml.RNode) error {
if err != nil {
return err
}
if yaml.IsEmpty(substNode) {
if yaml.IsMissingOrNull(substNode) {
// has the substitution prefix, but missing the setter extension
return errors.Errorf("missing x-k8s-cli.substitution for %s", key)
}

View File

@@ -81,9 +81,9 @@ func (s *Set) visitSequence(object *yaml.RNode, p string, schema *openapi.Resour
}
// visitScalar
func (s *Set) visitScalar(object *yaml.RNode, p string, schema *openapi.ResourceSchema) error {
func (s *Set) visitScalar(object *yaml.RNode, p string, oa, setterSchema *openapi.ResourceSchema) error {
// get the openAPI for this field describing how to apply the setter
ext, err := getExtFromComment(schema)
ext, err := getExtFromComment(setterSchema)
if err != nil {
return err
}
@@ -91,8 +91,13 @@ func (s *Set) visitScalar(object *yaml.RNode, p string, schema *openapi.Resource
return nil
}
var k8sSchema *spec.Schema
if oa != nil {
k8sSchema = oa.Schema
}
// perform a direct set of the field if it matches
ok, err := s.set(object, ext, schema.Schema)
ok, err := s.set(object, ext, k8sSchema, setterSchema.Schema)
if err != nil {
return err
}
@@ -214,7 +219,7 @@ func (s *Set) substituteUtil(ext *CliExtension, visited sets.String, nameMatch *
}
// set applies the value from ext to field if its name matches s.Name
func (s *Set) set(field *yaml.RNode, ext *CliExtension, sch *spec.Schema) (bool, error) {
func (s *Set) set(field *yaml.RNode, ext *CliExtension, k8sSch, sch *spec.Schema) (bool, error) {
// check full setter
if ext.Setter == nil || !s.isMatch(ext.Setter.Name) {
return false, nil
@@ -234,14 +239,21 @@ func (s *Set) set(field *yaml.RNode, ext *CliExtension, sch *spec.Schema) (bool,
// this has a full setter, set its value
field.YNode().Value = ext.Setter.Value
// format the node so it is quoted if it is a string
yaml.FormatNonStringStyle(field.YNode(), *sch)
// format the node so it is quoted if it is a string. If there is
// type information on the setter schema, we use it. Otherwise we
// fall back to the field schema if it exists.
if len(sch.Type) > 0 {
yaml.FormatNonStringStyle(field.YNode(), *sch)
} else if k8sSch != nil {
yaml.FormatNonStringStyle(field.YNode(), *k8sSch)
}
return true, nil
}
// validateAgainstSchema validates the input setter value against user provided
// openAI schema
func validateAgainstSchema(ext *CliExtension, sch *spec.Schema) error {
fixSchemaTypes(sch)
sc := spec.Schema{}
sc.Properties = map[string]spec.Schema{}
sc.Properties[ext.Setter.Name] = *sch
@@ -301,6 +313,33 @@ func validateAgainstSchema(ext *CliExtension, sch *spec.Schema) error {
return nil
}
// fixSchemaTypes traverses the schema and checks for some common
// errors for the type field. This currently involves users using
// 'int' instead of 'integer' and 'bool' instead of 'boolean'. Early versions
// of setters didn't validate this, so there are users that have invalid
// types in their packages.
func fixSchemaTypes(sc *spec.Schema) {
for i := range sc.Type {
currentType := sc.Type[i]
if currentType == "int" {
sc.Type[i] = "integer"
}
if currentType == "bool" {
sc.Type[i] = "boolean"
}
}
if items := sc.Items; items != nil {
if items.Schema != nil {
fixSchemaTypes(items.Schema)
}
for i := range items.Schemas {
schema := items.Schemas[i]
fixSchemaTypes(&schema)
}
}
}
// SetOpenAPI updates a setter value
type SetOpenAPI struct {
// Name is the name of the setter to add

View File

@@ -126,7 +126,7 @@ metadata:
},
{
name: "set-foo-no-type",
description: "if a type is not specified for a setter, keep the existing quoting",
description: "if a type is not specified for a setter or k8s schema, keep existing quoting",
setter: "foo",
openapi: `
openAPI:
@@ -138,16 +138,16 @@ openAPI:
value: "4"
`,
input: `
apiVersion: apps/v1
kind: Deployment
apiVersion: custom/v1
kind: Example
metadata:
name: nginx-deployment
annotations:
foo: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.foo"}
`,
expected: `
apiVersion: apps/v1
kind: Deployment
apiVersion: custom/v1
kind: Example
metadata:
name: nginx-deployment
annotations:
@@ -666,6 +666,68 @@ spec:
- "1"
- "2"
- "3"
`,
},
{
name: "set-with-invalid-type-int",
description: "if a type is set to int instead of integer, we accept it",
setter: "foo",
openapi: `
openAPI:
definitions:
io.k8s.cli.setters.foo:
x-k8s-cli:
setter:
name: foo
value: "4"
type: int
`,
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
foo: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.foo"}
`,
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
foo: 4 # {"$ref": "#/definitions/io.k8s.cli.setters.foo"}
`,
},
{
name: "set-with-invalid-type-bool",
description: "if a type is set to bool instead of boolean, we accept it",
setter: "foo",
openapi: `
openAPI:
definitions:
io.k8s.cli.setters.foo:
x-k8s-cli:
setter:
name: foo
value: "true"
type: bool
`,
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
foo: false # {"$ref": "#/definitions/io.k8s.cli.setters.foo"}
`,
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
foo: true # {"$ref": "#/definitions/io.k8s.cli.setters.foo"}
`,
},
}

View File

@@ -13,6 +13,7 @@ import (
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/setters2"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// SetterCreator creates or updates a setter in the OpenAPI definitions, and inserts references
@@ -45,6 +46,16 @@ type SetterCreator struct {
// live apply/preview. This field is added to the setter definition to record
// the package publisher's intent to make the setter required to be set.
Required bool
// Path to openAPI file
OpenAPIPath string
// Path to resources folder
ResourcesPath string
}
func (c *SetterCreator) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
return nil, c.Create(c.OpenAPIPath, c.ResourcesPath)
}
func (c SetterCreator) Create(openAPIPath, resourcesPath string) error {

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"io/ioutil"
"os"
"regexp"
"strings"
"github.com/go-openapi/spec"
@@ -42,9 +43,24 @@ type SubstitutionCreator struct {
// FieldValue if set will add the OpenAPI reference to fields if they have this value.
// Optional. If unspecified match all field values.
FieldValue string
// Path to openAPI file
OpenAPIPath string
// Path to resources folder
ResourcesPath string
}
func (c *SubstitutionCreator) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
return nil, c.Create(c.OpenAPIPath, c.ResourcesPath)
}
func (c SubstitutionCreator) Create(openAPIPath, resourcesPath string) error {
values, err := markersAndRefs(c.Name, c.Pattern)
if err != nil {
return err
}
c.Values = values
d := setters2.SubstitutionDefinition{
Name: c.Name,
Values: c.Values,
@@ -121,6 +137,47 @@ func (c SubstitutionCreator) Create(openAPIPath, resourcesPath string) error {
}.Execute()
}
// createMarkersAndRefs takes the input pattern, creates setter/substitution markers
// and corresponding openAPI refs
func markersAndRefs(substName, pattern string) ([]setters2.Value, error) {
var values []setters2.Value
// extract setter name tokens from pattern enclosed in ${}
re := regexp.MustCompile(`\$\{([^}]*)\}`)
markers := re.FindAllString(pattern, -1)
if len(markers) == 0 {
return nil, errors.Errorf("unable to find setter or substitution names in pattern, " +
"setter names must be enclosed in ${}")
}
for _, marker := range markers {
name := strings.TrimSuffix(strings.TrimPrefix(marker, "${"), "}")
if name == substName {
return nil, fmt.Errorf("setters must have different name than the substitution: %s", name)
}
ref, err := spec.NewRef(fieldmeta.DefinitionsPrefix + fieldmeta.SubstitutionDefinitionPrefix + name)
if err != nil {
return nil, err
}
var markerRef string
subst, _ := openapi.Resolve(&ref)
// check if the substitution exists with the marker name or fall back to creating setter
// ref with the name
if subst != nil {
markerRef = fieldmeta.DefinitionsPrefix + fieldmeta.SubstitutionDefinitionPrefix + name
} else {
markerRef = fieldmeta.DefinitionsPrefix + fieldmeta.SetterDefinitionPrefix + name
}
values = append(
values,
setters2.Value{Marker: marker, Ref: markerRef},
)
}
return values, nil
}
// CreateSettersForSubstitution creates the setters for all the references in the substitution
// values if they don't already exist in openAPIPath file.
func (c SubstitutionCreator) CreateSettersForSubstitution(openAPIPath string) error {

View File

@@ -16,7 +16,7 @@ type visitor interface {
// node is the scalar field value
// path is the path to the field; path elements are separated by '.'
// oa is the OpenAPI schema for the field
visitScalar(node *yaml.RNode, path string, oa *openapi.ResourceSchema) error
visitScalar(node *yaml.RNode, path string, oa *openapi.ResourceSchema, fieldOA *openapi.ResourceSchema) error
// visitSequence is called for each sequence field value on a resource
// node is the sequence field value
@@ -50,9 +50,9 @@ func acceptImpl(v visitor, object *yaml.RNode, p string, oa *openapi.ResourceSch
}
return object.VisitFields(func(node *yaml.MapNode) error {
// get the schema for the field and propagate it
oa = getSchema(node.Key, oa, node.Key.YNode().Value)
fieldSchema := getSchema(node.Key, oa, node.Key.YNode().Value)
// Traverse each field value
return acceptImpl(v, node.Value, p+"."+node.Key.YNode().Value, oa)
return acceptImpl(v, node.Value, p+"."+node.Key.YNode().Value, fieldSchema)
})
case yaml.SequenceNode:
// get the schema for the sequence node, use the schema provided if not present
@@ -61,15 +61,15 @@ func acceptImpl(v visitor, object *yaml.RNode, p string, oa *openapi.ResourceSch
return err
}
// get the schema for the elements
oa = getSchema(object, oa, "")
schema := getSchema(object, oa, "")
return object.VisitElements(func(node *yaml.RNode) error {
// Traverse each list element
return acceptImpl(v, node, p, oa)
return acceptImpl(v, node, p, schema)
})
case yaml.ScalarNode:
// Visit the scalar field
oa = getSchema(object, oa, "")
return v.visitScalar(object, p, oa)
fieldSchema := getSchema(object, oa, "")
return v.visitScalar(object, p, oa, fieldSchema)
}
return nil
}
@@ -110,7 +110,7 @@ func getSchema(r *yaml.RNode, s *openapi.ResourceSchema, field string) *openapi.
return s
}
if yaml.IsEmpty(r) {
if yaml.IsMissingOrNull(r) {
return nil
}

View File

@@ -67,14 +67,15 @@ func (e ElementSetter) Filter(rn *RNode) (*RNode, error) {
matchingElementFound := false
for i := range rn.YNode().Content {
elem := rn.Content()[i]
newNode := NewRNode(elem)
// empty elements are not valid -- they at least need an associative key
if IsEmpty(NewRNode(elem)) {
if IsMissingOrNull(newNode) || IsEmptyMap(newNode) {
continue
}
// check if this is the element we are matching
val, err := NewRNode(elem).Pipe(FieldMatcher{Name: e.Key, StringValue: e.Value})
val, err := newNode.Pipe(FieldMatcher{Name: e.Key, StringValue: e.Value})
if err != nil {
return nil, err
}
@@ -467,6 +468,9 @@ func (s FieldSetter) Filter(rn *RNode) (*RNode, error) {
if err := ErrorIfInvalid(rn, yaml.ScalarNode); err != nil {
return rn, err
}
if IsMissingOrNull(s.Value) {
return rn, nil
}
// only apply the style if there is not an existing style
// or we want to override it
if !s.OverrideStyle || s.Value.YNode().Style == 0 {
@@ -478,7 +482,7 @@ func (s FieldSetter) Filter(rn *RNode) (*RNode, error) {
}
// Clear the field if it is empty, or explicitly null
if s.Value == nil || IsNull(s.Value) {
if s.Value == nil || s.Value.IsTaggedNull() {
return rn.Pipe(Clear(s.Name))
}
@@ -545,7 +549,7 @@ func IsFoundOrError(rn *RNode, err error) bool {
func ErrorIfAnyInvalidAndNonNull(kind yaml.Kind, rn ...*RNode) error {
for i := range rn {
if IsEmpty(rn[i]) {
if IsMissingOrNull(rn[i]) {
continue
}
if err := ErrorIfInvalid(rn[i], kind); err != nil {
@@ -564,7 +568,7 @@ var nodeTypeIndex = map[yaml.Kind]string{
}
func ErrorIfInvalid(rn *RNode, kind yaml.Kind) error {
if rn == nil || rn.YNode() == nil || IsNull(rn) {
if IsMissingOrNull(rn) {
// node has no type, pass validation
return nil
}

View File

@@ -395,6 +395,19 @@ bar: buz
assert.Equal(t, `baz
`, assertNoErrorString(t)(k.String()))
// Empty value
node, err = Parse(`
foo
`)
assert.NoError(t, err)
instance = FieldSetter{}
k, err = instance.Filter(node)
assert.NoError(t, err)
assert.Equal(t, `foo
`, assertNoErrorString(t)(node.String()))
assert.Equal(t, `foo
`, assertNoErrorString(t)(k.String()))
// Encounter error
node, err = Parse(`
-a

View File

@@ -302,7 +302,7 @@ kind: Deployment
spec:
template:
spec:
containers: {}
containers: []
`,
dest: `
apiVersion: apps/v1

View File

@@ -262,7 +262,7 @@ kind: Deployment
{description: `remove list -- empty in src`,
source: `
kind: Deployment
items: {}
items: []
`,
dest: `
kind: Deployment
@@ -273,7 +273,7 @@ items:
`,
expected: `
kind: Deployment
items: {}
items: []
`,
},
}

View File

@@ -167,8 +167,8 @@ spec: {}
expected: `
kind: Deployment
spec:
foo: bar1
baz: buz
foo: bar1
`,
},

View File

@@ -52,11 +52,11 @@ func (m Merger) VisitMap(nodes walk.Sources, s *openapi.ResourceSchema) (*yaml.R
if err := m.SetStyle(nodes); err != nil {
return nil, err
}
if yaml.IsEmpty(nodes.Dest()) {
if yaml.IsMissingOrNull(nodes.Dest()) {
// Add
return nodes.Origin(), nil
}
if yaml.IsNull(nodes.Origin()) {
if nodes.Origin().IsTaggedNull() {
// clear the value
return walk.ClearNode, nil
}
@@ -108,11 +108,11 @@ func (m Merger) VisitList(nodes walk.Sources, s *openapi.ResourceSchema, kind wa
}
// Add
if yaml.IsEmpty(nodes.Dest()) {
if yaml.IsMissingOrNull(nodes.Dest()) {
return nodes.Origin(), nil
}
// Clear
if yaml.IsNull(nodes.Origin()) {
if nodes.Origin().IsTaggedNull() {
return walk.ClearNode, nil
}
// Recursively Merge dest

View File

@@ -91,7 +91,7 @@ kind: Deployment
{description: `remove scalar -- empty in src`,
source: `
kind: Deployment
field: {}
field: null
`,
dest: `
kind: Deployment
@@ -99,7 +99,6 @@ field: value1
`,
expected: `
kind: Deployment
field: {}
`,
},

View File

@@ -8,7 +8,8 @@ var elementTestCases = []testCase{
//
// Test Case
//
{description: `Add an element to an existing list`,
{
description: `Add an element to an existing list`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -57,7 +58,8 @@ spec:
//
// Test Case
//
{description: `Add an element to a non-existing list`,
{
description: `Add an element to a non-existing list`,
origin: `
apiVersion: apps/v1
kind: Deployment`,
@@ -86,7 +88,8 @@ spec:
name: foo
`},
{description: `Add an element to a non-existing list, existing in dest`,
{
description: `Add an element to a non-existing list, existing in dest`,
origin: `
apiVersion: apps/v1
kind: Deployment`,
@@ -127,7 +130,8 @@ spec:
// Test Case
// TODO(pwittrock): Figure out if there is something better we can do here
// This element is missing from the destination -- only the new fields are added
{description: `Add a field to the element, element missing from dest`,
{
description: `Add a field to the element, element missing from dest`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -168,7 +172,8 @@ spec:
//
// Test Case
//
{description: `Update a field on the elem, element missing from the dest`,
{
description: `Update a field on the elem, element missing from the dest`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -212,7 +217,8 @@ spec:
//
// Test Case
//
{description: `Update a field on the elem, element present in the dest`,
{
description: `Update a field on the elem, element present in the dest`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -308,7 +314,8 @@ spec:
//
// Test Case
//
{description: `Add a field on the elem, element and field present in the dest`,
{
description: `Add a field on the elem, element and field present in the dest`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -356,14 +363,15 @@ spec:
//
// Test Case
//
{description: `Ignore an element`,
{
description: `Ignore an element`,
origin: `
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers: {}
containers: null
`,
update: `
apiVersion: apps/v1
@@ -371,7 +379,7 @@ kind: Deployment
spec:
template:
spec:
containers: {}
containers: null
`,
local: `
apiVersion: apps/v1
@@ -397,7 +405,8 @@ spec:
//
// Test Case
//
{description: `Leave deleted`,
{
description: `Leave deleted`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -424,7 +433,8 @@ kind: Deployment
//
// Test Case
//
{description: `Remove an element -- matching`,
{
description: `Remove an element -- matching`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -463,7 +473,8 @@ spec:
//
// Test Case
//
{description: `Remove an element -- field missing from update`,
{
description: `Remove an element -- field missing from update`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -503,7 +514,8 @@ spec:
//
// Test Case
//
{description: `Remove an element -- element missing`,
{
description: `Remove an element -- element missing`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -554,7 +566,8 @@ spec:
//
// Test Case
//
{description: `Remove an element -- empty containers`,
{
description: `Remove an element -- empty containers`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -571,7 +584,7 @@ kind: Deployment
spec:
template:
spec:
containers: {}
containers: null
`,
local: `
apiVersion: apps/v1
@@ -595,7 +608,8 @@ spec:
//
// Test Case
//
{description: `Remove an element -- missing list field`,
{
description: `Remove an element -- missing list field`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -635,7 +649,8 @@ spec:
//
// Test Case
//
{description: `infer merge keys merge'`,
{
description: `infer merge keys merge'`,
origin: `
kind: Deployment
spec:
@@ -678,7 +693,8 @@ spec:
//
// Test Case
//
{description: `no infer merge keys merge using schema`,
{
description: `no infer merge keys merge using schema`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -725,7 +741,8 @@ spec:
//
// Test Case
//
{description: `no infer merge keys merge using explicit schema as line comment'`,
{
description: `no infer merge keys merge using explicit schema as line comment'`,
origin: `
apiVersion: custom
kind: Deployment
@@ -772,7 +789,8 @@ spec:
//
// Test Case
//
{description: `no infer merge keys merge using explicit schema as head comment'`,
{
description: `no infer merge keys merge using explicit schema as head comment'`,
origin: `
apiVersion: custom
kind: Deployment
@@ -821,7 +839,8 @@ spec:
//
// Test Case
//
{description: `no infer merge keys merge using explicit schema to parent field'`,
{
description: `no infer merge keys merge using explicit schema to parent field'`,
origin: `
apiVersion: custom
kind: Deployment
@@ -860,7 +879,8 @@ spec: # {"$ref":"#/definitions/io.k8s.api.core.v1.PodSpec"}
//
// Test Case
//
{description: `no infer merge keys merge using explicit schema to parent field header'`,
{
description: `no infer merge keys merge using explicit schema to parent field header'`,
origin: `
apiVersion: custom
kind: Deployment

View File

@@ -6,7 +6,8 @@ package merge3_test
var kustomizationTestCases = []testCase{
// Kustomization Test Cases
{description: `ConfigMapGenerator merge`,
{
description: `ConfigMapGenerator merge`,
origin: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
@@ -48,7 +49,8 @@ configMapGenerator:
- configkey=configs/another_configfile2
name: a-configmap2`},
{description: `SecretGenerator merge`,
{
description: `SecretGenerator merge`,
origin: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

View File

@@ -9,7 +9,8 @@ var listTestCases = []testCase{
//
// Test Case
//
{description: `Replace list`,
{
description: `Replace list`,
origin: `
list:
- 1
@@ -34,7 +35,8 @@ list:
//
// Test Case
//
{description: `Add an updated list`,
{
description: `Add an updated list`,
origin: `
apiVersion: apps/v1
list: # old value
@@ -62,7 +64,8 @@ list:
//
// Test Case
//
{description: `Add keep an omitted field`,
{
description: `Add keep an omitted field`,
origin: `
apiVersion: apps/v1
kind: Deployment`,
@@ -89,7 +92,8 @@ kind: StatefulSet
// Test Case
//
// TODO(#36): consider making this an error
{description: `Change an updated field`,
{
description: `Change an updated field`,
origin: `
apiVersion: apps/v1
list: # old value
@@ -119,7 +123,8 @@ list: # conflicting value
//
// Test Case
//
{description: `Ignore a field -- set`,
{
description: `Ignore a field -- set`,
origin: `
apiVersion: apps/v1
list: # ignore value
@@ -151,7 +156,8 @@ list:
//
// Test Case
//
{description: `Ignore a field -- empty`,
{
description: `Ignore a field -- empty`,
origin: `
apiVersion: apps/v1
list: # ignore value
@@ -174,7 +180,8 @@ apiVersion: apps/v1
//
// Test Case
//
{description: `Explicitly clear a field`,
{
description: `Explicitly clear a field`,
origin: `
apiVersion: apps/v1`,
update: `
@@ -192,7 +199,8 @@ apiVersion: apps/v1`},
//
// Test Case
//
{description: `Implicitly clear a field`,
{
description: `Implicitly clear a field`,
origin: `
apiVersion: apps/v1
list: # clear value
@@ -214,7 +222,8 @@ apiVersion: apps/v1`},
// Test Case
//
// TODO(#36): consider making this an error
{description: `Implicitly clear a changed field`,
{
description: `Implicitly clear a changed field`,
origin: `
apiVersion: apps/v1
list: # old value

View File

@@ -7,7 +7,8 @@ var mapTestCases = []testCase{
//
// Test Case
//
{description: `Add the annotations map field`,
{
description: `Add the annotations map field`,
origin: `
kind: Deployment`,
update: `
@@ -27,7 +28,8 @@ metadata:
//
// Test Case
//
{description: `Add an annotation to the field`,
{
description: `Add an annotation to the field`,
origin: `
kind: Deployment
metadata:
@@ -54,7 +56,8 @@ metadata:
//
// Test Case
//
{description: `Add an annotation to the field, field missing from dest`,
{
description: `Add an annotation to the field, field missing from dest`,
origin: `
kind: Deployment
metadata:
@@ -64,7 +67,7 @@ metadata:
kind: Deployment
metadata:
annotations:
a: b # ignore because unchanged
a: b # ignored because unchanged
d: e`,
local: `
kind: Deployment`,
@@ -77,7 +80,8 @@ metadata:
//
// Test Case
//
{description: `Update an annotation on the field, field messing rom the dest`,
{
description: `Update an annotation on the field, field messing rom the dest`,
origin: `
kind: Deployment
metadata:
@@ -105,7 +109,8 @@ metadata:
//
// Test Case
//
{description: `Add an annotation to the field, field missing from dest`,
{
description: `Add an annotation to the field, field missing from dest`,
origin: `
kind: Deployment
metadata:
@@ -115,7 +120,7 @@ metadata:
kind: Deployment
metadata:
annotations:
a: b # ignore because unchanged
a: b # ignored because unchanged
d: e`,
local: `
kind: Deployment`,
@@ -128,7 +133,8 @@ metadata:
//
// Test Case
//
{description: `Remove an annotation`,
{
description: `Remove an annotation`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -158,7 +164,8 @@ metadata:
// Test Case
//
// TODO(#36) support ~annotations~: {} deletion
{description: `Specify a field as empty that isn't present in the source`,
{
description: `Specify a field as empty that isn't present in the source`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -186,7 +193,8 @@ metadata:
//
// Test Case
//
{description: `Remove an annotation`,
{
description: `Remove an annotation`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -213,7 +221,8 @@ metadata:
//
// Test Case
//
{description: `Remove annotations field`,
{
description: `Remove annotations field`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -238,7 +247,8 @@ metadata:
//
// Test Case
//
{description: `Remove annotations field, but keep in dest`,
{
description: `Remove annotations field, but keep in dest`,
origin: `
apiVersion: apps/v1
kind: Deployment
@@ -266,7 +276,8 @@ metadata:
//
// Test Case
//
{description: `Remove annotations, but they are already empty`,
{
description: `Remove annotations, but they are already empty`,
origin: `
apiVersion: apps/v1
kind: Deployment

View File

@@ -8,13 +8,15 @@ var scalarTestCases = []testCase{
//
// Test Case
//
{description: `Set and updated a field`,
origin: `kind: Deployment`,
update: `kind: StatefulSet`,
local: `kind: Deployment`,
expected: `kind: StatefulSet`},
{
description: `Set and updated a field`,
origin: `kind: Deployment`,
update: `kind: StatefulSet`,
local: `kind: Deployment`,
expected: `kind: StatefulSet`},
{description: `Add an updated field`,
{
description: `Add an updated field`,
origin: `
apiVersion: apps/v1
kind: Deployment # old value`,
@@ -27,7 +29,109 @@ apiVersion: apps/v1`,
apiVersion: apps/v1
kind: StatefulSet # new value`},
{description: `Add keep an omitted field`,
//
// Test Case
//
{
description: `Ensure comments are updated`,
origin: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/merge-source: 'dest'
config.kubernetes.io/path: 'temp.yaml'
spec:
replicas: 3`,
update: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/merge-source: 'updated'
config.kubernetes.io/path: 'temp.yaml'
spec:
replicas: 3 # {"$openapi":"replicas"}`,
local: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/merge-source: 'original'
config.kubernetes.io/path: 'temp.yaml'
spec:
replicas: 3
`,
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/merge-source: 'updated'
config.kubernetes.io/path: 'temp.yaml'
spec:
replicas: 3 # {"$openapi":"replicas"}
`},
{
description: `Ensure deleted comments are not updated`,
origin: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/merge-source: 'dest'
config.kubernetes.io/path: 'temp.yaml'
spec:
replicas: 3 # {"$openapi":"replicas"}`,
update: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/merge-source: 'updated'
config.kubernetes.io/path: 'temp.yaml'
spec:
replicas: 3 # {"$openapi":"replicas"}`,
local: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/merge-source: 'original'
config.kubernetes.io/path: 'temp.yaml'
spec:
replicas: 4
`,
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/merge-source: 'updated'
config.kubernetes.io/path: 'temp.yaml'
spec:
replicas: 4
`},
{
description: `Add keep an omitted field`,
origin: `
apiVersion: apps/v1
kind: Deployment`,
@@ -48,7 +152,8 @@ kind: StatefulSet
// Test Case
//
// TODO(#36): consider making this an error
{description: `Change an updated field`,
{
description: `Change an updated field`,
origin: `
apiVersion: apps/v1
kind: Deployment # old value`,
@@ -62,7 +167,8 @@ kind: Service # conflicting value`,
apiVersion: apps/v1
kind: StatefulSet # new value`},
{description: `Ignore a field`,
{
description: `Ignore a field`,
origin: `
apiVersion: apps/v1
kind: Deployment # ignore this field`,
@@ -74,7 +180,8 @@ apiVersion: apps/v1`,
expected: `
apiVersion: apps/v1`},
{description: `Explicitly clear a field`,
{
description: `Explicitly clear a field`,
origin: `
apiVersion: apps/v1`,
update: `
@@ -102,7 +209,8 @@ apiVersion: apps/v1`},
// Test Case
//
// TODO(#36): consider making this an error
{description: `Implicitly clear a changed field`,
{
description: `Implicitly clear a changed field`,
origin: `
apiVersion: apps/v1
kind: Deployment`,
@@ -117,7 +225,8 @@ apiVersion: apps/v1`},
//
// Test Case
//
{description: `Merge an empty scalar value`,
{
description: `Merge an empty scalar value`,
origin: `
apiVersion: apps/v1
`,

View File

@@ -4,6 +4,7 @@
package merge3
import (
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/yaml"
"sigs.k8s.io/kustomize/kyaml/yaml/walk"
@@ -19,7 +20,7 @@ const (
type Visitor struct{}
func (m Visitor) VisitMap(nodes walk.Sources, s *openapi.ResourceSchema) (*yaml.RNode, error) {
if yaml.IsNull(nodes.Updated()) || yaml.IsNull(nodes.Dest()) {
if nodes.Updated().IsTaggedNull() || nodes.Dest().IsTaggedNull() {
// explicitly cleared from either dest or update
return walk.ClearNode, nil
}
@@ -38,11 +39,11 @@ func (m Visitor) VisitMap(nodes walk.Sources, s *openapi.ResourceSchema) (*yaml.
}
func (m Visitor) visitAList(nodes walk.Sources, _ *openapi.ResourceSchema) (*yaml.RNode, error) {
if yaml.IsEmpty(nodes.Updated()) && !yaml.IsEmpty(nodes.Origin()) {
if yaml.IsMissingOrNull(nodes.Updated()) && !yaml.IsMissingOrNull(nodes.Origin()) {
// implicitly cleared from update -- element was deleted
return walk.ClearNode, nil
}
if yaml.IsEmpty(nodes.Dest()) {
if yaml.IsMissingOrNull(nodes.Dest()) {
// not cleared, but missing from the dest
// initialize a new value that can be recursively merged
return yaml.NewRNode(&yaml.Node{Kind: yaml.SequenceNode}), nil
@@ -53,19 +54,33 @@ func (m Visitor) visitAList(nodes walk.Sources, _ *openapi.ResourceSchema) (*yam
}
func (m Visitor) VisitScalar(nodes walk.Sources, s *openapi.ResourceSchema) (*yaml.RNode, error) {
if yaml.IsNull(nodes.Updated()) || yaml.IsNull(nodes.Dest()) {
if nodes.Updated().IsTaggedNull() || nodes.Dest().IsTaggedNull() {
// explicitly cleared from either dest or update
return nil, nil
}
if yaml.IsEmpty(nodes.Updated()) != yaml.IsEmpty(nodes.Origin()) {
if yaml.IsMissingOrNull(nodes.Updated()) != yaml.IsMissingOrNull(nodes.Origin()) {
// value added or removed in update
return nodes.Updated(), nil
}
if yaml.IsEmpty(nodes.Updated()) && yaml.IsEmpty(nodes.Origin()) {
if yaml.IsMissingOrNull(nodes.Updated()) && yaml.IsMissingOrNull(nodes.Origin()) {
// value added or removed in update
return nodes.Dest(), nil
}
updatedStr, err := nodes.Updated().String()
if err != nil {
return nil, errors.Wrap(err)
}
originStr, err := nodes.Origin().String()
if err != nil {
return nil, errors.Wrap(err)
}
if updatedStr != originStr {
// change in update node
return nodes.Updated(), nil
}
if nodes.Updated().YNode().Value != nodes.Origin().YNode().Value {
// value changed in update
return nodes.Updated(), nil
@@ -76,16 +91,16 @@ func (m Visitor) VisitScalar(nodes walk.Sources, s *openapi.ResourceSchema) (*ya
}
func (m Visitor) visitNAList(nodes walk.Sources) (*yaml.RNode, error) {
if yaml.IsNull(nodes.Updated()) || yaml.IsNull(nodes.Dest()) {
if nodes.Updated().IsTaggedNull() || nodes.Dest().IsTaggedNull() {
// explicitly cleared from either dest or update
return walk.ClearNode, nil
}
if yaml.IsEmpty(nodes.Updated()) != yaml.IsEmpty(nodes.Origin()) {
if yaml.IsMissingOrNull(nodes.Updated()) != yaml.IsMissingOrNull(nodes.Origin()) {
// value added or removed in update
return nodes.Updated(), nil
}
if yaml.IsEmpty(nodes.Updated()) && yaml.IsEmpty(nodes.Origin()) {
if yaml.IsMissingOrNull(nodes.Updated()) && yaml.IsMissingOrNull(nodes.Origin()) {
// value not present in source or dest
return nodes.Dest(), nil
}

View File

@@ -22,7 +22,7 @@ func IsAssociative(schema *openapi.ResourceSchema, nodes []*yaml.RNode, infer bo
}
for i := range nodes {
node := nodes[i]
if yaml.IsEmpty(node) {
if yaml.IsMissingOrNull(node) {
continue
}
if node.IsAssociative() {

View File

@@ -26,70 +26,65 @@ const (
NodeTagMap = "!!map"
NodeTagSeq = "!!seq"
NodeTagEmpty = ""
// TODO: deprecate these
NullNodeTag = NodeTagNull
StringTag = NodeTagString
BoolTag = NodeTagBool
IntTag = NodeTagInt
)
// NullNode returns a RNode point represents a null; value
func NullNode() *RNode {
return NewRNode(&Node{Tag: NullNodeTag})
// MakeNullNode returns an RNode that represents an empty document.
func MakeNullNode() *RNode {
return NewRNode(&Node{Tag: NodeTagNull})
}
// IsMissingOrNull returns true if the RNode is nil or contains and explicitly null value.
// IsMissingOrNull is true if the RNode is nil or explicitly tagged null.
// TODO: make this a method on RNode.
func IsMissingOrNull(node *RNode) bool {
return node == nil || node.YNode() == nil || node.YNode().Tag == NullNodeTag
return node.IsNil() || node.YNode().Tag == NodeTagNull
}
// IsEmpty returns true if the RNode is MissingOrNull, or is either a MappingNode with
// no fields.
func IsEmpty(node *RNode) bool {
if IsMissingOrNull(node) {
return true
}
// Empty sequence is a special case and temporarily not considered as empty here.
// Some users may want to keep empty sequence for compatibility reason.
// For example, use JSON 6902 patch.
return node.YNode().Kind == yaml.MappingNode && len(node.YNode().Content) == 0
}
func IsNull(node *RNode) bool {
return node != nil && node.YNode() != nil && node.YNode().Tag == NullNodeTag
// IsEmptyMap returns true if the RNode is an empty node or an empty map.
// TODO: make this a method on RNode.
func IsEmptyMap(node *RNode) bool {
return IsMissingOrNull(node) || IsYNodeEmptyMap(node.YNode())
}
func IsFieldEmpty(node *MapNode) bool {
if node == nil || node.Value == nil || node.Value.YNode() == nil ||
node.Value.YNode().Tag == NullNodeTag {
node.Value.YNode().Tag == NodeTagNull {
return true
}
return IsYNodeEmptyMap(node.Value.YNode()) ||
IsYNodeEmptySeq(node.Value.YNode())
}
if node.Value.YNode().Kind == yaml.MappingNode && len(node.Value.YNode().Content) == 0 {
return true
}
if node.Value.YNode().Kind == yaml.SequenceNode && len(node.Value.YNode().Content) == 0 {
return true
}
func IsYNodeEmptyMap(n *yaml.Node) bool {
return n != nil && n.Kind == yaml.MappingNode && len(n.Content) == 0
}
return false
// IsYNodeTaggedNull returns true if the node is explicitly tagged Null.
func IsYNodeTaggedNull(n *yaml.Node) bool {
return n != nil && n.Tag == NodeTagNull
}
func IsYNodeEmptySeq(n *yaml.Node) bool {
return n != nil && n.Kind == yaml.SequenceNode && len(n.Content) == 0
}
// IsYNodeEmptyDoc is true if the node is a Document with no content.
// E.g.: "---\n---"
func IsYNodeEmptyDoc(n *yaml.Node) bool {
return n.Kind == yaml.DocumentNode && n.Content[0].Tag == NodeTagNull
}
func IsYNodeString(n *yaml.Node) bool {
return n.Kind == yaml.ScalarNode && n.Tag == NodeTagString
}
// GetValue returns underlying yaml.Node Value field
func GetValue(node *RNode) string {
if IsEmpty(node) {
if IsMissingOrNull(node) {
return ""
}
return node.YNode().Value
}
func IsFieldNull(node *MapNode) bool {
return node != nil && node.Value != nil && node.Value.YNode() != nil &&
node.Value.YNode().Tag == NullNodeTag
}
// Parser parses values into configuration.
type Parser struct {
Kind string `yaml:"kind,omitempty"`
@@ -265,6 +260,12 @@ type MapNode struct {
Value *RNode
}
// IsNilOrEmpty returns true if the MapNode is nil,
// has no value, or has a value that appears empty.
func (mn *MapNode) IsNilOrEmpty() bool {
return mn == nil || mn.Value.IsNilOrEmpty()
}
type MapNodeSlice []*MapNode
func (m MapNodeSlice) Keys() []*RNode {
@@ -369,9 +370,28 @@ const (
LabelsField = "labels"
)
// IsNil is true if the node is nil, or its underlying YNode is nil.
func (rn *RNode) IsNil() bool {
return rn == nil || rn.YNode() == nil
}
// IsTaggedNull is true if a non-nil node is explicitly tagged Null.
func (rn *RNode) IsTaggedNull() bool {
return !rn.IsNil() && IsYNodeTaggedNull(rn.YNode())
}
// IsNilOrEmpty is true if the node is nil,
// has no YNode, or has YNode that appears empty.
func (rn *RNode) IsNilOrEmpty() bool {
return rn.IsNil() ||
IsYNodeTaggedNull(rn.YNode()) ||
IsYNodeEmptyMap(rn.YNode()) ||
IsYNodeEmptySeq(rn.YNode())
}
// GetMeta returns the ResourceMeta for an RNode
func (rn *RNode) GetMeta() (ResourceMeta, error) {
if IsEmpty(rn) {
if IsMissingOrNull(rn) {
return ResourceMeta{}, nil
}
missingMeta := true
@@ -386,17 +406,17 @@ func (rn *RNode) GetMeta() (ResourceMeta, error) {
m := ResourceMeta{}
// TODO: consider optimizing this parsing
if f := n.Field(APIVersionField); !IsFieldEmpty(f) {
if f := n.Field(APIVersionField); !f.IsNilOrEmpty() {
m.APIVersion = GetValue(f.Value)
missingMeta = false
}
if f := n.Field(KindField); !IsFieldEmpty(f) {
if f := n.Field(KindField); !f.IsNilOrEmpty() {
m.Kind = GetValue(f.Value)
missingMeta = false
}
mf := n.Field(MetadataField)
if IsFieldEmpty(mf) {
if mf.IsNilOrEmpty() {
if missingMeta {
return m, ErrMissingMetadata
}
@@ -404,16 +424,16 @@ func (rn *RNode) GetMeta() (ResourceMeta, error) {
}
meta := mf.Value
if f := meta.Field(NameField); !IsFieldEmpty(f) {
if f := meta.Field(NameField); !f.IsNilOrEmpty() {
m.Name = f.Value.YNode().Value
missingMeta = false
}
if f := meta.Field(NamespaceField); !IsFieldEmpty(f) {
if f := meta.Field(NamespaceField); !f.IsNilOrEmpty() {
m.Namespace = GetValue(f.Value)
missingMeta = false
}
if f := meta.Field(LabelsField); !IsFieldEmpty(f) {
if f := meta.Field(LabelsField); !f.IsNilOrEmpty() {
m.Labels = map[string]string{}
_ = f.Value.VisitFields(func(node *MapNode) error {
m.Labels[GetValue(node.Key)] = GetValue(node.Value)
@@ -421,7 +441,7 @@ func (rn *RNode) GetMeta() (ResourceMeta, error) {
})
missingMeta = false
}
if f := meta.Field(AnnotationsField); !IsFieldEmpty(f) {
if f := meta.Field(AnnotationsField); !f.IsNilOrEmpty() {
m.Annotations = map[string]string{}
_ = f.Value.VisitFields(func(node *MapNode) error {
m.Annotations[GetValue(node.Key)] = GetValue(node.Value)
@@ -640,7 +660,7 @@ func (rn *RNode) ElementValues(key string) ([]string, error) {
var elements []string
for i := 0; i < len(rn.Content()); i++ {
field := NewRNode(rn.Content()[i]).Field(key)
if !IsFieldEmpty(field) {
if !field.IsNilOrEmpty() {
elements = append(elements, field.Value.YNode().Value)
}
}

View File

@@ -167,6 +167,61 @@ type: string
assert.Equal(t, expected, actual)
}
func TestIsYNodeTaggedNull(t *testing.T) {
if IsYNodeTaggedNull(nil) {
t.Fatalf("nil cannot be tagged null")
}
if IsYNodeTaggedNull(&Node{}) {
t.Fatalf("untagged node is not tagged")
}
if IsYNodeTaggedNull(&Node{Tag: NodeTagFloat}) {
t.Fatalf("float tagged node is not tagged")
}
if !IsYNodeTaggedNull(&Node{Tag: NodeTagNull}) {
t.Fatalf("tagged node is tagged")
}
}
func TestIsYNodeEmptyMap(t *testing.T) {
if IsYNodeEmptyMap(nil) {
t.Fatalf("nil cannot be a map")
}
if IsYNodeEmptyMap(&Node{}) {
t.Fatalf("raw node is not a map")
}
if IsYNodeEmptyMap(&Node{Kind: SequenceNode}) {
t.Fatalf("seq node is not a map")
}
n := &Node{Kind: MappingNode}
if !IsYNodeEmptyMap(n) {
t.Fatalf("empty mapping node is an empty mapping node")
}
n.Content = append(n.Content, &Node{Kind: SequenceNode})
if IsYNodeEmptyMap(n) {
t.Fatalf("a node with content isn't empty")
}
}
func TestIsYNodeEmptySeq(t *testing.T) {
if IsYNodeEmptySeq(nil) {
t.Fatalf("nil cannot be a map")
}
if IsYNodeEmptySeq(&Node{}) {
t.Fatalf("raw node is not a map")
}
if IsYNodeEmptySeq(&Node{Kind: MappingNode}) {
t.Fatalf("map node is not a sequence")
}
n := &Node{Kind: SequenceNode}
if !IsYNodeEmptySeq(n) {
t.Fatalf("empty sequence node is an empty sequence node")
}
n.Content = append(n.Content, &Node{Kind: MappingNode})
if IsYNodeEmptySeq(n) {
t.Fatalf("a node with content isn't empty")
}
}
func TestIsMissingOrNull(t *testing.T) {
if !IsMissingOrNull(nil) {
t.Fatalf("input: nil")
@@ -180,60 +235,167 @@ func TestIsMissingOrNull(t *testing.T) {
t.Fatalf("input: valid node")
}
// node with NullNodeTag
if !IsMissingOrNull(NullNode()) {
if !IsMissingOrNull(MakeNullNode()) {
t.Fatalf("input: with NullNodeTag")
}
}
func TestIsEmpty(t *testing.T) {
if !IsEmpty(nil) {
t.Fatalf("input: nil")
}
// missing value or null value
if !IsEmpty(NewRNode(nil)) {
t.Fatalf("input: nil value")
}
// not array or map
if IsEmpty(NewScalarRNode("foo")) {
t.Fatalf("input: not array or map")
}
}
func TestIsEmpty_Arrays(t *testing.T) {
node := NewListRNode()
// empty array. empty array is not expected as empty
if IsEmpty(node) {
if IsMissingOrNull(NewListRNode()) {
t.Fatalf("input: empty array")
}
// array with 1 item
node = NewListRNode("foo")
if IsEmpty(node) {
node := NewListRNode("foo")
if IsMissingOrNull(node) {
t.Fatalf("input: array with 1 item")
}
// delete the item in array
node.value.Content = nil
if IsEmpty(node) {
if IsMissingOrNull(node) {
t.Fatalf("input: empty array")
}
}
func TestIsEmpty_Maps(t *testing.T) {
func TestIsEmptyMap(t *testing.T) {
node := NewMapRNode(nil)
// empty map
if !IsEmpty(node) {
if !IsEmptyMap(node) {
t.Fatalf("input: empty map")
}
// map with 1 item
node = NewMapRNode(&map[string]string{
"foo": "bar",
})
if IsEmpty(node) {
if IsEmptyMap(node) {
t.Fatalf("input: map with 1 item")
}
// delete the item in map
node.value.Content = nil
if !IsEmpty(node) {
if !IsEmptyMap(node) {
t.Fatalf("input: empty map")
}
}
func TestIsNil(t *testing.T) {
var rn *RNode
if !rn.IsNil() {
t.Fatalf("uninitialized RNode should be nil")
}
if !NewRNode(nil).IsNil() {
t.Fatalf("missing value YNode should be nil")
}
if MakeNullNode().IsNil() {
t.Fatalf("value tagged null is not nil")
}
if NewMapRNode(nil).IsNil() {
t.Fatalf("empty map not nil")
}
if NewListRNode().IsNil() {
t.Fatalf("empty list not nil")
}
}
func TestIsTaggedNull(t *testing.T) {
var rn *RNode
if rn.IsTaggedNull() {
t.Fatalf("nil RNode cannot be tagged")
}
if NewRNode(nil).IsTaggedNull() {
t.Fatalf("bare RNode should not be tagged")
}
if !MakeNullNode().IsTaggedNull() {
t.Fatalf("a null node is tagged null by definition")
}
if NewMapRNode(nil).IsTaggedNull() {
t.Fatalf("empty map should not be tagged null")
}
if NewListRNode().IsTaggedNull() {
t.Fatalf("empty list should not be tagged null")
}
}
func TestRNodeIsNilOrEmpty(t *testing.T) {
var rn *RNode
if !rn.IsNilOrEmpty() {
t.Fatalf("uninitialized RNode should be empty")
}
if !NewRNode(nil).IsNilOrEmpty() {
t.Fatalf("missing value YNode should be empty")
}
if !MakeNullNode().IsNilOrEmpty() {
t.Fatalf("value tagged null should be empty")
}
if !NewMapRNode(nil).IsNilOrEmpty() {
t.Fatalf("empty map should be empty")
}
if NewMapRNode(&map[string]string{"foo": "bar"}).IsNilOrEmpty() {
t.Fatalf("non-empty map should not be empty")
}
if !NewListRNode().IsNilOrEmpty() {
t.Fatalf("empty list should be empty")
}
if NewListRNode("foo").IsNilOrEmpty() {
t.Fatalf("non-empty list should not be empty")
}
}
func TestMapNodeIsNilOrEmpty(t *testing.T) {
var mn *MapNode
if !mn.IsNilOrEmpty() {
t.Fatalf("nil should be empty")
}
mn = &MapNode{Key: MakeNullNode()}
if !mn.IsNilOrEmpty() {
t.Fatalf("missing value should be empty")
}
mn.Value = NewRNode(nil)
if !mn.IsNilOrEmpty() {
t.Fatalf("missing value YNode should be empty")
}
mn.Value = MakeNullNode()
if !mn.IsNilOrEmpty() {
t.Fatalf("value tagged null should be empty")
}
mn.Value = NewMapRNode(nil)
if !mn.IsNilOrEmpty() {
t.Fatalf("empty map should be empty")
}
mn.Value = NewMapRNode(&map[string]string{"foo": "bar"})
if mn.IsNilOrEmpty() {
t.Fatalf("non-empty map should not be empty")
}
mn.Value = NewListRNode()
if !mn.IsNilOrEmpty() {
t.Fatalf("empty list should be empty")
}
mn.Value = NewListRNode("foo")
if mn.IsNilOrEmpty() {
t.Fatalf("non-empty list should not be empty")
}
}

View File

@@ -51,7 +51,7 @@ func (l *Walker) walkAssociativeSequence() (*yaml.RNode, error) {
if err != nil {
return nil, err
}
if yaml.IsEmpty(val) {
if yaml.IsMissingOrNull(val) || yaml.IsEmptyMap(val) {
_, err = dest.Pipe(yaml.ElementSetter{Key: key, Value: value})
if err != nil {
return nil, err
@@ -74,7 +74,7 @@ func (l *Walker) walkAssociativeSequence() (*yaml.RNode, error) {
}
}
// field is empty
if yaml.IsEmpty(dest) {
if yaml.IsMissingOrNull(dest) {
return nil, nil
}
return dest, nil
@@ -97,7 +97,7 @@ func (l *Walker) walkAssociativeSequence() (*yaml.RNode, error) {
if err != nil {
return nil, err
}
if yaml.IsEmpty(val) {
if yaml.IsMissingOrNull(val) {
_, err = dest.Pipe(yaml.ElementSetter{Key: key, Value: value})
if err != nil {
return nil, err
@@ -121,7 +121,7 @@ func (l *Walker) walkAssociativeSequence() (*yaml.RNode, error) {
}
// field is empty
if yaml.IsEmpty(dest) {
if yaml.IsMissingOrNull(dest) {
return nil, nil
}
return dest, nil

View File

@@ -39,7 +39,7 @@ func (l Walker) walkMap() (*yaml.RNode, error) {
continue
}
field := l.Sources[i].Field(key)
if field == nil || yaml.IsEmpty(field.Key) {
if field == nil || yaml.IsMissingOrNull(field.Key) {
keys = append(keys, nil)
continue
}
@@ -148,7 +148,7 @@ func (l Walker) fieldValue(fieldName string) ([]*yaml.RNode, *openapi.ResourceSc
field := l.Sources[i].Field(fieldName)
f, s := l.valueIfPresent(field)
fields = append(fields, f)
if sch == nil && !s.IsEmpty() {
if sch == nil && !s.IsMissingOrNull() {
sch = s
}
}

Some files were not shown because too many files have changed in this diff Show More