-
Notifications
You must be signed in to change notification settings - Fork 743
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
Implement the match logic #932
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,157 @@ | ||||
package mutation | ||||
|
||||
import ( | ||||
"fmt" | ||||
|
||||
mutationsv1 "github.com/open-policy-agent/gatekeeper/apis/mutations/v1alpha1" | ||||
corev1 "k8s.io/api/core/v1" | ||||
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" | ||||
"k8s.io/apimachinery/pkg/api/meta" | ||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||||
"k8s.io/apimachinery/pkg/labels" | ||||
"k8s.io/apimachinery/pkg/runtime" | ||||
) | ||||
|
||||
// Matches verifies if the given object belonging to the given namespace | ||||
// matches the current mutator. | ||||
func Matches(match mutationsv1.Match, obj runtime.Object, ns *corev1.Namespace) (bool, error) { | ||||
meta, err := meta.Accessor(obj) | ||||
if err != nil { | ||||
return false, fmt.Errorf("Accessor failed for %s", obj.GetObjectKind().GroupVersionKind().Kind) | ||||
} | ||||
|
||||
foundMatch := false | ||||
|
||||
for _, kk := range match.Kinds { | ||||
kindMatches := false | ||||
groupMatches := false | ||||
|
||||
for _, k := range kk.Kinds { | ||||
if k == "*" || k == obj.GetObjectKind().GroupVersionKind().Kind { | ||||
kindMatches = true | ||||
break | ||||
} | ||||
} | ||||
if len(kk.Kinds) == 0 { | ||||
kindMatches = true | ||||
} | ||||
|
||||
for _, g := range kk.APIGroups { | ||||
if g == "*" || g == obj.GetObjectKind().GroupVersionKind().Group { | ||||
groupMatches = true | ||||
break | ||||
} | ||||
} | ||||
if len(kk.APIGroups) == 0 { | ||||
groupMatches = true | ||||
} | ||||
|
||||
if kindMatches && groupMatches { | ||||
foundMatch = true | ||||
} | ||||
} | ||||
if len(match.Kinds) == 0 { | ||||
foundMatch = true | ||||
} | ||||
|
||||
if !foundMatch { | ||||
return false, nil | ||||
} | ||||
|
||||
if match.Scope == apiextensionsv1beta1.ClusterScoped && | ||||
meta.GetNamespace() != "" { | ||||
return false, nil | ||||
} | ||||
|
||||
if match.Scope == apiextensionsv1beta1.NamespaceScoped && | ||||
meta.GetNamespace() == "" { | ||||
return false, nil | ||||
} | ||||
|
||||
found := false | ||||
for _, n := range match.Namespaces { | ||||
if meta.GetNamespace() == n { | ||||
found = true | ||||
break | ||||
} | ||||
} | ||||
if !found && len(match.Namespaces) > 0 { | ||||
return false, nil | ||||
} | ||||
|
||||
for _, n := range match.ExcludedNamespaces { | ||||
if meta.GetNamespace() == n { | ||||
return false, nil | ||||
} | ||||
} | ||||
if match.LabelSelector != nil { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we also match the label selector against old object? It seems weird, but that's how MutatingWebhookConfiguration's logic works: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assumed it was only against the old object, as it's the criteria for applying a given mutation. Given all the stability premises and the fact that we are only adding (and not changing) labels, I think it's safe to assume that if the old object matches the selector, the new one will. If only the new one will, it will be a matter of another round of mutation before the stability. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the last bit, I think apart from this discussion this is good to go. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding the above people to the review, hopefully that kick-starts the discussion. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 on not deviating from K8s mutatingwebhookconfig logic to avoid surprises to users
what if users want to match label on the old object then mutate the object to delete that label? is that a supported scenario for MVP? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to make sure I understood. See gatekeeper/pkg/mutation/system.go Line 85 in 624bfcc
In that case, what is referred as old vs new? And what should happen if we find out that after a mutation the object does not match anymore? Re: deleting labels, this is not possible with the current design as we are only adding non-existing labels / annotations. This is needed for making sure the mutation converges. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given all the reasons above, +1 on not testing OldObject for now
Thanks! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lemme make sure I'm getting this straight: I think I don't grok the purpose of applying to both old and new objects unless you allow the mutation webhook to create or delete objects. It seems unexpected at best. I'm in favor of shirking the k8s standard here and doing what makes more common sense, but I suspect I am missing something, too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One point of confusion is I don't think that changes much, if any, of the above analysis, but has been the source of a bit of confusion. |
||||
selector, err := metav1.LabelSelectorAsSelector(match.LabelSelector) | ||||
if err != nil { | ||||
return false, err | ||||
} | ||||
if !selector.Matches(labels.Set(meta.GetLabels())) { | ||||
return false, nil | ||||
} | ||||
} | ||||
|
||||
if match.NamespaceSelector != nil { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For MutatingWebhookConfigurations:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The namespace remains the same, so there won't be difference in new vs old. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Implemented the first two points and added tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The namespace would remain the same unless the object itself is a namespace. |
||||
selector, err := metav1.LabelSelectorAsSelector(match.NamespaceSelector) | ||||
if err != nil { | ||||
return false, err | ||||
} | ||||
|
||||
switch { | ||||
case isNamespace(obj): // if the object is a namespace, namespace selector matches against the object | ||||
if !selector.Matches(labels.Set(meta.GetLabels())) { | ||||
return false, nil | ||||
} | ||||
case meta.GetNamespace() == "": | ||||
// cluster scoped, matches by default | ||||
case !selector.Matches(labels.Set(ns.Labels)): | ||||
return false, nil | ||||
} | ||||
} | ||||
|
||||
return true, nil | ||||
} | ||||
|
||||
// AppliesTo checks if any item the given slice of ApplyTo applies to the given object | ||||
func AppliesTo(applyTo []mutationsv1.ApplyTo, obj *unstructured.Unstructured) bool { | ||||
for _, apply := range applyTo { | ||||
matchesGroup := false | ||||
matchesVersion := false | ||||
matchesKind := false | ||||
|
||||
gvk := obj.GroupVersionKind() | ||||
for _, g := range apply.Groups { | ||||
if g == gvk.Group { | ||||
matchesGroup = true | ||||
break | ||||
} | ||||
} | ||||
for _, g := range apply.Versions { | ||||
if g == gvk.Version { | ||||
matchesVersion = true | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how do we handle when there are multiple versions of the same group kind in the cluster? this here would resolve to mismatch but the apiserver would consider it a match. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Note that this check is for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure I got the point. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gotcha! Let's make sure we document this. To support multiple versions of the same schema, user needs to add another gvk to the |
||||
break | ||||
} | ||||
} | ||||
for _, g := range apply.Kinds { | ||||
if g == gvk.Kind { | ||||
matchesKind = true | ||||
break | ||||
} | ||||
} | ||||
if matchesGroup && | ||||
matchesVersion && | ||||
matchesKind { | ||||
return true | ||||
} | ||||
} | ||||
return false | ||||
} | ||||
|
||||
func isNamespace(obj runtime.Object) bool { | ||||
return obj.GetObjectKind().GroupVersionKind().Kind == "Namespace" && | ||||
obj.GetObjectKind().GroupVersionKind().Group == "" | ||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: merge import blocks