Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kmap #2354

Merged
merged 6 commits into from
Nov 19, 2021
Merged

kmap #2354

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions apis/duck/v1/status_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (

"knative.dev/pkg/apis"
"knative.dev/pkg/apis/duck/ducktypes"
"knative.dev/pkg/kmeta"
"knative.dev/pkg/kmap"
)

// +genduck
Expand Down Expand Up @@ -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))
Expand Down
5 changes: 2 additions & 3 deletions apis/duck/v1beta1/status_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (

"knative.dev/pkg/apis"
"knative.dev/pkg/apis/duck/ducktypes"
"knative.dev/pkg/kmeta"
"knative.dev/pkg/kmap"
)

// +genduck
Expand Down Expand Up @@ -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 {
Expand Down
71 changes: 71 additions & 0 deletions kmap/lookup.go
Original file line number Diff line number Diff line change
@@ -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}
}
104 changes: 104 additions & 0 deletions kmap/lookup_test.go
Original file line number Diff line number Diff line change
@@ -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)
}

})
}

}
75 changes: 75 additions & 0 deletions kmap/map.go
Original file line number Diff line number Diff line change
@@ -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
}
56 changes: 48 additions & 8 deletions kmeta/map_test.go → kmap/map_test.go
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package kmeta
package kmap

import (
"testing"
Expand Down Expand Up @@ -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)
}
})
}
Expand Down Expand Up @@ -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)
}
})
}
Expand All @@ -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)
}
})
}
Expand Down
Loading