diff --git a/Dockerfile b/Dockerfile index b367f3a..3180757 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,7 @@ RUN go mod download COPY cmd/main.go cmd/main.go COPY api/ api/ COPY internal/ internal/ +COPY pkg/ pkg/ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command diff --git a/internal/webhooks/pod.go b/internal/webhooks/pod.go index 518dbe2..c9446a9 100644 --- a/internal/webhooks/pod.go +++ b/internal/webhooks/pod.go @@ -50,9 +50,21 @@ func (p *PodImageWebhookAdmission) Handle(ctx context.Context, request admission } switch request.Operation { case admissionv1.Delete: - round, _ := p.handlePodUpdate(ctx, request.Namespace, request.Name) + pod := &corev1.Pod{} + err := json.Unmarshal(request.OldObject.Raw, pod) + if err != nil { + return admission.Errored(500, err) + } + + // avoid handling multiple pod deletion events. when pod deleted, webhook will send 3 times events, first event no delete timestamp. + // see https://www.qinglite.cn/doc/527564767afc0aac0. + if !pod.DeletionTimestamp.IsZero() { + return admission.Allowed("already handled") + } + + round, _ := p.handlePodUpdate(ctx, pod.Namespace, pod.Name) if round != 0 { - klog.Infof("trigger new round %d for pod deleting %s/%s", round, request.Namespace, request.Name) + klog.Infof("trigger new round %d for pod deleting %s/%s", round, pod.Namespace, pod.Name) } return admission.Allowed(fmt.Sprintf("new round: %d", round)) case admissionv1.Create: diff --git a/internal/webhooks/pod_test.go b/internal/webhooks/pod_test.go index 8f699de..7039129 100644 --- a/internal/webhooks/pod_test.go +++ b/internal/webhooks/pod_test.go @@ -19,6 +19,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" "github.com/google/go-cmp/cmp" + "github.com/samber/lo" snapshotpodv1alpha1 "github.com/baizeai/kube-snapshot/api/v1alpha1" ) @@ -149,6 +150,12 @@ func mustMarshalObject(obj runtime.Object) []byte { return data } +func newPodWithDeleteTime() *corev1.Pod { + pod := newPod() + pod.DeletionTimestamp = lo.ToPtr(metav1.NewTime(time.Now())) + return pod +} + func TestPodImageWebhookAdmission_Handle(t *testing.T) { type fields struct { Client client.Client @@ -277,6 +284,68 @@ func TestPodImageWebhookAdmission_Handle(t *testing.T) { }, }, }, + { + name: "test handle delete event", + fields: fields{ + Client: newFakeClient(newSnapshotPod()), + }, + args: args{ + request: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Operation: admissionv1.Delete, + UID: "test-uid", + OldObject: runtime.RawExtension{ + Raw: mustMarshalObject(newPod1()), + }, + Resource: metav1.GroupVersionResource{ + Group: "", + Version: "v1", + Resource: "pods", + }, + }, + }, + }, + want: admission.Response{ + AdmissionResponse: admissionv1.AdmissionResponse{ + Allowed: true, + Result: &metav1.Status{ + Message: "new round: 3", + Code: 200, + }, + }, + }, + }, + { + name: "test event already handled", + fields: fields{ + Client: newFakeClient(newSnapshotPod()), + }, + args: args{ + request: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Operation: admissionv1.Delete, + UID: "test-uid", + OldObject: runtime.RawExtension{ + Raw: mustMarshalObject(newPodWithDeleteTime()), + }, + Resource: metav1.GroupVersionResource{ + Group: "", + Version: "v1", + Resource: "pods", + }, + }, + }, + }, + want: admission.Response{ + AdmissionResponse: admissionv1.AdmissionResponse{ + Allowed: true, + Result: &metav1.Status{ + Message: "already handled", + Code: 200, + }, + }, + }, + }, } for _, tt := range tests {