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

Upgrade k8s.io/* to v0.23, sigs.k8s.io/controller-runtime to v0.11 #5282

Closed
1 task done
timebertt opened this issue Jan 17, 2022 · 12 comments
Closed
1 task done

Upgrade k8s.io/* to v0.23, sigs.k8s.io/controller-runtime to v0.11 #5282

timebertt opened this issue Jan 17, 2022 · 12 comments
Assignees
Labels
kind/enhancement Enhancement, improvement, extension

Comments

@timebertt
Copy link
Member

timebertt commented Jan 17, 2022

How to categorize this issue?

What would you like to be added:

We should upgrade to the latest versions of our go upstream dependencies:

Important changes / Action items

Here is a list of a few upstream changes to look out for when vendoring g/g, k/* and c-r in any of our repos (e.g. extensions). Please consider the release notes of controller-runtime@v0.11.0 and upwards as well for a more complete list and more details.

⚠️ breaking / most notable changes

ℹ️ other changes / good to know / for information only:

@timebertt timebertt added the kind/enhancement Enhancement, improvement, extension label Jan 17, 2022
@rfranzke
Copy link
Member

rfranzke commented Feb 1, 2022

/assign @acumino

@timebertt
Copy link
Member Author

@acumino The API server metrics added in kubernetes/kubernetes#104983 look amazingly useful to me.
Can you open a PR to whitelist these metrics in the Shoot monitoring stack?

monitoringAllowedMetrics = []string{

@acumino
Copy link
Member

acumino commented Mar 22, 2022

@timebertt I was checking on this
consider replacing custom coding in seed-admission-controller with builder.WebhookBuilder.{WithDefaulter,WithValidator}.
Seems It's not right and we need to follow our old way, which is using server.Register().

To use builder.WebhookBuilder.{WithDefaulter, WithValidator} it needs to implement admission.{CustomValidator, CustomDefaulter} interface.
If you check, the arguments of the function of that interface it requires a runtime object, and validation/mutation is done on that object.
Also before processing the request, it decodes the object here.

But in g/g seed-admission-controller case here the Handle dependes on multiple resources and not one. So we can't use builder.WebhookBuilder.{WithDefaulter,WithValidator}.

@timebertt
Copy link
Member Author

Thanks for checking the details.
Regarding extensioncrds: you're right the current structure cannot simply be refactored into admission.CustomValidator. And probably it doesn't provide much value there. So we can skip this.
Regarding podschedulername: this is already simple enough and uses as much upstream logic as possible with admission.HandlerFunc.

Regarding extensionresources: this code here could be refactored to use admission.CustomValidator:

artifacts := map[metav1.GroupVersionResource]*artifact{
gvr("backupbuckets"): {
newObject: func() client.Object { return new(extensionsv1alpha1.BackupBucket) },
validateCreateResource: func(n, _ client.Object) field.ErrorList {
return validation.ValidateBackupBucket(n.(*extensionsv1alpha1.BackupBucket))
},
validateUpdateResource: func(n, o client.Object) field.ErrorList {
return validation.ValidateBackupBucketUpdate(n.(*extensionsv1alpha1.BackupBucket), o.(*extensionsv1alpha1.BackupBucket))
},
},

I think, this would greatly benefit from it in terms of readability and minimize custom coding.
Basically we can drop this code here in favor of reusing the upstream implementation:
type handler struct {
decoder *admission.Decoder
logger logr.Logger
artifacts map[metav1.GroupVersionResource]*artifact
allowInvalidExtensionResources bool
}
var _ admission.Handler = &handler{}
func (h *handler) InjectDecoder(d *admission.Decoder) error {
h.decoder = d
return nil
}
func (h *handler) Handle(ctx context.Context, request admission.Request) admission.Response {
_, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
artifact, ok := h.artifacts[request.Resource]
if !ok {
return admission.Allowed("validation not found for the given resource")
}
switch request.Operation {
case admissionv1.Create:
return h.handleValidation(request, artifact.newObject, artifact.validateCreateResource)
case admissionv1.Update:
return h.handleValidation(request, artifact.newObject, artifact.validateUpdateResource)
default:
return admission.Allowed("operation is not CREATE or UPDATE")
}
}
type (
newObjectFunc func() client.Object
validateFunc func(new, old client.Object) field.ErrorList
)
// artifact servers as a helper to setup the corresponding function.
type artifact struct {
// newObject is a simple function that creates and returns a new resource.
newObject newObjectFunc
// validateCreateResource is a wrapper function for the different create validation functions.
validateCreateResource validateFunc
// validateUpdateResource is a wrapper function for the different update validation functions.
validateUpdateResource validateFunc
}
func (h handler) handleValidation(request admission.Request, newObject newObjectFunc, validate validateFunc) admission.Response {
obj := newObject()
if err := h.decoder.DecodeRaw(request.Object, obj); err != nil {
h.logger.Error(err, "Could not decode object", "object", request.Object)
return admission.Errored(http.StatusUnprocessableEntity, fmt.Errorf("could not decode object %v: %w", request.Object, err))
}
h.logger.Info("Validating extension resource", "resource", request.Resource, "name", kutil.ObjectName(obj), "operation", request.Operation)
var oldObj client.Object
if len(request.OldObject.Raw) != 0 {
oldObj = newObject()
if err := h.decoder.DecodeRaw(request.OldObject, oldObj); err != nil {
h.logger.Error(err, "Could not decode old object", "oldObject", oldObj)
return admission.Errored(http.StatusBadRequest, fmt.Errorf("could not decode old object %v: %v", oldObj, err))
}
}
errors := validate(obj, oldObj)
if len(errors) != 0 {
err := apierrors.NewInvalid(schema.GroupKind{
Group: request.Kind.Group,
Kind: request.Kind.Kind,
}, kutil.ObjectName(obj), errors)
h.logger.Info("Invalid extension resource detected", "operation", request.Operation, "error", err.Error())
if h.allowInvalidExtensionResources {
return admission.Allowed(err.Error())
}
return admission.Denied(err.Error())
}
return admission.Allowed("validation successful")
}
func gvr(resource string) metav1.GroupVersionResource {
return metav1.GroupVersionResource{
Group: extensionsv1alpha1.SchemeGroupVersion.Group,
Version: extensionsv1alpha1.SchemeGroupVersion.Version,
Resource: resource,
}
}
func gvrDruid(resource string) metav1.GroupVersionResource {
return metav1.GroupVersionResource{
Group: druidv1alpha1.GroupVersion.Group,
Version: druidv1alpha1.GroupVersion.Version,
Resource: resource,
}
}

@acumino
Copy link
Member

acumino commented Mar 28, 2022

In case of extensionresources also I think the current implementation is simple, since in extensionresource also we are dealing with multiple gvr if we want to follow admission.CustomValidator we have to implement it for every gvr and all of them need to be added to manager separately this will make our code bulkier only. The current approach have a little custom coding but I think this is a better implementation. WDYT?

@timebertt
Copy link
Member Author

Hmm, the current approach does have quite some custom coding, see the second code block I referenced. Basically half the file can be dropped by reusing upstream implementations.
Setting the webhook for every GVK on the other hand is very simple, doesn't increase maintenance burden and is easy to understand. So I see a clear advantage of refactoring this webhook, do you agree?

@acumino
Copy link
Member

acumino commented Mar 28, 2022

The implementation will be mostly easy only. But I feel, we are not going to have many benefits from it, I can open a PR in the coming days, and then we can look if we want the newer way.

@acumino
Copy link
Member

acumino commented Mar 29, 2022

One more hurdle which I missed previously is that the CustomValidator returns error check here whereas our functions returns field.ErrorList check here.
So to adapt CustomValidator we need to rewrite our validation function i.e. whenever one field is invalid return error at that place only instead of listing down all errors and returning that as field.ErrorList which is our current approach.

@acumino
Copy link
Member

acumino commented Mar 30, 2022

One more hurdle which I missed previously is that the CustomValidator returns error check here whereas our functions returns field.ErrorList check here. So to adapt CustomValidator we need to rewrite our validation function i.e. whenever one field is invalid return error at that place only instead of listing down all errors and returning that as field.ErrorList which is our current approach.

@timebertt Can you take a look here?

@timebertt
Copy link
Member Author

You can call ToAggregate() on the ErrorList:

func (list ErrorList) ToAggregate() utilerrors.Aggregate {

Then you don't need to change the validation code and can simply call it in the CustomValidator implementation.

@acumino
Copy link
Member

acumino commented Apr 25, 2022

/close
All required tasks have been completed.

@gardener-prow gardener-prow bot closed this as completed Apr 25, 2022
@gardener-prow
Copy link
Contributor

gardener-prow bot commented Apr 25, 2022

@acumino: Closing this issue.

In response to this:

/close
All required tasks have been completed.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/enhancement Enhancement, improvement, extension
Projects
None yet
Development

No branches or pull requests

3 participants