diff --git a/kyaml/yaml/example_test.go b/kyaml/yaml/example_test.go index baa8e8ed9..89da82731 100644 --- a/kyaml/yaml/example_test.go +++ b/kyaml/yaml/example_test.go @@ -228,7 +228,7 @@ func ExampleElementMatcher_Filter() { log.Fatal(err) } elem, err := obj.Pipe(ElementMatcher{ - FieldValue: "c", Create: NewScalarRNode("c"), + Values: []string{"c"}, Create: NewScalarRNode("c"), }) if err != nil { log.Fatal(err) @@ -255,7 +255,9 @@ func ExampleElementMatcher_Filter_primitiveFound() { log.Fatal(err) } elem, err := obj.Pipe(ElementMatcher{ - FieldValue: "c", Create: NewScalarRNode("c"), + Keys: []string{""}, + Values: []string{"c"}, + Create: NewScalarRNode("c"), }) if err != nil { log.Fatal(err) @@ -287,7 +289,7 @@ image: nginx log.Fatal(err) } elem, err := obj.Pipe(ElementMatcher{ - FieldName: "name", FieldValue: "baz", Create: append}) + Keys: []string{"name"}, Values: []string{"baz"}, Create: append}) if err != nil { log.Fatal(err) } @@ -321,7 +323,7 @@ image: nginx log.Fatal(err) } elem, err := obj.Pipe(ElementMatcher{ - FieldName: "name", FieldValue: "baz", Create: append}) + Keys: []string{"name"}, Values: []string{"baz"}, Create: append}) if err != nil { log.Fatal(err) } diff --git a/kyaml/yaml/fns.go b/kyaml/yaml/fns.go index 382204050..72e44c6b6 100644 --- a/kyaml/yaml/fns.go +++ b/kyaml/yaml/fns.go @@ -65,6 +65,7 @@ type ElementSetter struct { func (e ElementSetter) Filter(rn *RNode) (*RNode, error) { return rn.Pipe(ElementSetterList{ + Kind: e.Kind, Element: e.Element, Keys: []string{e.Key}, Values: []string{e.Value}, @@ -105,6 +106,13 @@ func (e ElementSetterList) isMappingSetter() bool { // the main difference between this Filter and the ElementSetter Filter // is that here we must iterate through all the key-value pairs func (e ElementSetterList) Filter(rn *RNode) (*RNode, error) { + if len(e.Keys) == 0 { + e.Keys = append(e.Keys, "") + } + if len(e.Values) == 0 { + e.Values = append(e.Values, "") + } + if err := ErrorIfInvalid(rn, SequenceNode); err != nil { return nil, err } @@ -126,15 +134,20 @@ func (e ElementSetterList) Filter(rn *RNode) (*RNode, error) { continue } - // make sure keys and values are the same length - if len(e.Keys) != len(e.Values) { - return nil, fmt.Errorf("length of keys and values must be the same") + if len(e.Keys) > len(e.Values) { + return nil, fmt.Errorf("cannot have more keys than values") } // check if this is the element we are matching + var val *RNode + var err error found := true for j := range e.Keys { - val, err := newNode.Pipe(FieldMatcher{Name: e.Keys[j], StringValue: e.Values[j]}) + if j >= len(e.Values) { + val, err = newNode.Pipe(Get(e.Keys[j])) + } else { + val, err = newNode.Pipe(FieldMatcher{Name: e.Keys[j], StringValue: e.Values[j]}) + } if err != nil { return nil, err } @@ -254,57 +267,24 @@ func (c FieldClearer) Filter(rn *RNode) (*RNode, error) { } func MatchElement(field, value string) ElementMatcher { - return ElementMatcher{FieldName: field, FieldValue: value} + return ElementMatcher{Keys: []string{field}, Values: []string{value}} +} + +func MatchElementList(keys []string, values []string) ElementMatcher { + return ElementMatcher{Keys: keys, Values: values} } func GetElementByKey(key string) ElementMatcher { - return ElementMatcher{FieldName: key, MatchAnyValue: true} + return ElementMatcher{Keys: []string{key}, MatchAnyValue: true} } // ElementMatcher returns the first element from a Sequence matching the -// specified field's value. If there's no match, and no configuration error, +// specified key-value pairs. If there's no match, and no configuration error, // the matcher returns nil, nil. + type ElementMatcher struct { Kind string `yaml:"kind,omitempty"` - // FieldName will attempt to match this field in each list element. - // Optional. Leave empty for lists of primitives (ScalarNode). - FieldName string `yaml:"name,omitempty"` - - // FieldValue will attempt to match each element field to this value. - // For lists of primitives, this will be used to match the primitive value. - FieldValue string `yaml:"value,omitempty"` - - // Create will create the Element if it is not found - Create *RNode `yaml:"create,omitempty"` - - // MatchAnyValue indicates that matcher should only consider the key and ignore - // the actual value in the list. FieldValue must be empty when MatchAnyValue is - // set to true. - MatchAnyValue bool `yaml:"noValue,omitempty"` -} - -func (e ElementMatcher) Filter(rn *RNode) (*RNode, error) { - return rn.Pipe(ElementMatcherList{ - Kind: e.Kind, - Keys: []string{e.FieldName}, - Values: []string{e.FieldValue}, - Create: e.Create, - MatchAnyValue: e.MatchAnyValue, - }) -} - -func MatchElementList(keys []string, values []string) ElementMatcherList { - return ElementMatcherList{Keys: keys, Values: values} -} - -// ElementMatcherList returns the first element from a Sequence matching all -// specified key, value pairs (as opposed to ElementMatcher, which matches -// a single key, value pair). If there's no match, and no configuration error, -// the matcher returns nil, nil. -type ElementMatcherList struct { - Kind string `yaml:"kind,omitempty"` - // Keys are the list of fields upon which to match this element. Keys []string @@ -320,7 +300,7 @@ type ElementMatcherList struct { MatchAnyValue bool `yaml:"noValue,omitempty"` } -func (e ElementMatcherList) Filter(rn *RNode) (*RNode, error) { +func (e ElementMatcher) Filter(rn *RNode) (*RNode, error) { if err := ErrorIfInvalid(rn, yaml.SequenceNode); err != nil { return nil, err } @@ -595,7 +575,7 @@ func (l PathGetter) elemFilter(part string) (Filter, error) { }) } // Append the Node - return ElementMatcher{FieldName: name, FieldValue: value, Create: elem}, nil + return ElementMatcher{Keys: []string{name}, Values: []string{value}, Create: elem}, nil } func (l PathGetter) fieldFilter( diff --git a/kyaml/yaml/fns_test.go b/kyaml/yaml/fns_test.go index 30bc8f794..febdd9923 100644 --- a/kyaml/yaml/fns_test.go +++ b/kyaml/yaml/fns_test.go @@ -109,7 +109,7 @@ func TestElementSetter(t *testing.T) { // ElementSetter will update node, so make a copy node := orig.Copy() // Remove an element, because ElementSetter.Element is left nil. - rn, err := node.Pipe(ElementSetter{Key: "a", Value: "b"}) + rn, err := node.Pipe(ElementSetterList{Keys: []string{"a"}, Values: []string{"b"}}) assert.NoError(t, err) assert.Nil(t, rn) assert.Equal(t, `- scalarValue @@ -118,7 +118,7 @@ func TestElementSetter(t *testing.T) { node = orig.Copy() // Nothing happens because no element is matched - rn, err = node.Pipe(ElementSetter{Key: "a", Value: "zebra"}) + rn, err = node.Pipe(ElementSetterList{Keys: []string{"a"}, Values: []string{"zebra"}}) assert.NoError(t, err) assert.Nil(t, rn) assert.Equal(t, `- a: b @@ -129,7 +129,7 @@ func TestElementSetter(t *testing.T) { node = orig.Copy() // Return error because ElementSetter doesn't support a single key // when there is a scalar value in the list - _, err = node.Pipe(ElementSetter{Key: "a"}) + _, err = node.Pipe(ElementSetterList{Keys: []string{"a"}}) assert.EqualError(t, err, "wrong Node Kind for expected: MappingNode was ScalarNode: value: {scalarValue}") node = MustParse(` @@ -138,7 +138,7 @@ func TestElementSetter(t *testing.T) { `) // {a: b} is removed since the value is omitted and only key is used // to match and no Element specified. - rn, err = node.Pipe(ElementSetter{Key: "a"}) + rn, err = node.Pipe(ElementSetterList{Keys: []string{"a"}}) assert.NoError(t, err) assert.Nil(t, rn) assert.Equal(t, `- c: d @@ -150,7 +150,7 @@ func TestElementSetter(t *testing.T) { `) // Return error because ElementSetter will assume all elements are scalar when // there is only value provided. - _, err = node.Pipe(ElementSetter{Value: "b"}) + _, err = node.Pipe(ElementSetterList{Values: []string{"b"}}) assert.EqualError(t, err, "wrong Node Kind for expected: ScalarNode was MappingNode: value: {a: b}") node = MustParse(` @@ -159,7 +159,7 @@ func TestElementSetter(t *testing.T) { `) // b is removed since ElementSetter use the value "b" to match the // scalar values. - rn, err = node.Pipe(ElementSetter{Value: "b"}) + rn, err = node.Pipe(ElementSetterList{Values: []string{"b"}}) assert.NoError(t, err) assert.Nil(t, rn) assert.Equal(t, `- a @@ -170,9 +170,9 @@ func TestElementSetter(t *testing.T) { newElement := NewMapRNode(&map[string]string{ "e": "f", }) - rn, err = node.Pipe(ElementSetter{ - Key: "a", - Value: "b", + rn, err = node.Pipe(ElementSetterList{ + Keys: []string{"a"}, + Values: []string{"b"}, Element: newElement.YNode(), }) assert.NoError(t, err) @@ -185,9 +185,9 @@ func TestElementSetter(t *testing.T) { node = orig.Copy() // Set an element with scalar, {"a": "b"} to "foo" newElement = NewScalarRNode("foo") - rn, err = node.Pipe(ElementSetter{ - Key: "a", - Value: "b", + rn, err = node.Pipe(ElementSetterList{ + Keys: []string{"a"}, + Values: []string{"b"}, Element: newElement.YNode(), }) assert.NoError(t, err) @@ -203,9 +203,9 @@ func TestElementSetter(t *testing.T) { newElement = NewMapRNode(&map[string]string{ "e": "f", }) - rn, err = node.Pipe(ElementSetter{ - Key: "x", - Value: "y", + rn, err = node.Pipe(ElementSetterList{ + Keys: []string{"x"}, + Values: []string{"y"}, Element: newElement.YNode(), }) assert.NoError(t, err) @@ -217,7 +217,7 @@ func TestElementSetter(t *testing.T) { `, assertNoErrorString(t)(node.String())) } -func TestElementSetterList(t *testing.T) { +func TestElementSetterMultipleKeys(t *testing.T) { orig := MustParse(` - a: b c: d @@ -365,23 +365,23 @@ func TestElementMatcherWithNoValue(t *testing.T) { `) assert.NoError(t, err) - rn, err := node.Pipe(ElementMatcher{FieldName: "b"}) + rn, err := node.Pipe(ElementMatcher{Keys: []string{"b"}}) assert.NoError(t, err) assert.Equal(t, "b: \"\"\n", assertNoErrorString(t)(rn.String())) - rn, err = node.Pipe(ElementMatcher{FieldName: "a"}) - assert.NoError(t, err) + rn, err = node.Pipe(ElementMatcher{Keys: []string{"a"}}) + assert.Error(t, err) assert.Nil(t, rn) - rn, err = node.Pipe(ElementMatcher{FieldName: "a", MatchAnyValue: true}) + rn, err = node.Pipe(ElementMatcher{Keys: []string{"a"}, MatchAnyValue: true}) assert.NoError(t, err) assert.Equal(t, "a: c\n", assertNoErrorString(t)(rn.String())) - _, err = node.Pipe(ElementMatcher{FieldName: "a", FieldValue: "c", MatchAnyValue: true}) + _, err = node.Pipe(ElementMatcher{Keys: []string{"a"}, Values: []string{"c"}, MatchAnyValue: true}) assert.Errorf(t, err, "FieldValue must be empty when NoValue is set to true") } -func TestElementMatcherList(t *testing.T) { +func TestElementMatcherMultipleKeys(t *testing.T) { node, err := Parse(` - a: b c: d