-
Notifications
You must be signed in to change notification settings - Fork 40
feat: add mutating webhook to label managed deploys created by aksService #1298
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
base: main
Are you sure you want to change the base?
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,51 @@ | ||
| /* | ||
| Copyright 2025 The KubeFleet 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 utils | ||
|
|
||
| import ( | ||
| "slices" | ||
|
|
||
| authenticationv1 "k8s.io/api/authentication/v1" | ||
| ) | ||
|
|
||
| // This file contains constants and utility functions related to Azure-specific logic. | ||
| // We define these in a separate file to avoid potential merge conflicts from the KubeFleet repository when backporting changes. | ||
| const ( | ||
| // ReconcileLabelKey is the label key added to resources that should be reconciled. | ||
| // The value indicates the reason the resource should be reconciled. | ||
| ReconcileLabelKey = "fleet.azure.com/reconcile" | ||
| // ReconcileLabelValue is the label value paired with ReconcileLabelKey, | ||
| // indicating the resource is managed and should be reconciled. | ||
| ReconcileLabelValue = "managed" | ||
|
|
||
| // AKSServiceUserName is the username whose deployment requests should be mutated. | ||
| AKSServiceUserName = "aksService" | ||
| // SystemMastersGroup is the Kubernetes group representing cluster administrators. | ||
| SystemMastersGroup = "system:masters" | ||
| ) | ||
|
|
||
| // IsAKSService reports whether the user is the aksService user with | ||
| // system:masters group membership. | ||
| func IsAKSService(userInfo authenticationv1.UserInfo) bool { | ||
| return userInfo.Username == AKSServiceUserName && slices.Contains(userInfo.Groups, SystemMastersGroup) | ||
| } | ||
|
|
||
| // HasReconcileLabel reports whether the given labels contain the fleet reconcile | ||
| // label key with a non-empty value. | ||
| func HasReconcileLabel(labels map[string]string) bool { | ||
| return labels[ReconcileLabelKey] != "" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| /* | ||
| Copyright 2025 The KubeFleet Authors. | ||
|
Contributor
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. Hi Wantong! Just to double-check: do we need to mutate the replicaSet as well? Otherwise, the Deployments won't function correctly.
Contributor
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. (Sorry if I remembered this wrong 🙈) |
||
|
|
||
| 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 deployment | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "net/http" | ||
|
|
||
| appsv1 "k8s.io/api/apps/v1" | ||
| "k8s.io/klog/v2" | ||
| "sigs.k8s.io/controller-runtime/pkg/manager" | ||
| "sigs.k8s.io/controller-runtime/pkg/webhook" | ||
| "sigs.k8s.io/controller-runtime/pkg/webhook/admission" | ||
|
|
||
| "go.goms.io/fleet/pkg/utils" | ||
| ) | ||
|
|
||
| // MutatingPath is the webhook service path for mutating Deployment resources. | ||
| var MutatingPath = fmt.Sprintf(utils.MutatingPathFmt, appsv1.SchemeGroupVersion.Group, appsv1.SchemeGroupVersion.Version, "deployment") | ||
|
|
||
| type deploymentMutator struct { | ||
| decoder webhook.AdmissionDecoder | ||
| } | ||
|
|
||
| // AddMutating registers the mutating webhook for Deployments with the manager. | ||
| func AddMutating(mgr manager.Manager) error { | ||
| hookServer := mgr.GetWebhookServer() | ||
| hookServer.Register(MutatingPath, &webhook.Admission{Handler: &deploymentMutator{decoder: admission.NewDecoder(mgr.GetScheme())}}) | ||
| return nil | ||
| } | ||
|
|
||
| // Handle injects the fleet.azure.com/reconcile=managed label onto the | ||
| // Deployment and its pod template when the request originated from the aksService user. | ||
| func (m *deploymentMutator) Handle(_ context.Context, req admission.Request) admission.Response { | ||
| klog.V(2).InfoS("handling deployment mutating webhook", | ||
| "operation", req.Operation, "namespace", req.Namespace, "name", req.Name, "user", req.UserInfo.Username) | ||
|
|
||
| // Pass through requests targeting reserved system namespaces. | ||
| if utils.IsReservedNamespace(req.Namespace) { | ||
| return admission.Allowed(fmt.Sprintf("namespace %s is a reserved system namespace, no mutation needed", req.Namespace)) | ||
| } | ||
|
|
||
| // Only mutate when the request was made by the aksService user with | ||
| // system:masters group membership. | ||
| if !utils.IsAKSService(req.UserInfo) { | ||
| return admission.Allowed("user is not aksService, no mutation needed") | ||
| } | ||
|
|
||
| var deploy appsv1.Deployment | ||
| if err := m.decoder.Decode(req, &deploy); err != nil { | ||
| return admission.Errored(http.StatusBadRequest, err) | ||
| } | ||
|
|
||
| // Add the reconcile label on the Deployment metadata. | ||
| if deploy.Labels == nil { | ||
| deploy.Labels = map[string]string{} | ||
| } | ||
| deploy.Labels[utils.ReconcileLabelKey] = utils.ReconcileLabelValue | ||
|
|
||
| // Add the reconcile label on the pod template metadata. | ||
| if deploy.Spec.Template.Labels == nil { | ||
| deploy.Spec.Template.Labels = map[string]string{} | ||
| } | ||
| deploy.Spec.Template.Labels[utils.ReconcileLabelKey] = utils.ReconcileLabelValue | ||
|
|
||
| marshaled, err := json.Marshal(deploy) | ||
| if err != nil { | ||
| return admission.Errored(http.StatusInternalServerError, err) | ||
| } | ||
|
|
||
| klog.V(2).InfoS("mutated deployment with reconcile label", | ||
| "operation", req.Operation, "namespace", req.Namespace, "name", req.Name) | ||
| return admission.PatchResponseFromRaw(req.Object.Raw, marshaled) | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.