Use resid.ResId in replacements.

This commit is contained in:
monopole
2021-04-22 20:41:18 -07:00
parent d5d44ce3fe
commit c828b1e49a
10 changed files with 195 additions and 84 deletions

View File

@@ -43,8 +43,8 @@ func applyReplacement(nodes []*yaml.RNode, value *yaml.RNode, targets []*types.T
t.FieldPaths = []string{types.DefaultReplacementFieldPath} t.FieldPaths = []string{types.DefaultReplacementFieldPath}
} }
for _, n := range nodes { for _, n := range nodes {
nodeId := getKrmId(n) id := makeResId(n)
if t.Select.KrmId.Match(nodeId) && !rejectId(t.Reject, nodeId) { if id.IsSelectedBy(t.Select.ResId) && !rejectId(t.Reject, id) {
err := applyToNode(n, value, t) err := applyToNode(n, value, t)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -55,9 +55,9 @@ func applyReplacement(nodes []*yaml.RNode, value *yaml.RNode, targets []*types.T
return nodes, nil return nodes, nil
} }
func rejectId(rejects []*types.Selector, nodeId *types.KrmId) bool { func rejectId(rejects []*types.Selector, id *resid.ResId) bool {
for _, r := range rejects { for _, r := range rejects {
if r.KrmId.Match(nodeId) { if id.IsSelectedBy(r.ResId) {
return true return true
} }
} }
@@ -152,32 +152,33 @@ func getRefinedValue(options *types.FieldOptions, rn *yaml.RNode) (*yaml.RNode,
func selectSourceNode(nodes []*yaml.RNode, selector *types.SourceSelector) (*yaml.RNode, error) { func selectSourceNode(nodes []*yaml.RNode, selector *types.SourceSelector) (*yaml.RNode, error) {
var matches []*yaml.RNode var matches []*yaml.RNode
for _, n := range nodes { for _, n := range nodes {
if selector.KrmId.Match(getKrmId(n)) { if makeResId(n).IsSelectedBy(selector.ResId) {
if len(matches) > 0 { if len(matches) > 0 {
return nil, fmt.Errorf("more than one match for source %v", selector) return nil, fmt.Errorf(
"multiple matches for selector %s", selector)
} }
matches = append(matches, n) matches = append(matches, n)
} }
} }
if len(matches) == 0 { if len(matches) == 0 {
return nil, fmt.Errorf("found no matches for source %v", selector) return nil, fmt.Errorf("nothing selected by %s", selector)
} }
return matches[0], nil return matches[0], nil
} }
func getKrmId(n *yaml.RNode) *types.KrmId { // makeResId makes a ResId from an RNode.
func makeResId(n *yaml.RNode) *resid.ResId {
ns, err := n.GetNamespace() ns, err := n.GetNamespace()
if err != nil { if err != nil {
// Resource has no metadata (no apiVersion, kind, nor metadata field). // Resource has no metadata (no apiVersion, kind, nor metadata field).
// In this case, it cannot be selected. return nil
return &types.KrmId{}
} }
apiVersion := n.Field(yaml.APIVersionField) apiVersion := n.Field(yaml.APIVersionField)
var group, version string var group, version string
if apiVersion != nil { if apiVersion != nil {
group, version = resid.ParseGroupVersion(yaml.GetValue(apiVersion.Value)) group, version = resid.ParseGroupVersion(yaml.GetValue(apiVersion.Value))
} }
return &types.KrmId{ return &resid.ResId{
Gvk: resid.Gvk{Group: group, Version: version, Kind: n.GetKind()}, Gvk: resid.Gvk{Group: group, Version: version, Kind: n.GetKind()},
Name: n.GetName(), Name: n.GetName(),
Namespace: ns, Namespace: ns,

View File

@@ -269,7 +269,7 @@ spec:
- select: - select:
kind: Deployment kind: Deployment
`, `,
expectedErr: "more than one match for source ~G_~V_Deployment", expectedErr: "multiple matches for selector ~G_~V_Deployment|~X|~N",
}, },
"replacement has no source": { "replacement has no source": {
input: `apiVersion: v1 input: `apiVersion: v1

View File

@@ -68,8 +68,7 @@ resources:
replacements: replacements:
- path: replacement.yaml - path: replacement.yaml
`) `)
th.WriteF("replacement.yaml", th.WriteF("replacement.yaml", `
`
source: source:
kind: Deployment kind: Deployment
fieldPath: spec.template.spec.containers.0.image fieldPath: spec.template.spec.containers.0.image
@@ -115,8 +114,7 @@ func TestReplacementTransformerWithDiamondShape(t *testing.T) {
th := kusttest_test.MakeEnhancedHarness(t) th := kusttest_test.MakeEnhancedHarness(t)
defer th.Reset() defer th.Reset()
th.WriteF("base/deployment.yaml", th.WriteF("base/deployments.yaml", `
`
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
@@ -150,21 +148,18 @@ spec:
- image: nginx:newtagB - image: nginx:newtagB
name: nginx name: nginx
`) `)
th.WriteF("base/kustomization.yaml", th.WriteK("base", `
`
resources: resources:
- deployment.yaml - deployments.yaml
`) `)
th.WriteF("a/kustomization.yaml", th.WriteK("a", `
`
namePrefix: a- namePrefix: a-
resources: resources:
- ../base - ../base
replacements: replacements:
- path: replacement.yaml - path: replacement.yaml
`) `)
th.WriteF("a/replacement.yaml", th.WriteF("a/replacement.yaml", `
`
source: source:
name: a-sourceA name: a-sourceA
fieldPath: spec.template.spec.containers.0.image fieldPath: spec.template.spec.containers.0.image
@@ -174,16 +169,14 @@ targets:
fieldPaths: fieldPaths:
- spec.template.spec.containers.[name=nginx].image - spec.template.spec.containers.[name=nginx].image
`) `)
th.WriteF("b/kustomization.yaml", th.WriteK("b", `
`
namePrefix: b- namePrefix: b-
resources: resources:
- ../base - ../base
replacements: replacements:
- path: replacement.yaml - path: replacement.yaml
`) `)
th.WriteF("b/replacement.yaml", th.WriteF("b/replacement.yaml", `
`
source: source:
name: b-sourceB name: b-sourceB
fieldPath: spec.template.spec.containers.0.image fieldPath: spec.template.spec.containers.0.image
@@ -193,14 +186,13 @@ targets:
fieldPaths: fieldPaths:
- spec.template.spec.containers.[name=nginx].image - spec.template.spec.containers.[name=nginx].image
`) `)
th.WriteF("combined/kustomization.yaml", th.WriteK(".", `
`
resources: resources:
- ../a - a
- ../b - b
`) `)
m := th.Run("combined", th.MakeDefaultOptions()) m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, ` th.AssertActualEqualsExpected(m, `
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment

View File

@@ -90,6 +90,13 @@ func (id ResId) GvknEquals(o ResId) bool {
return id.Name == o.Name && id.Gvk.Equals(o.Gvk) return id.Name == o.Name && id.Gvk.Equals(o.Gvk)
} }
// IsSelectedBy returns true if self is selected by the argument.
func (id ResId) IsSelectedBy(selector ResId) bool {
return (selector.Name == "" || selector.Name == id.Name) &&
(selector.Namespace == "" || selector.IsNsEquals(id)) &&
id.Gvk.IsSelected(&selector.Gvk)
}
// Equals returns true if the other id matches // Equals returns true if the other id matches
// namespace/Group/Version/Kind/name. // namespace/Group/Version/Kind/name.
func (id ResId) Equals(o ResId) bool { func (id ResId) Equals(o ResId) bool {

View File

@@ -360,6 +360,100 @@ var ids = []ResId{
}, },
} }
func TestResIdIsSelected(t *testing.T) {
type selectable struct {
id ResId
expectSelected bool
}
var testCases = []struct {
selector ResId
selectables []selectable
}{
{
selector: ResId{
Namespace: "X",
Name: "nm",
Gvk: Gvk{Group: "g", Version: "v", Kind: "k"},
},
selectables: []selectable{
{
id: ResId{
Namespace: "X",
Name: "nm",
Gvk: Gvk{Group: "g", Version: "v", Kind: "k"},
},
expectSelected: true,
},
{
id: ResId{
Namespace: "x",
Name: "nm",
Gvk: Gvk{Group: "g", Version: "v", Kind: "k"},
},
expectSelected: false,
},
{
id: ResId{
Name: "nm",
Gvk: Gvk{Group: "g", Version: "v", Kind: "k"},
},
expectSelected: false,
},
},
},
{
selector: ResId{
/* Namespace wildcard */
Name: "nm",
Gvk: Gvk{Group: "g" /* Version wildcard */, Kind: "k"},
},
selectables: []selectable{
{
id: ResId{
Namespace: "X",
Name: "nm",
Gvk: Gvk{Group: "g", Version: "v", Kind: "k"},
},
expectSelected: true,
},
{
id: ResId{
Namespace: "x",
Name: "nm",
Gvk: Gvk{Group: "g", Version: "v", Kind: "k"},
},
expectSelected: true,
},
{
id: ResId{
Name: "nm",
Gvk: Gvk{Group: "g", Version: "VVV", Kind: "k"},
},
expectSelected: true,
},
},
},
}
for _, tst := range testCases {
for _, pair := range tst.selectables {
if pair.id.IsSelectedBy(tst.selector) {
if !pair.expectSelected {
t.Fatalf(
"expected id %s to NOT be selected by %s",
pair.id, tst.selector)
}
} else {
if pair.expectSelected {
t.Fatalf(
"expected id %s to be selected by %s",
pair.id, tst.selector)
}
}
}
}
}
func TestFromString(t *testing.T) { func TestFromString(t *testing.T) {
for _, id := range ids { for _, id := range ids {
newId := FromString(id.String()) newId := FromString(id.String())

View File

@@ -61,13 +61,13 @@ func TestFindPatchTargets(t *testing.T) {
}{ }{
"select_01": { "select_01": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{Name: "name.*"}, ResId: resid.ResId{Name: "name.*"},
}, },
count: 3, count: 3,
}, },
"select_02": { "select_02": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{Name: "name.*"}, ResId: resid.ResId{Name: "name.*"},
AnnotationSelector: "foo=bar", AnnotationSelector: "foo=bar",
}, },
count: 2, count: 2,
@@ -80,7 +80,7 @@ func TestFindPatchTargets(t *testing.T) {
}, },
"select_04": { "select_04": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{ ResId: resid.ResId{
Gvk: resid.Gvk{ Gvk: resid.Gvk{
Kind: "Kind1", Kind: "Kind1",
}, },
@@ -91,31 +91,31 @@ func TestFindPatchTargets(t *testing.T) {
}, },
"select_05": { "select_05": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{Name: "NotMatched"}, ResId: resid.ResId{Name: "NotMatched"},
}, },
count: 0, count: 0,
}, },
"select_06": { "select_06": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{Name: ""}, ResId: resid.ResId{Name: ""},
}, },
count: 4, count: 4,
}, },
"select_07": { "select_07": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{Namespace: "default"}, ResId: resid.ResId{Namespace: "default"},
}, },
count: 2, count: 2,
}, },
"select_08": { "select_08": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{Namespace: ""}, ResId: resid.ResId{Namespace: ""},
}, },
count: 4, count: 4,
}, },
"select_09": { "select_09": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{ ResId: resid.ResId{
Namespace: "default", Namespace: "default",
Name: "name.*", Name: "name.*",
Gvk: resid.Gvk{ Gvk: resid.Gvk{
@@ -127,55 +127,55 @@ func TestFindPatchTargets(t *testing.T) {
}, },
"select_10": { "select_10": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{Name: "^name.*"}, ResId: resid.ResId{Name: "^name.*"},
}, },
count: 3, count: 3,
}, },
"select_11": { "select_11": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{Name: "name.*$"}, ResId: resid.ResId{Name: "name.*$"},
}, },
count: 3, count: 3,
}, },
"select_12": { "select_12": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{Name: "^name.*$"}, ResId: resid.ResId{Name: "^name.*$"},
}, },
count: 3, count: 3,
}, },
"select_13": { "select_13": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{Namespace: "^def.*"}, ResId: resid.ResId{Namespace: "^def.*"},
}, },
count: 2, count: 2,
}, },
"select_14": { "select_14": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{Namespace: "def.*$"}, ResId: resid.ResId{Namespace: "def.*$"},
}, },
count: 2, count: 2,
}, },
"select_15": { "select_15": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{Namespace: "^def.*$"}, ResId: resid.ResId{Namespace: "^def.*$"},
}, },
count: 2, count: 2,
}, },
"select_16": { "select_16": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{Namespace: "default"}, ResId: resid.ResId{Namespace: "default"},
}, },
count: 2, count: 2,
}, },
"select_17": { "select_17": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{Namespace: "NotMatched"}, ResId: resid.ResId{Namespace: "NotMatched"},
}, },
count: 0, count: 0,
}, },
"select_18": { "select_18": {
target: types.Selector{ target: types.Selector{
KrmId: types.KrmId{Namespace: "ns1"}, ResId: resid.ResId{Namespace: "ns1"},
}, },
count: 1, count: 1,
}, },

