Repository: knative/pkg
Pull request #2354 (2021-11-19 15:31:34)

kmap
- move some functional map operations to it's own package `kmap`
- include a new accessor helper for migrating key names
- use kmap methods in various knative.dev/pkg subpackages
- add deprecation notices


This PR has two purposes:
- It sets up the `configmap.Parse` methods to be moved here 
  - https://github.com/knative/serving/issues/9957
- The accessor struct will help us manage multiple annotation keys 
   - https://github.com/knative/serving/issues/12103

It's important that this package has no external dependencies (especially k8s) to help with queue proxy size issues

knative-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/pkg/pull/2354#" title="Author self-approved">dprotaso</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative%2Fpkg).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/knative/pkg/blob/main/OWNERS)~~ [dprotaso]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
codecov[bot]: # [Codecov](https://codecov.io/gh/knative/pkg/pull/2354?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) Report
> Merging [#2354](https://codecov.io/gh/knative/pkg/pull/2354?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) (d7e21e5) into [main](https://codecov.io/gh/knative/pkg/commit/5708c4c442327025bc26eed03813dc10946d5a50?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) (5708c4c) will **increase** coverage by `0.07%`.
> The diff coverage is `90.90%`.

[![Impacted file tree graph](https://codecov.io/gh/knative/pkg/pull/2354/graphs/tree.svg?width=650&height=150&src=pr&token=rlqpDnfxbO&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative)](https://codecov.io/gh/knative/pkg/pull/2354?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative)

```diff
@@            Coverage Diff             @@
##             main    #2354      +/-   ##
==========================================
+ Coverage   63.55%   63.62%   +0.07%     
==========================================
  Files         227      228       +1     
  Lines        9834     9851      +17     
==========================================
+ Hits         6250     6268      +18     
- Misses       3307     3308       +1     
+ Partials      277      275       -2     
```


| [Impacted Files](https://codecov.io/gh/knative/pkg/pull/2354?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) | Coverage Δ | |
|---|---|---|
| [kmap/accessor.go](https://codecov.io/gh/knative/pkg/pull/2354/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-a21hcC9hY2Nlc3Nvci5nbw==) | `75.00% <75.00%> (ø)` | |
| [apis/duck/v1/status\_types.go](https://codecov.io/gh/knative/pkg/pull/2354/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-YXBpcy9kdWNrL3YxL3N0YXR1c190eXBlcy5nbw==) | `100.00% <100.00%> (ø)` | |
| [apis/duck/v1beta1/status\_types.go](https://codecov.io/gh/knative/pkg/pull/2354/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-YXBpcy9kdWNrL3YxYmV0YTEvc3RhdHVzX3R5cGVzLmdv) | `100.00% <100.00%> (ø)` | |
| [kmap/map.go](https://codecov.io/gh/knative/pkg/pull/2354/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-a21hcC9tYXAuZ28=) | `100.00% <100.00%> (ø)` | |
| [reconciler/retry.go](https://codecov.io/gh/knative/pkg/pull/2354/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-cmVjb25jaWxlci9yZXRyeS5nbw==) | `100.00% <0.00%> (ø)` | |
| [test/gcs/mock/mock.go](https://codecov.io/gh/knative/pkg/pull/2354/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-dGVzdC9nY3MvbW9jay9tb2NrLmdv) | `92.47% <0.00%> (+2.15%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/knative/pkg/pull/2354?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/knative/pkg/pull/2354?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative). Last update [5708c4c...d7e21e5](https://codecov.io/gh/knative/pkg/pull/2354?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative).

knative-prow-robot: @dprotaso: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-knative-pkg-unit-tests | aa874a166bd965d7e2bcea33bc83df2c1e966ff0 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_pkg/2354/pull-knative-pkg-unit-tests/1461732107444293632) | true | `/test pull-knative-pkg-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_pkg/2354). [Your PR dashboard](https://gubernator.knative.dev/pr/dprotaso).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
dprotaso: /hold going to update some godoc

unit test failure is some other flake
dprotaso: /hold cancel
knative-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/pkg/pull/2354#" title="Author self-approved">dprotaso</a>*, *<a href="https://github.com/knative/pkg/pull/2354#pullrequestreview-811463132" title="Approved">mattmoor</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative%2Fpkg).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/knative/pkg/blob/main/OWNERS)~~ [dprotaso,mattmoor]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/apis/duck/v1/status_types.go b/apis/duck/v1/status_types.go
index bf740e065..15e264534 100644
--- a/apis/duck/v1/status_types.go
+++ b/apis/duck/v1/status_types.go
@@ -21,7 +21,7 @@ import (
 
 	"knative.dev/pkg/apis"
 	"knative.dev/pkg/apis/duck/ducktypes"
-	"knative.dev/pkg/kmeta"
+	"knative.dev/pkg/kmap"
 )
 
 // +genduck
@@ -92,8 +92,7 @@ func (s *Status) GetCondition(t apis.ConditionType) *apis.Condition {
 func (s *Status) ConvertTo(ctx context.Context, sink *Status, predicates ...func(apis.ConditionType) bool) {
 	sink.ObservedGeneration = s.ObservedGeneration
 	if s.Annotations != nil {
-		// This will deep copy the map.
-		sink.Annotations = kmeta.UnionMaps(s.Annotations)
+		sink.Annotations = kmap.Union(s.Annotations)
 	}
 
 	conditions := make(apis.Conditions, 0, len(s.Conditions))
diff --git a/apis/duck/v1beta1/status_types.go b/apis/duck/v1beta1/status_types.go
index 722902676..e1ae875eb 100644
--- a/apis/duck/v1beta1/status_types.go
+++ b/apis/duck/v1beta1/status_types.go
@@ -26,7 +26,7 @@ import (
 
 	"knative.dev/pkg/apis"
 	"knative.dev/pkg/apis/duck/ducktypes"
-	"knative.dev/pkg/kmeta"
+	"knative.dev/pkg/kmap"
 )
 
 // +genduck
@@ -109,8 +109,7 @@ func (s *Status) GetCondition(t apis.ConditionType) *apis.Condition {
 func (s *Status) ConvertTo(ctx context.Context, sink *Status) {
 	sink.ObservedGeneration = s.ObservedGeneration
 	if s.Annotations != nil {
-		// This will deep copy the map.
-		sink.Annotations = kmeta.UnionMaps(s.Annotations)
+		sink.Annotations = kmap.Copy(s.Annotations)
 	}
 	for _, c := range s.Conditions {
 		switch c.Type {
diff --git a/kmap/lookup.go b/kmap/lookup.go
new file mode 100644
index 000000000..6066c00b6
--- /dev/null
+++ b/kmap/lookup.go
@@ -0,0 +1,71 @@
+/*
+Copyright 2021 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package kmap
+
+// OrderedLookup is a utility struct for getting values from a map
+// given a list of ordered keys
+//
+// This is to help the migration/renaming of annotations & labels
+type OrderedLookup struct {
+	Keys []string
+}
+
+// Key returns the default key that should be used for
+// accessing the map
+func (a *OrderedLookup) Key() string {
+	return a.Keys[0]
+}
+
+// Value iterates looks up the ordered keys in the map and returns
+// a string value. An empty string will be returned if the keys
+// are not present in the map
+func (a *OrderedLookup) Value(m map[string]string) string {
+	_, v, _ := a.Get(m)
+	return v
+}
+
+// Get iterates over the ordered keys and looks up the corresponding
+// values in the map
+//
+// It returns the key, value, and true|false signaling whether the
+// key was present in the map
+//
+// If no key is present the default key (lowest ordinal) is returned
+// with an empty string as the value
+func (a *OrderedLookup) Get(m map[string]string) (string, string, bool) {
+	var k, v string
+	var ok bool
+	for _, k = range a.Keys {
+		v, ok = m[k]
+		if ok {
+			return k, v, ok
+		}
+	}
+
+	return a.Keys[0], "", false
+}
+
+// NewOrderedLookup builds a utilty struct for looking up N keys
+// in a map in a specific order
+//
+// If no keys are supplied this method will panic
+func NewOrderedLookup(keys ...string) *OrderedLookup {
+	if len(keys) == 0 {
+		panic("expected to have at least a single key")
+	}
+	return &OrderedLookup{Keys: keys}
+}
diff --git a/kmap/map.go b/kmap/map.go
new file mode 100644
index 000000000..98a521ee7
--- /dev/null
+++ b/kmap/map.go
@@ -0,0 +1,75 @@
+/*
+Copyright 2021 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package kmap
+
+// Copy makes a copy of the map.
+func Copy(a map[string]string) map[string]string {
+	ret := make(map[string]string, len(a))
+	for k, v := range a {
+		ret[k] = v
+	}
+	return ret
+}
+
+// Union returns a map constructed from the union of input maps.
+// where values from latter maps win.
+func Union(maps ...map[string]string) map[string]string {
+	if len(maps) == 0 {
+		return map[string]string{}
+	}
+	out := make(map[string]string, len(maps[0]))
+
+	for _, m := range maps {
+		for k, v := range m {
+			out[k] = v
+		}
+	}
+	return out
+}
+
+// Filter creates a copy of the provided map, filtering out the elements
+// that match `filter`.
+// nil `filter` is accepted.
+func Filter(in map[string]string, filter func(string) bool) map[string]string {
+	ret := make(map[string]string, len(in))
+	for k, v := range in {
+		if filter != nil && filter(k) {
+			continue
+		}
+		ret[k] = v
+	}
+	return ret
+}
+
+// ExcludeKeys creates a copy of the provided map filtering out the excluded `keys`
+func ExcludeKeys(in map[string]string, keys ...string) map[string]string {
+	ret := make(map[string]string, len(in))
+
+outer:
+	for k, v := range in {
+		// opted to skip memory allocation (creating a set) in favour of
+		// looping since the places Knative will use this we typically
+		// exclude one or two keys
+		for _, excluded := range keys {
+			if k == excluded {
+				continue outer
+			}
+		}
+		ret[k] = v
+	}
+	return ret
+}
diff --git a/kmeta/map.go b/kmeta/map.go
index 37154cd28..475923320 100644
--- a/kmeta/map.go
+++ b/kmeta/map.go
@@ -16,41 +16,19 @@ limitations under the License.
 
 package kmeta
 
+import "knative.dev/pkg/kmap"
+
 // CopyMap makes a copy of the map.
-func CopyMap(a map[string]string) map[string]string {
-	ret := make(map[string]string, len(a))
-	for k, v := range a {
-		ret[k] = v
-	}
-	return ret
-}
+// Deprecated: use kmap.Copy
+var CopyMap = kmap.Copy
 
 // UnionMaps returns a map constructed from the union of input maps.
 // where values from latter maps win.
-func UnionMaps(maps ...map[string]string) map[string]string {
-	if len(maps) == 0 {
-		return map[string]string{}
-	}
-	out := make(map[string]string, len(maps[0]))
-
-	for _, m := range maps {
-		for k, v := range m {
-			out[k] = v
-		}
-	}
-	return out
-}
+// Deprecated: use kmap.Union
+var UnionMaps = kmap.Union
 
 // FilterMap creates a copy of the provided map, filtering out the elements
 // that match `filter`.
 // nil `filter` is accepted.
-func FilterMap(in map[string]string, filter func(string) bool) map[string]string {
-	ret := make(map[string]string, len(in))
-	for k, v := range in {
-		if filter != nil && filter(k) {
-			continue
-		}
-		ret[k] = v
-	}
-	return ret
-}
+// Deprecated: use kmap.Filter
+var FilterMap = kmap.Filter


Test patch:
diff --git a/kmap/lookup_test.go b/kmap/lookup_test.go
new file mode 100644
index 000000000..8bd24162b
--- /dev/null
+++ b/kmap/lookup_test.go
@@ -0,0 +1,104 @@
+/*
+Copyright 2021 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package kmap
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestNewOrderedLookup(t *testing.T) {
+	keys := []string{"1", "2", "3"}
+	a := NewOrderedLookup(keys...)
+
+	if diff := cmp.Diff(keys, a.Keys); diff != "" {
+		t.Error("NewOrderedLookup unexpected diff (-want, +got):", diff)
+	}
+}
+
+func TestNewOrderedLookup_BadInput(t *testing.T) {
+	defer func() {
+		if r := recover(); r == nil {
+			t.Error("expected no keys to panic")
+		}
+	}()
+	NewOrderedLookup()
+}
+
+func TestOrderedLookup_Get(t *testing.T) {
+	tests := []struct {
+		name     string
+		keys     []string
+		in       map[string]string
+		outKey   string
+		outValue string
+		outOk    bool
+	}{{
+		name:     "single key in map",
+		keys:     []string{"old", "new"},
+		in:       map[string]string{"old": "1"},
+		outKey:   "old",
+		outValue: "1",
+		outOk:    true,
+	}, {
+		name:     "another single key in map",
+		keys:     []string{"old", "new"},
+		in:       map[string]string{"new": "1"},
+		outKey:   "new",
+		outValue: "1",
+		outOk:    true,
+	}, {
+		name:     "lower ordinal takes priority",
+		keys:     []string{"old", "new"},
+		in:       map[string]string{"new": "1", "old": "2"},
+		outKey:   "old",
+		outValue: "2",
+		outOk:    true,
+	}, {
+		name: "missing key in map",
+		keys: []string{"old", "new"},
+		in:   map[string]string{},
+
+		// We still return what key we used to access the values
+		// and we first key since it has priority
+		outKey: "old",
+		outOk:  false,
+	}}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			a := NewOrderedLookup(tc.keys...)
+
+			k, v, ok := a.Get(tc.in)
+
+			if tc.outOk != ok {
+				t.Error("expected ok to be", tc.outOk)
+			}
+
+			if tc.outValue != v {
+				t.Errorf("expected value to be %q got: %q", tc.outValue, v)
+			}
+
+			if tc.outKey != k {
+				t.Errorf("expected key to be %q got: %q", tc.outKey, k)
+			}
+
+		})
+	}
+
+}
diff --git a/kmeta/map_test.go b/kmap/map_test.go
similarity index 73%
rename from kmeta/map_test.go
rename to kmap/map_test.go
index c9ced690f..81be1b80a 100644
--- a/kmeta/map_test.go
+++ b/kmap/map_test.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2020 The Knative Authors
+Copyright 2021 The Knative Authors
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package kmeta
+package kmap
 
 import (
 	"testing"
@@ -70,9 +70,9 @@ func TestUnion(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			got := UnionMaps(test.in...)
+			got := Union(test.in...)
 			if diff := cmp.Diff(test.want, got); diff != "" {
-				t.Error("MakeLabels (-want, +got) =", diff)
+				t.Error("Union (-want, +got) =", diff)
 			}
 		})
 	}
@@ -114,9 +114,9 @@ func TestFilter(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			got := FilterMap(test.in, test.filter)
+			got := Filter(test.in, test.filter)
 			if diff := cmp.Diff(test.want, got); diff != "" {
-				t.Error("MakeAnnotations (-want, +got) =", diff)
+				t.Error("Filter (-want, +got) =", diff)
 			}
 		})
 	}
@@ -142,9 +142,49 @@ func TestCopy(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			got := CopyMap(test.in)
+			got := Copy(test.in)
 			if diff := cmp.Diff(test.want, got); diff != "" {
-				t.Error("MakeAnnotations (-want, +got) =", diff)
+				t.Error("Copy(-want, +got) =", diff)
+			}
+		})
+	}
+}
+
+func TestExcludeKeys(t *testing.T) {
+	tests := []struct {
+		name    string
+		in      map[string]string
+		exclude []string
+		want    map[string]string
+	}{{
+		name: "nil in",
+		want: map[string]string{},
+	}, {
+		name: "no excluded keys",
+		in:   map[string]string{"k": "v"},
+		want: map[string]string{"k": "v"},
+	}, {
+		name:    "exclude single key",
+		in:      map[string]string{"k": "v"},
+		exclude: []string{"k"},
+		want:    map[string]string{},
+	}, {
+		name:    "exclude multiple keys",
+		in:      map[string]string{"k": "v", "k2": "v2", "k3": "v3"},
+		exclude: []string{"k2", "k3"},
+		want:    map[string]string{"k": "v"},
+	}, {
+		name:    "exclude key not present",
+		in:      map[string]string{"k": "v", "k2": "v2", "k3": "v3"},
+		exclude: []string{"key"},
+		want:    map[string]string{"k": "v", "k2": "v2", "k3": "v3"},
+	}}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			got := ExcludeKeys(test.in, test.exclude...)
+			if diff := cmp.Diff(test.want, got); diff != "" {
+				t.Error("ExcludeKeys (-want, +got) =", diff)
 			}
 		})
 	}
diff --git a/leaderelection/config_test.go b/leaderelection/config_test.go
index e3359bf63..2f2ca83a0 100644
--- a/leaderelection/config_test.go
+++ b/leaderelection/config_test.go
@@ -26,7 +26,7 @@ import (
 
 	"github.com/google/go-cmp/cmp"
 	corev1 "k8s.io/api/core/v1"
-	"knative.dev/pkg/kmeta"
+	"knative.dev/pkg/kmap"
 )
 
 const (
@@ -69,7 +69,7 @@ func TestNewConfigMapFromData(t *testing.T) {
 		expected: okConfig(),
 	}, {
 		name: "OK config - controller enabled with multiple buckets",
-		data: kmeta.UnionMaps(okData(), map[string]string{
+		data: kmap.Union(okData(), map[string]string{
 			"buckets": "5",
 		}),
 		expected: func() *Config {
@@ -79,37 +79,37 @@ func TestNewConfigMapFromData(t *testing.T) {
 		}(),
 	}, {
 		name: "invalid lease-duration",
-		data: kmeta.UnionMaps(okData(), map[string]string{
+		data: kmap.Union(okData(), map[string]string{
 			"lease-duration": "flops",
 		}),
 		err: `failed to parse "lease-duration": time: invalid duration`,
 	}, {
 		name: "invalid renew-deadline",
-		data: kmeta.UnionMaps(okData(), map[string]string{
+		data: kmap.Union(okData(), map[string]string{
 			"renew-deadline": "flops",
 		}),
 		err: `failed to parse "renew-deadline": time: invalid duration`,
 	}, {
 		name: "invalid retry-period",
-		data: kmeta.UnionMaps(okData(), map[string]string{
+		data: kmap.Union(okData(), map[string]string{
 			"retry-period": "flops",
 		}),
 		err: `failed to parse "retry-period": time: invalid duration`,
 	}, {
 		name: "invalid buckets - not an int",
-		data: kmeta.UnionMaps(okData(), map[string]string{
+		data: kmap.Union(okData(), map[string]string{
 			"buckets": "not-an-int",
 		}),
 		err: `failed to parse "buckets": strconv.ParseUint: parsing "not-an-int": invalid syntax`,
 	}, {
 		name: "invalid buckets - too small",
-		data: kmeta.UnionMaps(okData(), map[string]string{
+		data: kmap.Union(okData(), map[string]string{
 			"buckets": "0",
 		}),
 		err: fmt.Sprint("buckets: value must be between 1 <= 0 <= ", MaxBuckets),
 	}, {
 		name: "invalid buckets - too large",
-		data: kmeta.UnionMaps(okData(), map[string]string{
+		data: kmap.Union(okData(), map[string]string{
 			"buckets": strconv.Itoa(int(MaxBuckets + 1)),
 		}),
 		err: fmt.Sprintf("buckets: value must be between 1 <= %d <= %d", MaxBuckets+1, MaxBuckets),