From 594ab9488ff189e93471801da23151b62324beba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 13 Apr 2026 10:50:48 +0200 Subject: [PATCH] Add deployment information in status --- api/v1alpha1/function_types.go | 13 +++-- api/v1alpha1/zz_generated.deepcopy.go | 17 +++++++ config/crd/bases/functions.dev_functions.yaml | 15 ++++-- internal/controller/function_controller.go | 47 +++++++++++-------- .../controller/function_controller_test.go | 30 ++++++++++++ 5 files changed, 97 insertions(+), 25 deletions(-) diff --git a/api/v1alpha1/function_types.go b/api/v1alpha1/function_types.go index cf6c424..0e84b6c 100644 --- a/api/v1alpha1/function_types.go +++ b/api/v1alpha1/function_types.go @@ -73,12 +73,12 @@ type FunctionSpecRegistry struct { // FunctionStatus defines the observed state of Function. type FunctionStatus struct { - Name string `json:"name"` - Runtime string `json:"runtime"` + Name string `json:"name"` Conditions []metav1.Condition `json:"conditions,omitempty"` - Git FunctionStatusGit `json:"git,omitempty"` + Git FunctionStatusGit `json:"git,omitempty"` + Deployment FunctionStatusDeployment `json:"deployment,omitempty"` } type FunctionStatusGit struct { @@ -87,6 +87,13 @@ type FunctionStatusGit struct { LastChecked metav1.Time `json:"lastChecked,omitempty"` } +type FunctionStatusDeployment struct { + Image string `json:"image,omitempty"` + ImageBuilt metav1.Time `json:"imageBuilt,omitempty"` + Deployer string `json:"deployer,omitempty"` + Runtime string `json:"runtime,omitempty"` +} + // +kubebuilder:object:root=true // FunctionList contains a list of Function. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 4a934d6..f782f4e 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -158,6 +158,7 @@ func (in *FunctionStatus) DeepCopyInto(out *FunctionStatus) { } } in.Git.DeepCopyInto(&out.Git) + in.Deployment.DeepCopyInto(&out.Deployment) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FunctionStatus. @@ -170,6 +171,22 @@ func (in *FunctionStatus) DeepCopy() *FunctionStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FunctionStatusDeployment) DeepCopyInto(out *FunctionStatusDeployment) { + *out = *in + in.ImageBuilt.DeepCopyInto(&out.ImageBuilt) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FunctionStatusDeployment. +func (in *FunctionStatusDeployment) DeepCopy() *FunctionStatusDeployment { + if in == nil { + return nil + } + out := new(FunctionStatusDeployment) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FunctionStatusGit) DeepCopyInto(out *FunctionStatusGit) { *out = *in diff --git a/config/crd/bases/functions.dev_functions.yaml b/config/crd/bases/functions.dev_functions.yaml index 2bb1f5a..e868264 100644 --- a/config/crd/bases/functions.dev_functions.yaml +++ b/config/crd/bases/functions.dev_functions.yaml @@ -165,6 +165,18 @@ spec: - type type: object type: array + deployment: + properties: + deployer: + type: string + image: + type: string + imageBuilt: + format: date-time + type: string + runtime: + type: string + type: object git: properties: lastChecked: @@ -177,11 +189,8 @@ spec: type: object name: type: string - runtime: - type: string required: - name - - runtime type: object type: object served: true diff --git a/internal/controller/function_controller.go b/internal/controller/function_controller.go index 90df4ed..ecf9f55 100644 --- a/internal/controller/function_controller.go +++ b/internal/controller/function_controller.go @@ -123,16 +123,12 @@ func (r *FunctionReconciler) reconcile(ctx context.Context, function *v1alpha1.F } defer repo.Cleanup() - r.updateFunctionStatusGit(function, repo) - if err := FlushStatus(ctx, function); err != nil { - return fmt.Errorf("failed to update status: %w", err) - } + function.Status.Name = metadata.Name if err := r.ensureDeployment(ctx, function, repo, metadata); err != nil { return fmt.Errorf("deploying function failed: %w", err) } - r.updateFunctionStatus(function, metadata) if err := FlushStatus(ctx, function); err != nil { return fmt.Errorf("failed to update status: %w", err) } @@ -170,6 +166,10 @@ func (r *FunctionReconciler) prepareSource(ctx context.Context, function *v1alph // Source is ready - git clone and metadata read succeeded function.MarkSourceReady() + function.Status.Git.ResolvedBranch = repo.Branch + function.Status.Git.ObservedCommit = repo.Commit + function.Status.Git.LastChecked = metav1.Now() + return repo, &metadata, nil } @@ -190,6 +190,15 @@ func (r *FunctionReconciler) ensureDeployment(ctx context.Context, function *v1a return nil } + // function is deployed -> update status with metadata information + deployer := metadata.Deploy.Deployer + if deployer == "" { + // knative is default deployer + deployer = "knative" + } + function.Status.Deployment.Deployer = deployer + function.Status.Deployment.Runtime = metadata.Runtime + // Function is deployed - check middleware version return r.handleMiddlewareUpdate(ctx, function, repo, metadata) } @@ -208,7 +217,14 @@ func (r *FunctionReconciler) handleMiddlewareUpdate(ctx context.Context, functio logger.Info("Function is not on latest middleware. Will redeploy") function.MarkMiddlewareNotUpToDate("MiddlewareOutdated", "Middleware is outdated, redeploying") - // Checkpoint 2: Flush status before long deploy operation + // update function image in status before long redeploy operation + functionDescribe, err := r.FuncCliManager.Describe(ctx, metadata.Name, function.Namespace) + if err != nil { + return fmt.Errorf("failed to describe function to get image details: %w", err) + } + function.Status.Deployment.Image = functionDescribe.Image + + // Flush status before long deploy operation if err := FlushStatus(ctx, function); err != nil { logger.Error(err, "Failed to update status before redeployment") } @@ -221,17 +237,17 @@ func (r *FunctionReconciler) handleMiddlewareUpdate(ctx context.Context, functio logger.Info("Function is deployed with latest middleware. No need to redeploy") } + functionDescribe, err := r.FuncCliManager.Describe(ctx, metadata.Name, function.Namespace) + if err != nil { + return fmt.Errorf("failed to describe function to get image details: %w", err) + } + function.Status.Deployment.Image = functionDescribe.Image + function.MarkMiddlewareUpToDate() function.MarkDeployReady() return nil } -// updateFunctionStatus updates the function status with current deployment information -func (r *FunctionReconciler) updateFunctionStatus(function *v1alpha1.Function, metadata *funcfn.Function) { - function.Status.Name = metadata.Name - function.Status.Runtime = metadata.Runtime -} - func (r *FunctionReconciler) setupPipelineRBAC(ctx context.Context, function *v1alpha1.Function) error { if err := r.ensureDeployFunctionRole(ctx, function.Namespace); err != nil { return fmt.Errorf("failed to ensure deploy-function role: %w", err) @@ -475,10 +491,3 @@ func (r *FunctionReconciler) isMiddlewareLatest(ctx context.Context, metadata *f return latestMiddleware == functionMiddleware, nil } - -// updateFunctionStatusGit updates the functions status with the Git information -func (r *FunctionReconciler) updateFunctionStatusGit(function *v1alpha1.Function, repo *git.Repository) { - function.Status.Git.ResolvedBranch = repo.Branch - function.Status.Git.ObservedCommit = repo.Commit - function.Status.Git.LastChecked = metav1.Now() -} diff --git a/internal/controller/function_controller_test.go b/internal/controller/function_controller_test.go index bb388f8..a7c6fe0 100644 --- a/internal/controller/function_controller_test.go +++ b/internal/controller/function_controller_test.go @@ -182,6 +182,36 @@ var _ = Describe("Function Controller", func() { Expect(status.Git.ObservedCommit).Should(Equal("foobar")) }, }), + + Entry("should contain the deployment information in the status", reconcileTestCase{ + spec: functionsdevv1alpha1.FunctionSpec{ + Repository: functionsdevv1alpha1.FunctionSpecRepository{ + URL: "https://github.com/foo/bar", + }, + }, + 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", + }, + Image: "my-image:v1.2.3", + }, 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", "", "main", mock.Anything).Return(createTmpGitRepo(functions.Function{ + Name: "func-go", + Runtime: "node", + Deploy: functions.DeploySpec{ + Deployer: "keda", + }}), nil) + }, + statusChecks: func(status *functionsdevv1alpha1.FunctionStatus) { + Expect(status.Deployment.Image).Should(Equal("my-image:v1.2.3")) + Expect(status.Deployment.Deployer).Should(Equal("keda")) + Expect(status.Deployment.Runtime).Should(Equal("node")) + }, + }), ) }) })