View File

@@ -9,7 +9,7 @@ import (
func TestPatchEquals(t *testing.T) { func TestPatchEquals(t *testing.T) {
selector := Selector{ selector := Selector{
KrmId: KrmId{ ResId: resid.ResId{
Gvk: resid.Gvk{ Gvk: resid.Gvk{
Group: "group", Group: "group",
Version: "version", Version: "version",
@@ -40,7 +40,7 @@ func TestPatchEquals(t *testing.T) {
Path: "foo", Path: "foo",
Patch: "bar", Patch: "bar",
Target: &Selector{ Target: &Selector{
KrmId: KrmId{ ResId: resid.ResId{
Gvk: resid.Gvk{ Gvk: resid.Gvk{
Group: "group", Group: "group",
Version: "version", Version: "version",
@@ -57,7 +57,7 @@ func TestPatchEquals(t *testing.T) {
Path: "foo", Path: "foo",
Patch: "bar", Patch: "bar",
Target: &Selector{ Target: &Selector{
KrmId: KrmId{ ResId: resid.ResId{
Gvk: resid.Gvk{ Gvk: resid.Gvk{
Group: "group", Group: "group",
Version: "version", Version: "version",

View File

@@ -3,6 +3,13 @@
package types package types
import (
"fmt"
"strings"
"sigs.k8s.io/kustomize/api/resid"
)
const DefaultReplacementFieldPath = "metadata.name" const DefaultReplacementFieldPath = "metadata.name"
// Replacement defines how to perform a substitution // Replacement defines how to perform a substitution
@@ -18,7 +25,7 @@ type Replacement struct {
// SourceSelector is the source of the replacement transformer. // SourceSelector is the source of the replacement transformer.
type SourceSelector struct { type SourceSelector struct {
// A specific object to read it from. // A specific object to read it from.
KrmId `json:",inline,omitempty" yaml:",inline,omitempty"` resid.ResId `json:",inline,omitempty" yaml:",inline,omitempty"`
// Structured field path expected in the allowed object. // Structured field path expected in the allowed object.
FieldPath string `json:"fieldPath" yaml:"fieldPath"` FieldPath string `json:"fieldPath" yaml:"fieldPath"`
@@ -27,6 +34,20 @@ type SourceSelector struct {
Options *FieldOptions `json:"options" yaml:"options"` Options *FieldOptions `json:"options" yaml:"options"`
} }
func (s *SourceSelector) String() string {
if s == nil {
return ""
}
result := []string{s.ResId.String()}
if s.FieldPath != "" {
result = append(result, s.FieldPath)
}
if opts := s.Options.String(); opts != "" {
result = append(result, opts)
}
return strings.Join(result, ":")
}
// TargetSelector specifies fields in one or more objects. // TargetSelector specifies fields in one or more objects.
type TargetSelector struct { type TargetSelector struct {
// Include objects that match this. // Include objects that match this.
@@ -57,3 +78,10 @@ type FieldOptions struct {
// If field missing, add it. // If field missing, add it.
Create bool `json:"create" yaml:"create"` Create bool `json:"create" yaml:"create"`
} }
func (fo *FieldOptions) String() string {
if fo == nil || fo.Delimiter == "" {
return ""
}
return fmt.Sprintf("%s(%d)", fo.Delimiter, fo.Index)
}

View File

@@ -4,6 +4,7 @@
package types package types
import ( import (
"fmt"
"regexp" "regexp"
"sigs.k8s.io/kustomize/api/resid" "sigs.k8s.io/kustomize/api/resid"
@@ -13,8 +14,8 @@ import (
// Any resource that matches intersection of all conditions // Any resource that matches intersection of all conditions
// is included in this set. // is included in this set.
type Selector struct { type Selector struct {
// KrmId refers to a GVKN/Ns of a resource. // ResId refers to a GVKN/Ns of a resource.
KrmId `json:",inline,omitempty" yaml:",inline,omitempty"` resid.ResId `json:",inline,omitempty" yaml:",inline,omitempty"`
// AnnotationSelector is a string that follows the label selection expression // AnnotationSelector is a string that follows the label selection expression
// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api // https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api
@@ -27,21 +28,9 @@ type Selector struct {
LabelSelector string `json:"labelSelector,omitempty" yaml:"labelSelector,omitempty"` LabelSelector string `json:"labelSelector,omitempty" yaml:"labelSelector,omitempty"`
} }
// KrmId refers to a GVKN/Ns of a resource. func (s *Selector) String() string {
type KrmId struct { return fmt.Sprintf(
resid.Gvk `json:",inline,omitempty" yaml:",inline,omitempty"` "%s:a=%s:l=%s", s.ResId, s.AnnotationSelector, s.LabelSelector)
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
}
// Match returns true if id selects other, i.e. id's fields
// either match other's or are empty
func (id *KrmId) Match(other *KrmId) bool {
return (id.Group == "" || id.Group == other.Group) &&
(id.Version == "" || id.Version == other.Version) &&
(id.Kind == "" || id.Kind == other.Kind) &&
(id.Name == "" || id.Name == other.Name) &&
(id.Namespace == "" || id.Namespace == other.Namespace)
} }
// SelectorRegex is a Selector with regex in GVK // SelectorRegex is a Selector with regex in GVK

View File

@@ -15,7 +15,7 @@ func TestSelectorRegexMatchGvk(t *testing.T) {
}{ }{
{ {
S: Selector{ S: Selector{
KrmId: KrmId{ ResId: resid.ResId{
Gvk: resid.Gvk{ Gvk: resid.Gvk{
Group: "group", Group: "group",
Version: "version", Version: "version",
@@ -32,7 +32,7 @@ func TestSelectorRegexMatchGvk(t *testing.T) {
}, },
{ {
S: Selector{ S: Selector{
KrmId: KrmId{ ResId: resid.ResId{
Gvk: resid.Gvk{ Gvk: resid.Gvk{
Group: "group", Group: "group",
Version: "", Version: "",
@@ -49,7 +49,7 @@ func TestSelectorRegexMatchGvk(t *testing.T) {
}, },
{ {
S: Selector{ S: Selector{
KrmId: KrmId{ ResId: resid.ResId{
Gvk: resid.Gvk{ Gvk: resid.Gvk{
Group: "group", Group: "group",
Version: "version", Version: "version",
@@ -66,7 +66,7 @@ func TestSelectorRegexMatchGvk(t *testing.T) {
}, },
{ {
S: Selector{ S: Selector{
KrmId: KrmId{ ResId: resid.ResId{
Gvk: resid.Gvk{ Gvk: resid.Gvk{
Group: "group", Group: "group",
Version: "version", Version: "version",
@@ -83,7 +83,7 @@ func TestSelectorRegexMatchGvk(t *testing.T) {
}, },
{ {
S: Selector{ S: Selector{
KrmId: KrmId{ ResId: resid.ResId{
Gvk: resid.Gvk{ Gvk: resid.Gvk{
Group: "g.*", Group: "g.*",
Version: "\\d+", Version: "\\d+",
@@ -100,7 +100,7 @@ func TestSelectorRegexMatchGvk(t *testing.T) {
}, },
{ {
S: Selector{ S: Selector{
KrmId: KrmId{ ResId: resid.ResId{
Gvk: resid.Gvk{ Gvk: resid.Gvk{
Group: "g.*", Group: "g.*",
Version: "\\d+", Version: "\\d+",
@@ -137,7 +137,7 @@ func TestSelectorRegexMatchName(t *testing.T) {
}{ }{
{ {
S: Selector{ S: Selector{
KrmId: KrmId{ ResId: resid.ResId{
Name: "foo", Name: "foo",
Namespace: "bar", Namespace: "bar",
}, },
@@ -147,7 +147,7 @@ func TestSelectorRegexMatchName(t *testing.T) {
}, },
{ {
S: Selector{ S: Selector{
KrmId: KrmId{ ResId: resid.ResId{
Name: "foo", Name: "foo",
Namespace: "bar", Namespace: "bar",
}, },
@@ -157,7 +157,7 @@ func TestSelectorRegexMatchName(t *testing.T) {
}, },
{ {
S: Selector{ S: Selector{
KrmId: KrmId{ ResId: resid.ResId{
Name: "f.*", Name: "f.*",
}, },
}, },
@@ -166,7 +166,7 @@ func TestSelectorRegexMatchName(t *testing.T) {
}, },
{ {
S: Selector{ S: Selector{
KrmId: KrmId{ ResId: resid.ResId{
Name: "b.*", Name: "b.*",
}, },
}, },
@@ -194,7 +194,7 @@ func TestSelectorRegexMatchNamespace(t *testing.T) {
}{ }{
{ {
S: Selector{ S: Selector{
KrmId: KrmId{ ResId: resid.ResId{
Name: "bar", Name: "bar",
Namespace: "foo", Namespace: "foo",
}, },
@@ -204,7 +204,7 @@ func TestSelectorRegexMatchNamespace(t *testing.T) {
}, },
{ {
S: Selector{ S: Selector{
KrmId: KrmId{ ResId: resid.ResId{
Name: "foo", Name: "foo",
Namespace: "bar", Namespace: "bar",
}, },
@@ -214,7 +214,7 @@ func TestSelectorRegexMatchNamespace(t *testing.T) {
}, },
{ {
S: Selector{ S: Selector{
KrmId: KrmId{ ResId: resid.ResId{
Namespace: "f.*", Namespace: "f.*",
}, },
}, },
@@ -223,7 +223,7 @@ func TestSelectorRegexMatchNamespace(t *testing.T) {
}, },
{ {
S: Selector{ S: Selector{
KrmId: KrmId{ ResId: resid.ResId{
Namespace: "b.*", Namespace: "b.*",
}, },
}, },