Skip to content

Commit

Permalink
add annotation wildcard support
Browse files Browse the repository at this point in the history
  • Loading branch information
JimBugwadia committed Dec 2, 2020
1 parent 76b6974 commit 59ba4fe
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 16 deletions.
8 changes: 4 additions & 4 deletions pkg/api/kyverno/v1/policy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,15 +201,15 @@ type ResourceDescription struct {
// +optional
Namespaces []string `json:"namespaces,omitempty" yaml:"namespaces,omitempty"`

// Annotations is a map of annotations (string key-value pairs). Annotation values
// supports wildcard characters "*" (matches zero or many characters) and
// "?" (at least one character).
// Annotations is a map of annotations (key-value pairs of type string). Annotation keys
// and values support the wildcard characters "*" (matches zero or many characters) and
// "?" (matches at least one character).
// +optional
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`

// Selector is a label selector. Label keys and values in `matchLabels` support the wildcard
// characters `*` (matches zero or many characters) and `?` (matches one character).
// This feature allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that
// Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that
// using ["*" : "*"] matches any key and value but does not match an empty label set.
// +optional
Selector *metav1.LabelSelector `json:"selector,omitempty" yaml:"selector,omitempty"`
Expand Down
35 changes: 23 additions & 12 deletions pkg/engine/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,29 @@ func checkNameSpace(namespaces []string, resourceNameSpace string) bool {
}

func checkAnnotations(annotations map[string]string, resourceAnnotations map[string]string) bool {
if len(annotations) == 0 {
return true
}

for k, v := range annotations {
if len(resourceAnnotations) == 0 {
return false
match := false
for k1, v1 := range resourceAnnotations {
if wildcard.Match(k, k1) && wildcard.Match(v, v1) {
match = true
break
}
}
if resourceAnnotations[k] != v {

if match == false {
return false
}
}

return true
}

func checkSelector(labelSelector *metav1.LabelSelector, resourceLabels map[string]string) (bool, error) {
replaceWildCardsInSelector(labelSelector, resourceLabels)
replaceWildcardsInSelector(labelSelector, resourceLabels)
selector, err := metav1.LabelSelectorAsSelector(labelSelector)
if err != nil {
log.Log.Error(err, "failed to build label selector")
Expand All @@ -80,13 +90,13 @@ func checkSelector(labelSelector *metav1.LabelSelector, resourceLabels map[strin
return false, nil
}

// replaceWildCardsInSelector replaces label selector keys containing
// wildcard characters with matching keys from the resource labels.
func replaceWildCardsInSelector(labelSelector *metav1.LabelSelector, resourceLabels map[string]string) {
// replaceWildcardsInSelector replaces label selector keys and values containing
// wildcard characters with matching keys and values from the resource labels.
func replaceWildcardsInSelector(labelSelector *metav1.LabelSelector, resourceLabels map[string]string) {
result := map[string]string{}
for k, v := range labelSelector.MatchLabels {
if containsWildCard(k) || containsWildCard(v) {
matchK, matchV := expandWildCards(k, v, resourceLabels)
if containsWildcards(k) || containsWildcards(v) {
matchK, matchV := expandWildcards(k, v, resourceLabels)
result[matchK] = matchV
} else {
result[k] = v
Expand All @@ -96,11 +106,11 @@ func replaceWildCardsInSelector(labelSelector *metav1.LabelSelector, resourceLab
labelSelector.MatchLabels = result
}

func containsWildCard(s string) bool {
func containsWildcards(s string) bool {
return strings.Contains(s, "*") || strings.Contains(s, "?")
}

func expandWildCards(k, v string, labels map[string]string) (key string, val string) {
func expandWildcards(k, v string, labels map[string]string) (key string, val string) {
for k1, v1 := range labels {
if wildcard.Match(k, k1) {
if wildcard.Match(v, v1) {
Expand All @@ -114,10 +124,11 @@ func expandWildCards(k, v string, labels map[string]string) (key string, val str
return k, v
}

// replaceWildCardChars will replace '*' and '?' characters which are not
// supported by Kubernetes with a '0'.
func replaceWildCardChars(s string) string {
s = strings.Replace(s, "*", "0", -1)
s = strings.Replace(s, "?", "0", -1)

return s
}

Expand Down
26 changes: 26 additions & 0 deletions pkg/engine/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -526,3 +526,29 @@ func testSelector(t *testing.T, s *metav1.LabelSelector, l map[string]string, ma
t.Errorf("select %v -> labels %v: expected %v received %v", s.MatchLabels, l, match, res)
}
}

func TestWildCardAnnotation(t *testing.T) {

// test single annotation values
testAnnotationMatch(t, map[string]string{}, map[string]string{}, true)
testAnnotationMatch(t, map[string]string{"test/*": "*"}, map[string]string{}, false)
testAnnotationMatch(t, map[string]string{"test/*": "*"}, map[string]string{"tes1/test": "*"}, false)
testAnnotationMatch(t, map[string]string{"test/*": "*"}, map[string]string{"test/test": "*"}, true)
testAnnotationMatch(t, map[string]string{"test/*": "*"}, map[string]string{"test/bar": "foo"}, true)
testAnnotationMatch(t, map[string]string{"test/b*": "*"}, map[string]string{"test/bar": "foo"}, true)

// test multiple annotation values
testAnnotationMatch(t, map[string]string{"test/b*": "*", "test2/*": "*"},
map[string]string{"test/bar": "foo"}, false)
testAnnotationMatch(t, map[string]string{"test/b*": "*", "test2/*": "*"},
map[string]string{"test/bar": "foo", "test2/123": "bar"}, true)
testAnnotationMatch(t, map[string]string{"test/b*": "*", "test2/*": "*"},
map[string]string{"test/bar": "foo", "test2/123": "bar", "test3/123": "bar2"}, true)
}

func testAnnotationMatch(t *testing.T, policy map[string]string, resource map[string]string, match bool) {
res := checkAnnotations(policy, resource)
if res != match {
t.Errorf("annotations %v -> labels %v: expected %v received %v", policy, resource, match, res)
}
}

0 comments on commit 59ba4fe

Please sign in to comment.