Reconciliation of MutatingWebhookConfiguration values didnt happen#682
Reconciliation of MutatingWebhookConfiguration values didnt happen#682Danil-Grigorev wants to merge 4 commits intoopenshift:masterfrom
Conversation
|
@Danil-Grigorev: This pull request references Bugzilla bug 1870158, which is valid. The bug has been moved to the POST state. The bug has been updated to refer to the pull request using the external bug tracker. 3 validation(s) were run on this bug
DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
c80b935 to
7d1adca
Compare
|
/retest |
7 similar comments
|
/retest |
|
/retest |
|
/retest |
|
/retest |
|
/retest |
|
/retest |
|
/retest |
|
@JoelSpeed @enxebre This PR implements one of the possible approaches to handling webhooks. Merging this will allow dropping generation management and spec hashing which is not currently used with our implementation, and rely solely on spec hash. Another approach could be use generation comparison, and drop defaulting/spec hashing instead, but this way the operator will frequently poll API server for updates without value changes. |
|
/retest |
Does the operator use an informer? If such, this shouldn't be a problem, we can use a cached reader and that should be sufficient. If we have informers set up already for this, then we should consider if the generation approach may be simpler, I believe this is what other implementation in library go have been using as one of their indicators, there must be a good reason for that. We may want to speak to the API folks to try and work out a bit more about the history of the apply code |
JoelSpeed
left a comment
There was a problem hiding this comment.
Would be good to see some comments on the defaulting functions with the sources of the defaults, ie, how did you work these out, is there anywhere in the code we can link to for the defaulting in the API so that we can see if any of these change easily at a later date.
Would also be good to get some test cases for these changes. This area is sparsely tested and without proper tests we aren't going to be able to ship this.
Yes, the implementation is intended to be used with an informer. I'll see how far I could test current implementation, and I think it would be good to merge it this release with the approach we already choose. Then the upstream discussion could raise the question, and the For now, I'll see how well tested the current implementation is, and do the changes. Do you think some heavier refactoring is allowed here? The |
c0f8796 to
014a83a
Compare
@JoelSpeed I added the comments on the code. The code is currently tested as much as it could be with the structure we got. Most of the absent coverage (38%) comes from |
014a83a to
52ce291
Compare
|
/retest |
52ce291 to
ad91f27
Compare
| // Ignore the error, as the object types are identical, and the path | ||
| // in the resource was already found | ||
| unstructured.SetNestedField(toWrite.Object, requiredSpec, path) |
There was a problem hiding this comment.
I don't really see the benefit of removing this error check, we know it shouldn't fail, but we should still catch it in case it does (eg we change the input and create some runtime error because we've given a reference not a pointer or something)
There was a problem hiding this comment.
You are right, it presense was justified by the code which didn't belong there, so I removed it. Now we are returning the error as expected.
| // setDefaultsValidatingWebhookConfiguration sets defaults for webhooks in the ValidatingWebhookConfiguration | ||
| // source: https://github.com/kubernetes/kubernetes/blob/c7f9dd0bafe2f3328148a85eed9c18322b9f308e/pkg/apis/admissionregistration/v1/zz_generated.defaults.go#L47-L87 | ||
| func setDefaultsValidatingWebhookConfiguration(in *admissionregistrationv1.ValidatingWebhookConfiguration) { | ||
| for i := range in.Webhooks { | ||
| a := &in.Webhooks[i] | ||
| setDefaultsValidatingWebhook(a) | ||
| if a.ClientConfig.Service != nil { | ||
| setDefaultsServiceReference(a.ClientConfig.Service) | ||
| } | ||
| for j := range a.Rules { | ||
| b := &a.Rules[j] | ||
| setDefaultsRule(&b.Rule) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // setDefaultsMutatingWebhookConfiguration sets defaults for webhooks in the MutatingWebhookConfiguration | ||
| // source: https://github.com/kubernetes/kubernetes/blob/c7f9dd0bafe2f3328148a85eed9c18322b9f308e/pkg/apis/admissionregistration/v1/zz_generated.defaults.go#L47-L87 | ||
| func setDefaultsMutatingWebhookConfiguration(in *admissionregistrationv1.MutatingWebhookConfiguration) { | ||
| for i := range in.Webhooks { | ||
| a := &in.Webhooks[i] | ||
| setDefaultsMutatingWebhook(a) | ||
| if a.ClientConfig.Service != nil { | ||
| setDefaultsServiceReference(a.ClientConfig.Service) | ||
| } | ||
| for j := range a.Rules { | ||
| b := &a.Rules[j] | ||
| setDefaultsRule(&b.Rule) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // setDefaultsValidatingWebhook set defaults for the given ValidatingWebhook | ||
| // source: https://github.com/kubernetes/kubernetes/blob/master/pkg/apis/admissionregistration/v1/defaults.go#L30-L80 | ||
| func setDefaultsValidatingWebhook(obj *admissionregistrationv1.ValidatingWebhook) { | ||
| if obj.FailurePolicy == nil { | ||
| policy := admissionregistrationv1.Fail | ||
| obj.FailurePolicy = &policy | ||
| } | ||
| if obj.MatchPolicy == nil { | ||
| policy := admissionregistrationv1.Equivalent | ||
| obj.MatchPolicy = &policy | ||
| } | ||
| if obj.NamespaceSelector == nil { | ||
| selector := metav1.LabelSelector{} | ||
| obj.NamespaceSelector = &selector | ||
| } | ||
| if obj.ObjectSelector == nil { | ||
| selector := metav1.LabelSelector{} | ||
| obj.ObjectSelector = &selector | ||
| } | ||
| if obj.TimeoutSeconds == nil { | ||
| obj.TimeoutSeconds = new(int32) | ||
| *obj.TimeoutSeconds = 10 | ||
| } | ||
| } | ||
|
|
||
| // setDefaultsMutatingWebhook set defaults for the given MutatingWebhook | ||
| // source: https://github.com/kubernetes/kubernetes/blob/master/pkg/apis/admissionregistration/v1/defaults.go#L30-L80 | ||
| func setDefaultsMutatingWebhook(obj *admissionregistrationv1.MutatingWebhook) { | ||
| if obj.FailurePolicy == nil { | ||
| policy := admissionregistrationv1.Fail | ||
| obj.FailurePolicy = &policy | ||
| } | ||
| if obj.MatchPolicy == nil { | ||
| policy := admissionregistrationv1.Equivalent | ||
| obj.MatchPolicy = &policy | ||
| } | ||
| if obj.NamespaceSelector == nil { | ||
| selector := metav1.LabelSelector{} | ||
| obj.NamespaceSelector = &selector | ||
| } | ||
| if obj.ObjectSelector == nil { | ||
| selector := metav1.LabelSelector{} | ||
| obj.ObjectSelector = &selector | ||
| } | ||
| if obj.TimeoutSeconds == nil { | ||
| obj.TimeoutSeconds = new(int32) | ||
| *obj.TimeoutSeconds = 10 | ||
| } | ||
| if obj.ReinvocationPolicy == nil { | ||
| never := admissionregistrationv1.NeverReinvocationPolicy | ||
| obj.ReinvocationPolicy = &never | ||
| } | ||
| } | ||
|
|
||
| // setDefaultsRule set default rule for both MutatingWebhook and ValidatingWebhook | ||
| func setDefaultsRule(obj *admissionregistrationv1.Rule) { | ||
| if obj.Scope == nil { | ||
| s := admissionregistrationv1.AllScopes | ||
| obj.Scope = &s | ||
| } | ||
| } | ||
|
|
||
| // setDefaults_ServiceReference sets defaults for Webhook's ServiceReference | ||
| func setDefaultsServiceReference(obj *admissionregistrationv1.ServiceReference) { | ||
| if obj.Port == nil { | ||
| obj.Port = pointer.Int32Ptr(443) | ||
| } | ||
| } |
There was a problem hiding this comment.
If this is all a direct copy from the standard defaulting implementation, why not just use the defaulting implementation? Could you not use scheme.Default(obj) to do this defaulting for you?
There was a problem hiding this comment.
@JoelSpeed Are we ok importing kubernetes into our repo?
| existingSpec, exists, err := unstructured.NestedFieldNoCopy(existingCopy.Object, path) | ||
| if err != nil { | ||
| return nil, false, err | ||
| } | ||
|
|
||
| if !modified { | ||
| return existingCopy, false, nil | ||
| if !exists { | ||
| return nil, false, fmt.Errorf("Object returned by the client not contain the specified path: %s", path) | ||
| } | ||
|
|
||
| requiredSpec, exists, err := unstructured.NestedFieldNoCopy(required.Object, path) | ||
| if err != nil { | ||
| if err := setSpecHashAnnotation(existingCopy, existingSpec); err != nil { | ||
| return nil, false, err | ||
| } | ||
| if !exists { | ||
| // No spec to update | ||
| if modified := ensureObjectMeta(existingCopy, required); !modified { |
There was a problem hiding this comment.
Why do we need to change the order of these? Should they not still work in the order they were in if we've introduced the defaulting?
pkg/operator/suite_test.go
Outdated
| []Reporter{printer.NewlineReporter{}}) | ||
| } | ||
|
|
||
| var _ = BeforeSuite(func(done Done) { |
There was a problem hiding this comment.
There's nothing happening in a go routine within this function, so you don't need to have done here, the following should suffice, then remove all reference to done and the timeout param on line 39
var _ = BeforeSuite(func() {
pkg/operator/sync_test.go
Outdated
|
|
||
| func TestSyncValidatingWebhooks(t *testing.T) { | ||
| defaultConfiguration := mapiv1.NewValidatingWebhookConfiguration() | ||
| setDefaultsValidatingWebhookConfiguration(defaultConfiguration) |
There was a problem hiding this comment.
Why do we set the defaults on this? Shouldn't that be handled as part of the apply method? Does this interfere with the testing?
There was a problem hiding this comment.
It is interfering with testing. If I don't set defaults here then I'm providing a webhook configuration that is not fully populated, and as we are using a fake client here, nothing will fix it. So all update tests will independently on the changes I do or don't do, will execute the update operation.
Another option would be putting this defaulting into api package, and apply during NewValidatingWebhookConfiguration call. But if we choose any other approach, we would have to make changes to our API, which is not good and under practices.
pkg/operator/sync_test.go
Outdated
|
|
||
| func TestSyncMutatingWebhooks(t *testing.T) { | ||
| defaultConfiguration := mapiv1.NewMutatingWebhookConfiguration() | ||
| setDefaultsMutatingWebhookConfiguration(defaultConfiguration) |
There was a problem hiding this comment.
Ditto on above, I don't think this is the right place to do this
pkg/operator/sync_test.go
Outdated
| name: "Successfully create an object", | ||
| path: "webhooks", | ||
| object: func() *unstructured.Unstructured { | ||
| obj, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(mapiv1.NewMutatingWebhookConfiguration()) |
There was a problem hiding this comment.
Should check the errors on this
func(t testing.T) *unstructured.Unstructured {
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(mapiv1.NewMutatingWebhookConfiguration())
if err != nil {
t.Fatalf(...)
}
pkg/operator/sync_test.go
Outdated
| return &unstructured.Unstructured{Object: obj} | ||
| }, | ||
| existing: invalidWebhookConfiguration, | ||
| err: fmt.Errorf("Object returned by the client not contain the specified path: webhooks"), |
There was a problem hiding this comment.
You might be better using errors.New from the "errors" package here, you're not using format strings at all
cb8d099 to
649ec9d
Compare
649ec9d to
01d5af3
Compare
|
/retest |
|
/retitle Reconciliation of MutatingWebhookConfiguration values didnt happen This shouldn't be needed to resolve the attached bug, let's try to get it verified with just the other PR and see if that goes through |
|
@Danil-Grigorev: No Bugzilla bug is referenced in the title of this pull request. DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
07f302e to
ef811a8
Compare
…perator Prevent user from setting malitious values in the rest of the webhook configuration fields, which could cause major diviations in mutation/validation procedure.
ef811a8 to
7f73f90
Compare
JoelSpeed
left a comment
There was a problem hiding this comment.
Could do with some testing ideally, see comments inline
| webhookMatchPolicy = admissionregistrationv1.Equivalent | ||
| webhookResourceSelector = metav1.LabelSelector{} | ||
| webhookScope = admissionregistrationv1.NamespacedScope | ||
| webhookReinvocationPolicy = admissionregistrationv1.IfNeededReinvocationPolicy |
There was a problem hiding this comment.
Can you highlight which of these are defaults? With a comment, eg is IfNeeded the default reinvocation policy? If not, why have we chose it
There was a problem hiding this comment.
It is not a default one, but I set it explicitly as someone could try to negate our changes from mutation with his own webhook. This way our webhook will be invoked for each additional call.
| // the previously required spec and metadata. For further detail, check the top-level comment. | ||
| func applyMutatingWebhookConfiguration(client dynamic.Interface, recorder events.Recorder, | ||
| // the previously required spec and metadata based on generation change. | ||
| func applyMutatingWebhookConfiguration(client v1.MutatingWebhookConfigurationInterface, recorder events.Recorder, |
There was a problem hiding this comment.
I would suggest just using kubernetes.Interface and then making into a MutatingWebhookConfigurationInterface within the code itself
| if klog.V(4) { | ||
| klog.Infof("%s %q changes: %v", required.GetKind(), required.GetNamespace()+"/"+required.GetName(), jsonPatchNoError(existing, toWrite)) | ||
| func copyValidatingWebhookCABundle(existing, required *admissionregistrationv1.ValidatingWebhookConfiguration) { | ||
| existingMutatingWebhooks := make(map[string]admissionregistrationv1.ValidatingWebhook) |
There was a problem hiding this comment.
This should probably be validating 😛
| existingMutatingWebhooks := make(map[string]admissionregistrationv1.ValidatingWebhook) | |
| existingValidatingWebhooks := make(map[string]admissionregistrationv1.ValidatingWebhook) |
| if klog.V(4) { | ||
| klog.Infof("%s %q changes: %v", required.GetKind(), required.GetNamespace()+"/"+required.GetName(), jsonPatchNoError(existing, toWrite)) | ||
| func copyValidatingWebhookCABundle(existing, required *admissionregistrationv1.ValidatingWebhookConfiguration) { | ||
| existingMutatingWebhooks := make(map[string]admissionregistrationv1.ValidatingWebhook) |
There was a problem hiding this comment.
This should be validating
| existingMutatingWebhooks := make(map[string]admissionregistrationv1.ValidatingWebhook) | |
| existingValidatingWebhooks := make(map[string]admissionregistrationv1.ValidatingWebhook) |
| ) | ||
|
|
||
| func TestMachinesetController(t *testing.T) { | ||
| RegisterFailHandler(Fail) |
There was a problem hiding this comment.
If we're using klog at all within this code, let's set the output
| RegisterFailHandler(Fail) | |
| RegisterFailHandler(Fail) | |
| klog.SetOutput(GinkgoWriter) |
|
@Danil-Grigorev: The following tests failed, say
Full PR test history. Your PR dashboard. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here. |
|
@Danil-Grigorev: The following test failed, say
Full PR test history. Your PR dashboard. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here. |
|
/close Closing this in favor of Joel's implementation currently merged #707 and an upcoming library-go approach - openshift/library-go#836 |
|
@Danil-Grigorev: Closed this PR. DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
Fix webhook spec update recognition
Enforce all the values we expect to be set on the webhooks. This ensures the hash
will always represent the full resource spec as we view it, so any specified field update
will be recognized and processed by the operator.