Skip to content

Commit 9508dce

Browse files
committed
feat: add mutating webhook to label managed deploys created by aksService
1 parent 32f48a3 commit 9508dce

11 files changed

Lines changed: 1293 additions & 6 deletions

pkg/utils/common.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ const (
6060
MutatingPathFmt = "/mutate-%s-%s-%s"
6161
lessGroupsStringFormat = "groups: %v"
6262
moreGroupsStringFormat = "groups: [%s, %s, %s,......]"
63+
64+
// ReconcileLabelKey is the label key added to resources that should be reconciled.
65+
// The value indicates the reason the resource should be reconciled.
66+
ReconcileLabelKey = "fleet.azure.com/reconcile"
67+
// ReconcileLabelValue is the label value paired with ReconcileLabelKey,
68+
// indicating the resource is managed and should be reconciled.
69+
ReconcileLabelValue = "managed"
6370
)
6471

6572
const (
@@ -504,6 +511,12 @@ func IsReservedNamespace(namespace string) bool {
504511
return strings.HasPrefix(namespace, fleetPrefix) || strings.HasPrefix(namespace, kubePrefix)
505512
}
506513

514+
// HasReconcileLabel reports whether the given labels contain the fleet reconcile
515+
// label key with a non-empty value.
516+
func HasReconcileLabel(labels map[string]string) bool {
517+
return labels[ReconcileLabelKey] != ""
518+
}
519+
507520
// IsFleetMemberNamespace indicates if an argued namespace is a fleet member namespace.
508521
func IsFleetMemberNamespace(namespace string) bool {
509522
return strings.HasPrefix(namespace, fleetMemberNamespacePrefix)

pkg/webhook/add_handler.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"go.goms.io/fleet/pkg/webhook/clusterresourceplacement"
66
"go.goms.io/fleet/pkg/webhook/clusterresourceplacementdisruptionbudget"
77
"go.goms.io/fleet/pkg/webhook/clusterresourceplacementeviction"
8+
"go.goms.io/fleet/pkg/webhook/deployment"
89
"go.goms.io/fleet/pkg/webhook/fleetresourcehandler"
910
"go.goms.io/fleet/pkg/webhook/membercluster"
1011
"go.goms.io/fleet/pkg/webhook/pdb"
@@ -29,4 +30,6 @@ func init() {
2930
AddToManagerFuncs = append(AddToManagerFuncs, resourceoverride.Add)
3031
AddToManagerFuncs = append(AddToManagerFuncs, clusterresourceplacementeviction.Add)
3132
AddToManagerFuncs = append(AddToManagerFuncs, clusterresourceplacementdisruptionbudget.Add)
33+
AddToManagerFuncs = append(AddToManagerFuncs, deployment.AddMutating)
34+
AddToManagerFuncs = append(AddToManagerFuncs, deployment.Add)
3235
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
Copyright 2025 The KubeFleet Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package deployment
18+
19+
import (
20+
"context"
21+
"encoding/json"
22+
"fmt"
23+
"net/http"
24+
25+
appsv1 "k8s.io/api/apps/v1"
26+
"k8s.io/klog/v2"
27+
"sigs.k8s.io/controller-runtime/pkg/manager"
28+
"sigs.k8s.io/controller-runtime/pkg/webhook"
29+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
30+
31+
"go.goms.io/fleet/pkg/utils"
32+
)
33+
34+
// MutatingPath is the webhook service path for mutating Deployment resources.
35+
var MutatingPath = fmt.Sprintf(utils.MutatingPathFmt, appsv1.SchemeGroupVersion.Group, appsv1.SchemeGroupVersion.Version, "deployment")
36+
37+
type deploymentMutator struct {
38+
decoder webhook.AdmissionDecoder
39+
}
40+
41+
// AddMutating registers the mutating webhook for Deployments with the manager.
42+
func AddMutating(mgr manager.Manager) error {
43+
hookServer := mgr.GetWebhookServer()
44+
hookServer.Register(MutatingPath, &webhook.Admission{Handler: &deploymentMutator{decoder: admission.NewDecoder(mgr.GetScheme())}})
45+
return nil
46+
}
47+
48+
// Handle injects the fleet.azure.com/reconcile=managed label onto the
49+
// Deployment and its pod template when the request originated from the aksService user.
50+
func (m *deploymentMutator) Handle(_ context.Context, req admission.Request) admission.Response {
51+
klog.V(2).InfoS("handling deployment mutating webhook",
52+
"operation", req.Operation, "namespace", req.Namespace, "name", req.Name, "user", req.UserInfo.Username)
53+
54+
// Pass through requests targeting reserved system namespaces.
55+
if utils.IsReservedNamespace(req.Namespace) {
56+
return admission.Allowed(fmt.Sprintf("namespace %s is a reserved system namespace, no mutation needed", req.Namespace))
57+
}
58+
59+
// Only mutate when the request was made by the aksService user with
60+
// system:masters group membership.
61+
if !isAKSService(req.UserInfo) {
62+
return admission.Allowed("user is not aksService, no mutation needed")
63+
}
64+
65+
var deploy appsv1.Deployment
66+
if err := m.decoder.Decode(req, &deploy); err != nil {
67+
return admission.Errored(http.StatusBadRequest, err)
68+
}
69+
70+
// Add the reconcile label on the Deployment metadata.
71+
if deploy.Labels == nil {
72+
deploy.Labels = map[string]string{}
73+
}
74+
deploy.Labels[utils.ReconcileLabelKey] = utils.ReconcileLabelValue
75+
76+
// Add the reconcile label on the pod template metadata.
77+
if deploy.Spec.Template.Labels == nil {
78+
deploy.Spec.Template.Labels = map[string]string{}
79+
}
80+
deploy.Spec.Template.Labels[utils.ReconcileLabelKey] = utils.ReconcileLabelValue
81+
82+
marshaled, err := json.Marshal(deploy)
83+
if err != nil {
84+
return admission.Errored(http.StatusInternalServerError, err)
85+
}
86+
87+
klog.V(2).InfoS("mutated deployment with reconcile label",
88+
"operation", req.Operation, "namespace", req.Namespace, "name", req.Name)
89+
return admission.PatchResponseFromRaw(req.Object.Raw, marshaled)
90+
}

0 commit comments

Comments
 (0)