@@ -24,6 +24,7 @@ import (
2424 authv1 "k8s.io/api/authorization/v1"
2525 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2626 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
27+ "k8s.io/apimachinery/pkg/labels"
2728 "k8s.io/apimachinery/pkg/runtime"
2829 "k8s.io/apimachinery/pkg/runtime/schema"
2930 "k8s.io/apimachinery/pkg/util/validation/field"
@@ -54,36 +55,42 @@ const (
5455 QueueNameLabel = "kueue.x-k8s.io/queue-name"
5556)
5657
57- type AppWrapperWebhook struct {
58- Config * config.AppWrapperConfig
59- SubjectAccessReviewer authClientv1.SubjectAccessReviewInterface
60- client client.Client
61- DiscoveryClient * discovery.DiscoveryClient
58+ type rbacACSupport struct {
59+ discoveryClient * discovery.DiscoveryClient
60+ subjectAccessReviewer authClientv1.SubjectAccessReviewInterface
6261 kindToResourceCache map [string ]string
6362}
6463
64+ type appWrapperWebhook struct {
65+ client client.Client
66+ defaultQueueName string
67+ enableKueueIntegrations bool
68+ manageJobsWithoutQueueName bool
69+ managedJobsNamespaceSelector labels.Selector
70+ userRBACAdmissionCheck bool
71+
72+ // support for userRBACAdmissionCheck; will be nil if it is not enabled
73+ rbacACSupport * rbacACSupport
74+ }
75+
6576//+kubebuilder:webhook:path=/mutate-workload-codeflare-dev-v1beta2-appwrapper,mutating=true,failurePolicy=fail,sideEffects=None,groups=workload.codeflare.dev,resources=appwrappers,verbs=create,versions=v1beta2,name=mappwrapper.kb.io,admissionReviewVersions=v1
6677
67- var _ webhook.CustomDefaulter = & AppWrapperWebhook {}
78+ var _ webhook.CustomDefaulter = & appWrapperWebhook {}
6879
6980// Default fills in default values when an AppWrapper is created:
7081// 1. Inject default queue name
7182// 2. Ensure Suspend is set appropriately
7283// 3. Add labels with the user name and id
73- func (w * AppWrapperWebhook ) Default (ctx context.Context , obj runtime.Object ) error {
84+ func (w * appWrapperWebhook ) Default (ctx context.Context , obj runtime.Object ) error {
7485 aw := obj .(* workloadv1beta2.AppWrapper )
7586 log .FromContext (ctx ).V (2 ).Info ("Applying defaults" , "job" , aw )
7687
7788 // Queue name and Suspend
78- if w .Config . EnableKueueIntegrations {
79- if w .Config . DefaultQueueName != "" {
80- aw .Labels = utilmaps .MergeKeepFirst (aw .Labels , map [string ]string {QueueNameLabel : w .Config . DefaultQueueName })
89+ if w .enableKueueIntegrations {
90+ if w .defaultQueueName != "" {
91+ aw .Labels = utilmaps .MergeKeepFirst (aw .Labels , map [string ]string {QueueNameLabel : w .defaultQueueName })
8192 }
82- nsSelector , err := metav1 .LabelSelectorAsSelector (w .Config .KueueJobReconciller .ManageJobsNamespaceSelector )
83- if err != nil {
84- return err
85- }
86- err = jobframework .ApplyDefaultForSuspend (ctx , (* wlc .AppWrapper )(aw ), w .client , w .Config .KueueJobReconciller .ManageJobsWithoutQueueName , nsSelector )
93+ err := jobframework .ApplyDefaultForSuspend (ctx , (* wlc .AppWrapper )(aw ), w .client , w .manageJobsWithoutQueueName , w .managedJobsNamespaceSelector )
8794 if err != nil {
8895 return err
8996 }
@@ -111,33 +118,33 @@ func (w *AppWrapperWebhook) Default(ctx context.Context, obj runtime.Object) err
111118
112119//+kubebuilder:webhook:path=/validate-workload-codeflare-dev-v1beta2-appwrapper,mutating=false,failurePolicy=fail,sideEffects=None,groups=workload.codeflare.dev,resources=appwrappers,verbs=create;update,versions=v1beta2,name=vappwrapper.kb.io,admissionReviewVersions=v1
113120
114- var _ webhook.CustomValidator = & AppWrapperWebhook {}
121+ var _ webhook.CustomValidator = & appWrapperWebhook {}
115122
116123// ValidateCreate validates invariants when an AppWrapper is created
117- func (w * AppWrapperWebhook ) ValidateCreate (ctx context.Context , obj runtime.Object ) (admission.Warnings , error ) {
124+ func (w * appWrapperWebhook ) ValidateCreate (ctx context.Context , obj runtime.Object ) (admission.Warnings , error ) {
118125 aw := obj .(* workloadv1beta2.AppWrapper )
119126 log .FromContext (ctx ).V (2 ).Info ("Validating create" , "job" , aw )
120127 allErrors := w .validateAppWrapperCreate (ctx , aw )
121- if w .Config . EnableKueueIntegrations {
128+ if w .enableKueueIntegrations {
122129 allErrors = append (allErrors , jobframework .ValidateJobOnCreate ((* wlc .AppWrapper )(aw ))... )
123130 }
124131 return nil , allErrors .ToAggregate ()
125132}
126133
127134// ValidateUpdate validates invariants when an AppWrapper is updated
128- func (w * AppWrapperWebhook ) ValidateUpdate (ctx context.Context , oldObj , newObj runtime.Object ) (admission.Warnings , error ) {
135+ func (w * appWrapperWebhook ) ValidateUpdate (ctx context.Context , oldObj , newObj runtime.Object ) (admission.Warnings , error ) {
129136 oldAW := oldObj .(* workloadv1beta2.AppWrapper )
130137 newAW := newObj .(* workloadv1beta2.AppWrapper )
131138 log .FromContext (ctx ).V (2 ).Info ("Validating update" , "job" , newAW )
132139 allErrors := w .validateAppWrapperUpdate (oldAW , newAW )
133- if w .Config . EnableKueueIntegrations {
140+ if w .enableKueueIntegrations {
134141 allErrors = append (allErrors , jobframework .ValidateJobOnUpdate ((* wlc .AppWrapper )(oldAW ), (* wlc .AppWrapper )(newAW ))... )
135142 }
136143 return nil , allErrors .ToAggregate ()
137144}
138145
139146// ValidateDelete is a noop for us, but is required to implement the CustomValidator interface
140- func (w * AppWrapperWebhook ) ValidateDelete (context.Context , runtime.Object ) (admission.Warnings , error ) {
147+ func (w * appWrapperWebhook ) ValidateDelete (context.Context , runtime.Object ) (admission.Warnings , error ) {
141148 return nil , nil
142149}
143150
@@ -151,7 +158,7 @@ func (w *AppWrapperWebhook) ValidateDelete(context.Context, runtime.Object) (adm
151158// 3. AppWrappers must not contain any resources that the user could not create directly
152159// 4. Every PodSet must be well-formed: the Path must exist and must be parseable as a PodSpecTemplate
153160// 5. AppWrappers must contain between 1 and 8 PodSets (Kueue invariant)
154- func (w * AppWrapperWebhook ) validateAppWrapperCreate (ctx context.Context , aw * workloadv1beta2.AppWrapper ) field.ErrorList {
161+ func (w * appWrapperWebhook ) validateAppWrapperCreate (ctx context.Context , aw * workloadv1beta2.AppWrapper ) field.ErrorList {
155162 allErrors := field.ErrorList {}
156163 components := aw .Spec .Components
157164 componentsPath := field .NewPath ("spec" ).Child ("components" )
@@ -182,7 +189,7 @@ func (w *AppWrapperWebhook) validateAppWrapperCreate(ctx context.Context, aw *wo
182189 }
183190
184191 // 3. RBAC check: Perform SubjectAccessReview to verify user is entitled to create component
185- if w .Config . UserRBACAdmissionCheck {
192+ if w .userRBACAdmissionCheck {
186193 ra := authv1.ResourceAttributes {
187194 Namespace : aw .Namespace ,
188195 Verb : "create" ,
@@ -203,7 +210,7 @@ func (w *AppWrapperWebhook) validateAppWrapperCreate(ctx context.Context, aw *wo
203210 sar .Spec .Extra [k ] = authv1 .ExtraValue (v )
204211 }
205212 }
206- sar , err = w .SubjectAccessReviewer .Create (ctx , sar , metav1.CreateOptions {})
213+ sar , err = w .rbacACSupport . subjectAccessReviewer .Create (ctx , sar , metav1.CreateOptions {})
207214 if err != nil {
208215 allErrors = append (allErrors , field .InternalError (compPath .Child ("template" ), err ))
209216 } else {
@@ -253,7 +260,7 @@ func (w *AppWrapperWebhook) validateAppWrapperCreate(ctx context.Context, aw *wo
253260}
254261
255262// validateAppWrapperUpdate enforces deep immutablity of all fields that were validated by validateAppWrapperCreate
256- func (w * AppWrapperWebhook ) validateAppWrapperUpdate (old * workloadv1beta2.AppWrapper , new * workloadv1beta2.AppWrapper ) field.ErrorList {
263+ func (w * appWrapperWebhook ) validateAppWrapperUpdate (old * workloadv1beta2.AppWrapper , new * workloadv1beta2.AppWrapper ) field.ErrorList {
257264 allErrors := field.ErrorList {}
258265 msg := "attempt to change immutable field"
259266 componentsPath := field .NewPath ("spec" ).Child ("components" )
@@ -297,34 +304,48 @@ func (w *AppWrapperWebhook) validateAppWrapperUpdate(old *workloadv1beta2.AppWra
297304 return allErrors
298305}
299306
300- func (w * AppWrapperWebhook ) lookupResource (gvk * schema.GroupVersionKind ) string {
301- if known , ok := w .kindToResourceCache [gvk .String ()]; ok {
307+ func (w * appWrapperWebhook ) lookupResource (gvk * schema.GroupVersionKind ) string {
308+ if known , ok := w .rbacACSupport . kindToResourceCache [gvk .String ()]; ok {
302309 return known
303310 }
304- resources , err := w .DiscoveryClient .ServerResourcesForGroupVersion (gvk .GroupVersion ().String ())
311+ resources , err := w .rbacACSupport . discoveryClient .ServerResourcesForGroupVersion (gvk .GroupVersion ().String ())
305312 if err != nil {
306313 return "*"
307314 }
308315 for _ , r := range resources .APIResources {
309316 if r .Kind == gvk .Kind {
310- w .kindToResourceCache [gvk .String ()] = r .Name
317+ w .rbacACSupport . kindToResourceCache [gvk .String ()] = r .Name
311318 return r .Name
312319 }
313320 }
314321 return "*"
315322}
316323
317324func SetupAppWrapperWebhook (mgr ctrl.Manager , awConfig * config.AppWrapperConfig ) error {
318- kubeClient , err := kubernetes . NewForConfig ( mgr . GetConfig () )
325+ nsSelector , err := metav1 . LabelSelectorAsSelector ( awConfig . KueueJobReconciller . ManageJobsNamespaceSelector )
319326 if err != nil {
320327 return err
321328 }
322- wh := & AppWrapperWebhook {
323- Config : awConfig ,
324- client : mgr .GetClient (),
325- DiscoveryClient : kubeClient .DiscoveryClient ,
326- SubjectAccessReviewer : kubeClient .AuthorizationV1 ().SubjectAccessReviews (),
327- kindToResourceCache : make (map [string ]string ),
329+ wh := & appWrapperWebhook {
330+ client : mgr .GetClient (),
331+ defaultQueueName : awConfig .DefaultQueueName ,
332+ enableKueueIntegrations : awConfig .EnableKueueIntegrations ,
333+ manageJobsWithoutQueueName : awConfig .KueueJobReconciller .ManageJobsWithoutQueueName ,
334+ managedJobsNamespaceSelector : nsSelector ,
335+ userRBACAdmissionCheck : awConfig .UserRBACAdmissionCheck ,
336+ }
337+
338+ if awConfig .UserRBACAdmissionCheck {
339+ kubeClient , err := kubernetes .NewForConfig (mgr .GetConfig ())
340+ if err != nil {
341+ return err
342+ }
343+ wh .rbacACSupport = & rbacACSupport {
344+ discoveryClient : kubeClient .DiscoveryClient ,
345+ subjectAccessReviewer : kubeClient .AuthorizationV1 ().SubjectAccessReviews (),
346+ kindToResourceCache : make (map [string ]string ),
347+ }
348+
328349 }
329350
330351 return ctrl .NewWebhookManagedBy (mgr ).
0 commit comments