Skip to content

Commit 4f3e20c

Browse files
committed
feat: default runtimeclass webhook
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
1 parent 2ed12d2 commit 4f3e20c

File tree

6 files changed

+95
-40
lines changed

6 files changed

+95
-40
lines changed

api/v1beta2/tenant_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ type TenantSpec struct {
4343
// Specifies the allowed RuntimeClasses assigned to the Tenant.
4444
// Capsule assures that all Pods resources created in the Tenant can use only one of the allowed RuntimeClasses.
4545
// Optional.
46-
RuntimeClasses *api.SelectorAllowedListSpec `json:"runtimeClasses,omitempty"`
46+
RuntimeClasses *api.DefaultAllowedListSpec `json:"runtimeClasses,omitempty"`
4747
// Specifies the allowed priorityClasses assigned to the Tenant.
4848
// Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses.
4949
// A default value can be specified, and all the Pod resources created will inherit the declared class.

api/v1beta2/zz_generated.deepcopy.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/webhook/defaults/errors.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,19 @@ func NewPriorityClassError(class string, msg error) error {
5454
func (e PriorityClassError) Error() string {
5555
return fmt.Sprintf("Failed to resolve Priority Class %s: %s", e.priorityClass, e.msg)
5656
}
57+
58+
type RuntimeClassError struct {
59+
runtimeClass string
60+
defaultClass string
61+
}
62+
63+
func NewRuntimeClassError(defaultClass, usedClass string) error {
64+
return &RuntimeClassError{
65+
runtimeClass: usedClass,
66+
defaultClass: defaultClass,
67+
}
68+
}
69+
70+
func (e RuntimeClassError) Error() string {
71+
return fmt.Sprintf("The Runtime Class %s is not allowed, leave an empty value or specify the default one %s", e.runtimeClass, e.defaultClass)
72+
}

pkg/webhook/defaults/pods.go

Lines changed: 74 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,79 +11,122 @@ import (
1111
corev1 "k8s.io/api/core/v1"
1212
schedulev1 "k8s.io/api/scheduling/v1"
1313
"k8s.io/client-go/tools/record"
14+
"k8s.io/utils/ptr"
1415
"sigs.k8s.io/controller-runtime/pkg/client"
1516
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
1617

17-
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
18+
"github.com/projectcapsule/capsule/pkg/api"
1819
"github.com/projectcapsule/capsule/pkg/webhook/utils"
1920
)
2021

2122
func mutatePodDefaults(ctx context.Context, req admission.Request, c client.Client, decoder admission.Decoder, recorder record.EventRecorder, namespace string) *admission.Response {
22-
var err error
23-
24-
pod := &corev1.Pod{}
25-
if err = decoder.Decode(req, pod); err != nil {
23+
var pod corev1.Pod
24+
if err := decoder.Decode(req, &pod); err != nil {
2625
return utils.ErroredResponse(err)
2726
}
2827

2928
pod.SetNamespace(namespace)
3029

31-
var tnt *capsulev1beta2.Tenant
30+
tnt, tErr := utils.TenantByStatusNamespace(ctx, c, pod.Namespace)
31+
if tErr != nil {
32+
return utils.ErroredResponse(tErr)
33+
} else if tnt == nil {
34+
return nil
35+
}
3236

33-
tnt, err = utils.TenantByStatusNamespace(ctx, c, pod.Namespace)
34-
if err != nil {
35-
return utils.ErroredResponse(err)
37+
var err error
38+
39+
pcMutated, pcErr := handlePriorityClassDefault(ctx, c, tnt.Spec.PriorityClasses, &pod)
40+
if pcErr != nil {
41+
return utils.ErroredResponse(pcErr)
42+
} else if pcMutated {
43+
defer func() {
44+
if err == nil {
45+
recorder.Eventf(tnt, corev1.EventTypeNormal, "TenantDefault", "Assigned Tenant default Priority Class %s to %s/%s", tnt.Spec.PriorityClasses.Default, pod.Namespace, pod.Name)
46+
}
47+
}()
48+
}
49+
50+
rcMutated, rcErr := handleRuntimeClassDefault(tnt.Spec.RuntimeClasses, &pod)
51+
if rcErr != nil {
52+
return utils.ErroredResponse(rcErr)
53+
} else if rcMutated {
54+
defer func() {
55+
if err == nil {
56+
recorder.Eventf(tnt, corev1.EventTypeNormal, "TenantDefault", "Assigned Tenant default Runtime Class %s to %s/%s", tnt.Spec.RuntimeClasses.Default, pod.Namespace, pod.Name)
57+
}
58+
}()
3659
}
3760

38-
if tnt == nil {
61+
if !rcMutated && !pcMutated {
3962
return nil
4063
}
4164

42-
allowed := tnt.Spec.PriorityClasses
65+
var marshaled []byte
4366

67+
if marshaled, err = json.Marshal(pod); err != nil {
68+
return utils.ErroredResponse(err)
69+
}
70+
71+
return ptr.To(admission.PatchResponseFromRaw(req.Object.Raw, marshaled))
72+
}
73+
74+
func handleRuntimeClassDefault(allowed *api.DefaultAllowedListSpec, pod *corev1.Pod) (mutated bool, err error) {
4475
if allowed == nil || allowed.Default == "" {
45-
return nil
76+
return false, nil
4677
}
4778

48-
priorityClassPod := pod.Spec.PriorityClassName
79+
runtimeClass := pod.Spec.RuntimeClassName
4980

50-
var mutate bool
81+
if allowed.Default == "" && runtimeClass == nil {
82+
return false, nil
83+
}
84+
85+
if allowed.Default != "" && runtimeClass != nil && *runtimeClass == allowed.Default {
86+
return false, nil
87+
}
88+
89+
if allowed.Default != "" && runtimeClass != nil && *runtimeClass != allowed.Default {
90+
// Should not happen, validation must be happened before
91+
return false, NewRuntimeClassError(allowed.Default, *runtimeClass)
92+
}
93+
94+
pod.Spec.RuntimeClassName = &allowed.Default
95+
96+
return true, nil
97+
}
98+
99+
func handlePriorityClassDefault(ctx context.Context, c client.Client, allowed *api.DefaultAllowedListSpec, pod *corev1.Pod) (mutated bool, err error) {
100+
if allowed == nil || allowed.Default == "" {
101+
return false, nil
102+
}
103+
104+
priorityClassPod := pod.Spec.PriorityClassName
51105

52106
var cpc *schedulev1.PriorityClass
53107
// PriorityClass name is empty, if no GlobalDefault is set and no PriorityClass was given on pod
54108
if len(priorityClassPod) > 0 && priorityClassPod != allowed.Default {
55109
cpc, err = utils.GetPriorityClassByName(ctx, c, priorityClassPod)
56110
// Should not happen, since API already checks if PC present
57111
if err != nil {
58-
response := admission.Denied(NewPriorityClassError(priorityClassPod, err).Error())
59-
60-
return &response
112+
return false, NewPriorityClassError(priorityClassPod, err)
61113
}
62114
} else {
63-
mutate = true
115+
mutated = true
64116
}
65117

66-
if mutate = mutate || (utils.IsDefaultPriorityClass(cpc) && cpc.GetName() != allowed.Default); !mutate {
67-
return nil
118+
if mutated = mutated || (utils.IsDefaultPriorityClass(cpc) && cpc.GetName() != allowed.Default); !mutated {
119+
return false, nil
68120
}
69121

70122
pc, err := utils.GetPriorityClassByName(ctx, c, allowed.Default)
71123
if err != nil {
72-
return utils.ErroredResponse(fmt.Errorf("failed to assign tenant default Priority Class: %w", err))
124+
return false, fmt.Errorf("failed to assign tenant default Priority Class: %w", err)
73125
}
74126

75127
pod.Spec.PreemptionPolicy = pc.PreemptionPolicy
76128
pod.Spec.Priority = &pc.Value
77129
pod.Spec.PriorityClassName = pc.Name
78-
// Marshal Pod
79-
marshaled, err := json.Marshal(pod)
80-
if err != nil {
81-
return utils.ErroredResponse(err)
82-
}
83-
84-
recorder.Eventf(tnt, corev1.EventTypeNormal, "TenantDefault", "Assigned Tenant default Priority Class %s to %s/%s", allowed.Default, pod.Namespace, pod.Name)
85-
86-
response := admission.PatchResponseFromRaw(req.Object.Raw, marshaled)
87130

88-
return &response
131+
return true, nil
89132
}

pkg/webhook/pod/runtimeclass_errors.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import (
1212

1313
type podRuntimeClassForbiddenError struct {
1414
runtimeClassName string
15-
spec api.SelectorAllowedListSpec
15+
spec api.DefaultAllowedListSpec
1616
}
1717

18-
func NewPodRuntimeClassForbidden(runtimeClassName string, spec api.SelectorAllowedListSpec) error {
18+
func NewPodRuntimeClassForbidden(runtimeClassName string, spec api.DefaultAllowedListSpec) error {
1919
return &podRuntimeClassForbiddenError{
2020
runtimeClassName: runtimeClassName,
2121
spec: spec,
@@ -25,5 +25,5 @@ func NewPodRuntimeClassForbidden(runtimeClassName string, spec api.SelectorAllow
2525
func (f podRuntimeClassForbiddenError) Error() (err string) {
2626
err = fmt.Sprintf("Pod Runtime Class %s is forbidden for the current Tenant: ", f.runtimeClassName)
2727

28-
return utils.AllowedValuesErrorMessage(f.spec, err)
28+
return utils.DefaultAllowedValuesErrorMessage(f.spec, err)
2929
}

pkg/webhook/utils/error.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,6 @@ func ErroredResponse(err error) *admission.Response {
2020
}
2121

2222
func DefaultAllowedValuesErrorMessage(allowed api.DefaultAllowedListSpec, err string) string {
23-
return AllowedValuesErrorMessage(allowed.SelectorAllowedListSpec, err)
24-
}
25-
26-
func AllowedValuesErrorMessage(allowed api.SelectorAllowedListSpec, err string) string {
2723
var extra []string
2824
if len(allowed.Exact) > 0 {
2925
extra = append(extra, fmt.Sprintf("use one from the following list (%s)", strings.Join(allowed.Exact, ", ")))

0 commit comments

Comments
 (0)