From 3692dbba2369f56b11690a12b7ca6d349228dd9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Thu, 23 Apr 2026 12:57:30 +0200 Subject: [PATCH] Add the value of the last-deployed annotation to the deployment status --- internal/controller/function_controller.go | 15 ++++++++++++ .../controller/function_controller_test.go | 24 +++++++++++++++++++ internal/controller/function_predicate.go | 2 -- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/internal/controller/function_controller.go b/internal/controller/function_controller.go index a7780be..d70d41f 100644 --- a/internal/controller/function_controller.go +++ b/internal/controller/function_controller.go @@ -22,6 +22,7 @@ import ( "os" "strconv" "strings" + "time" "github.com/functions-dev/func-operator/internal/funccli" fn "github.com/functions-dev/func-operator/internal/function" @@ -52,6 +53,9 @@ import ( const ( deployFunctionRoleName = "func-operator-deploy-function" controllerConfigName = "func-operator-controller-config" + + funcAnnotationPrefix = "functions.knative.dev/" + funcAnnotationLastDeployed = funcAnnotationPrefix + "last-deployed" ) // FunctionReconciler reconciles a Function object @@ -161,6 +165,16 @@ func (r *FunctionReconciler) reconcile(ctx context.Context, function *v1alpha1.F function.Status.Name = metadata.Name + if val, ok := function.Annotations[funcAnnotationLastDeployed]; ok { + t, err := time.Parse(time.RFC3339, val) + if err != nil { + // log a warning, but don't return error, as this can't resolve on its own + log.FromContext(ctx).Info("could not parse "+funcAnnotationLastDeployed+" annotation", "error", err) + } else { + function.Status.Deployment.ImageBuilt = metav1.NewTime(t) + } + } + if err := r.ensureDeployment(ctx, function, repo, metadata); err != nil { return fmt.Errorf("deploying function failed: %w", err) } @@ -295,6 +309,7 @@ func (r *FunctionReconciler) handleMiddlewareUpdate(ctx context.Context, functio function.Status.Middleware.PendingRebuild = false function.Status.Middleware.LastRebuild = metav1.Now() + function.Status.Deployment.ImageBuilt = metav1.Now() function.RecordHistoryEvent(fmt.Sprintf("Middleware updated from %q to %q", functionDescribe.Middleware.Version, latestMiddleware)) diff --git a/internal/controller/function_controller_test.go b/internal/controller/function_controller_test.go index e102347..f08d969 100644 --- a/internal/controller/function_controller_test.go +++ b/internal/controller/function_controller_test.go @@ -21,6 +21,7 @@ import ( "fmt" "os" "path/filepath" + "time" "github.com/functions-dev/func-operator/internal/funccli" "github.com/functions-dev/func-operator/internal/git" @@ -463,6 +464,29 @@ var _ = Describe("Function Controller", func() { Expect(annotations).NotTo(HaveKey("functions.knative.dev/reason")) }, }), + + Entry("should add last-deployed annotation in deployment status details", reconcileTestCase{ + spec: defaultSpec, + annotations: map[string]string{ + funcAnnotationLastDeployed: "2026-01-02T15:04:05+06:00", + }, + configureMocks: func(funcMock *funccli.MockManager, gitMock *git.MockManager) { + funcMock.EXPECT().Describe(mock.Anything, functionName, resourceNamespace).Return(functions.Instance{ + Middleware: functions.Middleware{ + Version: "v1.0.0", + }, + }, nil) + funcMock.EXPECT().GetLatestMiddlewareVersion(mock.Anything, mock.Anything, mock.Anything).Return("v1.0.0", nil) + funcMock.EXPECT().GetMiddlewareVersion(mock.Anything, functionName, resourceNamespace).Return("v1.0.0", nil) + + gitMock.EXPECT().CloneRepository(mock.Anything, "https://github.com/foo/bar", "", "my-branch", mock.Anything).Return(createTmpGitRepo(functions.Function{Name: "func-go"}), nil) + }, + statusChecks: func(status *functionsdevv1alpha1.FunctionStatus) { + expectedTime, err := time.Parse(time.RFC3339, "2026-01-02T15:04:05+06:00") + Expect(err).NotTo(HaveOccurred()) + Expect(status.Deployment.ImageBuilt.UTC()).To(Equal(expectedTime.UTC())) + }, + }), Entry("should set ServiceReady condition to false with unknown reason when ready status is empty", reconcileTestCase{ spec: defaultSpec, configureMocks: func(funcMock *funccli.MockManager, gitMock *git.MockManager) { diff --git a/internal/controller/function_predicate.go b/internal/controller/function_predicate.go index 8e39488..cece398 100644 --- a/internal/controller/function_predicate.go +++ b/internal/controller/function_predicate.go @@ -24,8 +24,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" ) -const funcAnnotationPrefix = "functions.knative.dev/" - // FuncAnnotationChangedPredicate triggers reconciliation when annotations // with the "functions.knative.dev/" prefix are added or changed. type FuncAnnotationChangedPredicate struct {