diff --git a/apis/meta/v1alpha1/labels_annotations.go b/apis/meta/v1alpha1/labels_annotations.go index a2546955..707aebc9 100644 --- a/apis/meta/v1alpha1/labels_annotations.go +++ b/apis/meta/v1alpha1/labels_annotations.go @@ -80,6 +80,8 @@ const ( DisplayNameAnnotationKey = "katanomi.dev/displayName" // CreatedTimeAnnotationKey creation time for objects CreatedTimeAnnotationKey = "katanomi.dev/creationTime" + // UpdatedTimeAnnotationKey update time for objects + UpdatedTimeAnnotationKey = "katanomi.dev/updateTime" // DeletedTimeAnnotationKey deletion time for objects DeletedTimeAnnotationKey = "katanomi.dev/deletionTime" // CrossClusterAnnotationKey annotates a cross cluster resource/action diff --git a/webhook/admission/transform.go b/webhook/admission/transform.go index ff61b3fe..98664b33 100644 --- a/webhook/admission/transform.go +++ b/webhook/admission/transform.go @@ -18,6 +18,7 @@ package admission import ( "context" + "time" mv1alpha1 "github.com/katanomi/pkg/apis/meta/v1alpha1" admissionv1 "k8s.io/api/admission/v1" @@ -175,6 +176,22 @@ func WithCancelledBy(scheme *runtime.Scheme, isCancelled func(oldObj runtime.Obj } } +// WithUpdateTime adds a updateTime annotation to the object +func WithUpdateTime() TransformFunc { + return func(ctx context.Context, obj runtime.Object, req admission.Request) { + if req.Operation != admissionv1.Update { + return + } + newObj := obj.(metav1.Object) + annotations := newObj.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + annotations[mv1alpha1.UpdatedTimeAnnotationKey] = time.Now().Format(time.RFC3339) + newObj.SetAnnotations(annotations) + } +} + // setCancelledBy will set obj annotation base on the request information func setCancelledBy(ctx context.Context, obj runtime.Object, req admission.Request) { logger := logging.FromContext(ctx) diff --git a/webhook/admission/transform_test.go b/webhook/admission/transform_test.go index 4f87f78c..b32b41c6 100644 --- a/webhook/admission/transform_test.go +++ b/webhook/admission/transform_test.go @@ -19,10 +19,13 @@ package admission import ( "context" "encoding/json" + "fmt" "testing" + "time" "github.com/katanomi/pkg/apis/meta/v1alpha1" . "github.com/onsi/gomega" + "github.com/onsi/gomega/matchers" v1 "k8s.io/api/admission/v1" authenticationv1 "k8s.io/api/authentication/v1" corev1 "k8s.io/api/core/v1" @@ -93,3 +96,41 @@ func TestWithCancelledBy(t *testing.T) { Expect(pod.Annotations[v1alpha1.CancelledByAnnotationKey]).To(Equal(`{"user":{"kind":"User","name":"admin"}}`)) } + +func TestWithUpdateTime(t *testing.T) { + g := NewGomegaWithT(t) + ctx := context.Background() + obj := &corev1.Pod{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "pod", + Namespace: "default", + UID: types.UID("abc"), + }, + } + + req := admission.Request{ + AdmissionRequest: v1.AdmissionRequest{ + Operation: v1.Create, + }, + } + WithUpdateTime()(ctx, obj, req) + g.Expect(obj.Annotations).NotTo(HaveKey(v1alpha1.UpdatedTimeAnnotationKey)) + + req.Operation = v1.Update + WithUpdateTime()(ctx, obj, req) + beTimeString := matchers.NewWithTransformMatcher(checkTimeString, BeTrue()) + g.Expect(obj.Annotations[v1alpha1.UpdatedTimeAnnotationKey]).To(beTimeString) +} + +func checkTimeString(actual interface{}) (interface{}, error) { + str, ok := actual.(string) + if !ok { + return false, fmt.Errorf("expected string value, got %v", actual) + } + _, err := time.Parse(time.RFC3339, str) + return err == nil, err +}