generated from vshn/go-bootstrap
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement
set-runonce-activedeadlineseconds
policy (#124)
Sets activeDeadlineSeconds for run-once pods.
- Loading branch information
Showing
8 changed files
with
280 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package webhooks | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"strconv" | ||
|
||
"gomodules.xyz/jsonpatch/v2" | ||
corev1 "k8s.io/api/core/v1" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/log" | ||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission" | ||
|
||
"github.com/appuio/appuio-cloud-agent/skipper" | ||
) | ||
|
||
// +kubebuilder:webhook:path=/mutate-pod-run-once-active-deadline,name=pod-run-once-active-deadline-mutator.appuio.io,admissionReviewVersions=v1,sideEffects=none,mutating=true,failurePolicy=Fail,groups="",resources=pods,verbs=create,versions=v1,matchPolicy=equivalent,reinvocationPolicy=IfNeeded | ||
//+kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch | ||
|
||
// PodRunOnceActiveDeadlineSecondsMutator adds .spec.activeDeadlineSeconds to pods with the restartPolicy set to "OnFailure" or "Never". | ||
type PodRunOnceActiveDeadlineSecondsMutator struct { | ||
Decoder admission.Decoder | ||
|
||
// Client is used to fetch namespace metadata for the override annotation | ||
Client client.Reader | ||
|
||
// DefaultNamespaceNodeSelectorAnnotation is the annotation to use for the default node selector | ||
OverrideAnnotation string | ||
|
||
// DefaultActiveDeadlineSeconds is the default activeDeadlineSeconds to apply to pods | ||
DefaultActiveDeadlineSeconds int | ||
|
||
Skipper skipper.Skipper | ||
} | ||
|
||
// Handle handles the admission requests | ||
func (m *PodRunOnceActiveDeadlineSecondsMutator) Handle(ctx context.Context, req admission.Request) admission.Response { | ||
ctx = log.IntoContext(ctx, log.FromContext(ctx). | ||
WithName("webhook.pod-run-once-active-deadline-mutator.appuio.io"). | ||
WithValues("id", req.UID, "user", req.UserInfo.Username). | ||
WithValues("operation", req.Operation). | ||
WithValues("namespace", req.Namespace, "name", req.Name, | ||
"group", req.Kind.Group, "version", req.Kind.Version, "kind", req.Kind.Kind)) | ||
|
||
return logAdmissionResponse(ctx, m.handle(ctx, req)) | ||
} | ||
|
||
func (m *PodRunOnceActiveDeadlineSecondsMutator) handle(ctx context.Context, req admission.Request) admission.Response { | ||
skip, err := m.Skipper.Skip(ctx, req) | ||
if err != nil { | ||
return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error while checking skipper: %w", err)) | ||
} | ||
if skip { | ||
return admission.Allowed("skipped") | ||
} | ||
|
||
var pod corev1.Pod | ||
if err := m.Decoder.Decode(req, &pod); err != nil { | ||
return admission.Errored(http.StatusUnprocessableEntity, err) | ||
} | ||
|
||
if pod.Spec.RestartPolicy != corev1.RestartPolicyOnFailure && pod.Spec.RestartPolicy != corev1.RestartPolicyNever { | ||
return admission.Allowed(fmt.Sprintf("pod restart policy is %q, no activeDeadlineSeconds needed", pod.Spec.RestartPolicy)) | ||
} | ||
|
||
if pod.Spec.ActiveDeadlineSeconds != nil { | ||
return admission.Allowed("pod already has an activeDeadlineSeconds value") | ||
} | ||
|
||
var ns corev1.Namespace | ||
if err := m.Client.Get(ctx, client.ObjectKey{Name: req.Namespace}, &ns); err != nil { | ||
return admission.Errored(http.StatusInternalServerError, fmt.Errorf("failed to fetch namespace for override annotation: %w", err)) | ||
} | ||
|
||
ads := m.DefaultActiveDeadlineSeconds | ||
msg := fmt.Sprintf("added default activeDeadlineSeconds %d", ads) | ||
if oa := ns.Annotations[m.OverrideAnnotation]; oa != "" { | ||
parsed, err := strconv.Atoi(oa) | ||
if err != nil { | ||
return admission.Errored(http.StatusInternalServerError, fmt.Errorf("failed to parse override annotation %q for namespace %q: %w", oa, req.Namespace, err)) | ||
} | ||
ads = parsed | ||
msg = fmt.Sprintf("added activeDeadlineSeconds %d from override annotation %q", ads, m.OverrideAnnotation) | ||
} | ||
|
||
return admission.Patched(msg, jsonpatch.Operation{ | ||
Operation: "add", | ||
Path: "/spec/restartPolicy", | ||
Value: ads, | ||
}) | ||
} |
133 changes: 133 additions & 0 deletions
133
webhooks/pod_runonce_active_deadline_seconds_mutator_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package webhooks | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/utils/ptr" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
"github.com/appuio/appuio-cloud-agent/skipper" | ||
) | ||
|
||
func Test_PodRunOnceActiveDeadlineSecondsMutator_Handle(t *testing.T) { | ||
const overrideAnnotation = "appuio.io/active-deadline-seconds-override" | ||
const defaultActiveDeadlineSeconds = 60 | ||
|
||
testCases := []struct { | ||
name string | ||
|
||
subject client.Object | ||
additionalObjects []client.Object | ||
|
||
allowed bool | ||
expectedActiveDeadlineSeconds int | ||
}{ | ||
{ | ||
name: "pod with restartPolicy=Always", | ||
subject: newPodWithSpec("testns", "pod1", corev1.PodSpec{ | ||
RestartPolicy: corev1.RestartPolicyAlways, | ||
}), | ||
additionalObjects: []client.Object{ | ||
newNamespace("testns", nil, nil), | ||
}, | ||
allowed: true, | ||
}, | ||
{ | ||
name: "pod with restartPolicy=OnFailure", | ||
subject: newPodWithSpec("testns", "pod1", corev1.PodSpec{ | ||
RestartPolicy: corev1.RestartPolicyOnFailure, | ||
}), | ||
additionalObjects: []client.Object{ | ||
newNamespace("testns", nil, nil), | ||
}, | ||
allowed: true, | ||
expectedActiveDeadlineSeconds: defaultActiveDeadlineSeconds, | ||
}, | ||
{ | ||
name: "pod with restartPolicy=Never", | ||
subject: newPodWithSpec("testns", "pod1", corev1.PodSpec{ | ||
RestartPolicy: corev1.RestartPolicyNever, | ||
}), | ||
additionalObjects: []client.Object{ | ||
newNamespace("testns", nil, nil), | ||
}, | ||
allowed: true, | ||
expectedActiveDeadlineSeconds: defaultActiveDeadlineSeconds, | ||
}, | ||
{ | ||
name: "pod in namespace with override annotation", | ||
subject: newPodWithSpec("testns", "pod1", corev1.PodSpec{ | ||
RestartPolicy: corev1.RestartPolicyNever, | ||
}), | ||
additionalObjects: []client.Object{ | ||
newNamespace("testns", nil, map[string]string{ | ||
overrideAnnotation: "30", | ||
}), | ||
}, | ||
allowed: true, | ||
expectedActiveDeadlineSeconds: 30, | ||
}, | ||
{ | ||
name: "pod with existing activeDeadlineSeconds", | ||
subject: newPodWithSpec("testns", "pod1", corev1.PodSpec{ | ||
RestartPolicy: corev1.RestartPolicyNever, | ||
ActiveDeadlineSeconds: ptr.To(int64(77)), | ||
}), | ||
additionalObjects: []client.Object{ | ||
newNamespace("testns", nil, nil), | ||
}, | ||
allowed: true, | ||
}, | ||
{ | ||
name: "pod in namespace with invalid override annotation", | ||
subject: newPodWithSpec("testns", "pod1", corev1.PodSpec{ | ||
RestartPolicy: corev1.RestartPolicyNever, | ||
}), | ||
additionalObjects: []client.Object{ | ||
newNamespace("testns", nil, map[string]string{ | ||
overrideAnnotation: "invalid", | ||
}), | ||
}, | ||
allowed: false, | ||
}, | ||
{ | ||
name: "non-existing namespace", | ||
subject: newPodWithSpec("testns", "pod1", corev1.PodSpec{ | ||
RestartPolicy: corev1.RestartPolicyNever, | ||
}), | ||
allowed: false, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
c, scheme, decoder := prepareClient(t, tc.additionalObjects...) | ||
|
||
subject := PodRunOnceActiveDeadlineSecondsMutator{ | ||
Decoder: decoder, | ||
Client: c, | ||
Skipper: skipper.StaticSkipper{}, | ||
|
||
OverrideAnnotation: overrideAnnotation, | ||
DefaultActiveDeadlineSeconds: defaultActiveDeadlineSeconds, | ||
} | ||
|
||
resp := subject.Handle(context.Background(), admissionRequestForObject(t, tc.subject, scheme)) | ||
t.Log("Response:", resp.Result.Reason, resp.Result.Message) | ||
require.Equal(t, tc.allowed, resp.Allowed) | ||
|
||
if tc.expectedActiveDeadlineSeconds == 0 { | ||
require.Len(t, resp.Patches, 0) | ||
return | ||
} | ||
|
||
require.Len(t, resp.Patches, 1) | ||
require.Equal(t, tc.expectedActiveDeadlineSeconds, resp.Patches[0].Value) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters