From ae52bb4e525dfd2f7535ed280329023d6110cc6a Mon Sep 17 00:00:00 2001 From: Anand Singh Date: Tue, 28 Oct 2025 15:26:55 +0530 Subject: [PATCH] CLOUDP-327089: Add status field support (squash of last 6 commits) --- api/v1/search/mongodbsearch_types.go | 4 ++ api/v1/search/status_options.go | 17 ++++++++ ...251024_fix_mongodbsearch_status_version.md | 7 ++++ .../operator/mongodbsearch_controller_test.go | 40 +++++++++++++++++-- .../mongodbsearch_reconcile_helper.go | 38 +++++++++++++----- 5 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 api/v1/search/status_options.go create mode 100644 changelog/20251024_fix_mongodbsearch_status_version.md diff --git a/api/v1/search/mongodbsearch_types.go b/api/v1/search/mongodbsearch_types.go index ce1ef5176..35c093c90 100644 --- a/api/v1/search/mongodbsearch_types.go +++ b/api/v1/search/mongodbsearch_types.go @@ -102,6 +102,7 @@ type MongoDBSearchStatus struct { // +k8s:openapi-gen=true // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase",description="Current state of the MongoDB deployment." +// +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".status.version",description="MongoDB Search version reconciled by the operator." // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The time since the MongoDB resource was created." // +kubebuilder:resource:path=mongodbsearch,scope=Namespaced,shortName=mdbs type MongoDBSearch struct { @@ -142,6 +143,9 @@ func (s *MongoDBSearch) UpdateStatus(phase status.Phase, statusOptions ...status if option, exists := status.GetOption(statusOptions, status.WarningsOption{}); exists { s.Status.Warnings = append(s.Status.Warnings, option.(status.WarningsOption).Warnings...) } + if option, exists := status.GetOption(statusOptions, MongoDBSearchVersionOption{}); exists { + s.Status.Version = option.(MongoDBSearchVersionOption).Version + } } func (s *MongoDBSearch) NamespacedName() types.NamespacedName { diff --git a/api/v1/search/status_options.go b/api/v1/search/status_options.go new file mode 100644 index 000000000..10720047c --- /dev/null +++ b/api/v1/search/status_options.go @@ -0,0 +1,17 @@ +package search + +import "github.com/mongodb/mongodb-kubernetes/api/v1/status" + +type MongoDBSearchVersionOption struct { + Version string +} + +var _ status.Option = MongoDBSearchVersionOption{} + +func NewMongoDBSearchVersionOption(version string) MongoDBSearchVersionOption { + return MongoDBSearchVersionOption{Version: version} +} + +func (o MongoDBSearchVersionOption) Value() interface{} { + return o.Version +} diff --git a/changelog/20251024_fix_mongodbsearch_status_version.md b/changelog/20251024_fix_mongodbsearch_status_version.md new file mode 100644 index 000000000..82fded3b0 --- /dev/null +++ b/changelog/20251024_fix_mongodbsearch_status_version.md @@ -0,0 +1,7 @@ +--- +title: Surface reconciled MongoDBSearch version +kind: fix +date: 2025-10-24 +--- + +* MongoDBSearch now records the reconciled mongot version in status and exposes it via a dedicated kubectl print column. diff --git a/controllers/operator/mongodbsearch_controller_test.go b/controllers/operator/mongodbsearch_controller_test.go index 472ea419a..19f29c575 100644 --- a/controllers/operator/mongodbsearch_controller_test.go +++ b/controllers/operator/mongodbsearch_controller_test.go @@ -172,7 +172,12 @@ func TestMongoDBSearchReconcile_Success(t *testing.T) { search.Spec.LogLevel = "WARN" mdbc := newMongoDBCommunity("mdb", mock.TestNamespace) - reconciler, c := newSearchReconciler(mdbc, search) + operatorConfig := searchcontroller.OperatorSearchConfig{ + SearchRepo: "testrepo", + SearchName: "mongot", + SearchVersion: "1.48.0", + } + reconciler, c := newSearchReconcilerWithOperatorConfig(mdbc, operatorConfig, search) res, err := reconciler.Reconcile( ctx, @@ -182,6 +187,11 @@ func TestMongoDBSearchReconcile_Success(t *testing.T) { assert.NoError(t, err) assert.Equal(t, expected, res) + // BEFORE readiness: version should still be empty (controller sets Version only after StatefulSet ready) + searchPending := &searchv1.MongoDBSearch{} + assert.NoError(t, c.Get(ctx, types.NamespacedName{Name: search.Name, Namespace: search.Namespace}, searchPending)) + assert.Empty(t, searchPending.Status.Version, "Status.Version must be empty before StatefulSet is marked ready") + svc := &corev1.Service{} err = c.Get(ctx, search.SearchServiceNamespacedName(), svc) assert.NoError(t, err) @@ -194,9 +204,18 @@ func TestMongoDBSearchReconcile_Success(t *testing.T) { assert.NoError(t, err) assert.Equal(t, string(configYaml), cm.Data[searchcontroller.MongotConfigFilename]) - sts := &appsv1.StatefulSet{} - err = c.Get(ctx, search.StatefulSetNamespacedName(), sts) + markStatefulSetReady(ctx, t, c, search.StatefulSetNamespacedName()) + + res, err = reconciler.Reconcile( + ctx, + reconcile.Request{NamespacedName: types.NamespacedName{Name: search.Name, Namespace: search.Namespace}}, + ) assert.NoError(t, err) + assert.Equal(t, expected, res) + + updatedSearch := &searchv1.MongoDBSearch{} + assert.NoError(t, c.Get(ctx, types.NamespacedName{Name: search.Name, Namespace: search.Namespace}, updatedSearch)) + assert.Equal(t, operatorConfig.SearchVersion, updatedSearch.Status.Version) } func checkSearchReconcileFailed( @@ -296,3 +315,18 @@ func TestMongoDBSearchReconcile_InvalidSearchImageVersion(t *testing.T) { }) } } + +func markStatefulSetReady(ctx context.Context, t *testing.T, c client.Client, name types.NamespacedName) { + t.Helper() + + sts := &appsv1.StatefulSet{} + assert.NoError(t, c.Get(ctx, name, sts)) + + sts.Status.UpdatedReplicas = 1 + sts.Status.ReadyReplicas = 1 + sts.Status.CurrentReplicas = 1 + sts.Status.Replicas = 1 + sts.Status.ObservedGeneration = sts.Generation + + assert.NoError(t, c.Status().Update(ctx, sts)) +} diff --git a/controllers/searchcontroller/mongodbsearch_reconcile_helper.go b/controllers/searchcontroller/mongodbsearch_reconcile_helper.go index 4e94074e7..0a8fcae48 100644 --- a/controllers/searchcontroller/mongodbsearch_reconcile_helper.go +++ b/controllers/searchcontroller/mongodbsearch_reconcile_helper.go @@ -89,7 +89,9 @@ func (r *MongoDBSearchReconcileHelper) reconcile(ctx context.Context, log *zap.S return workflow.Failed(err) } - if err := r.ValidateSearchImageVersion(); err != nil { + version := r.getMongotVersion() + + if err := r.ValidateSearchImageVersion(version); err != nil { return workflow.Failed(err) } @@ -137,7 +139,7 @@ func (r *MongoDBSearchReconcileHelper) reconcile(ctx context.Context, log *zap.S return statefulSetStatus } - return workflow.OK() + return workflow.OK().WithAdditionalOptions(searchv1.NewMongoDBSearchVersionOption(version)) } func (r *MongoDBSearchReconcileHelper) ensureSourceKeyfile(ctx context.Context, log *zap.SugaredLogger) (statefulset.Modification, error) { @@ -435,9 +437,7 @@ func (r *MongoDBSearchReconcileHelper) ValidateSingleMongoDBSearchForSearchSourc return nil } -func (r *MongoDBSearchReconcileHelper) ValidateSearchImageVersion() error { - version := r.getMongotImage() - +func (r *MongoDBSearchReconcileHelper) ValidateSearchImageVersion(version string) error { if strings.Contains(version, unsupportedSearchVersion) { return xerrors.Errorf(unsupportedSearchVersionErrorFmt, unsupportedSearchVersion) } @@ -445,14 +445,15 @@ func (r *MongoDBSearchReconcileHelper) ValidateSearchImageVersion() error { return nil } -func (r *MongoDBSearchReconcileHelper) getMongotImage() string { +func (r *MongoDBSearchReconcileHelper) getMongotVersion() string { version := strings.TrimSpace(r.mdbSearch.Spec.Version) if version != "" { return version } - if r.operatorSearchConfig.SearchVersion != "" { - return r.operatorSearchConfig.SearchVersion + version = strings.TrimSpace(r.operatorSearchConfig.SearchVersion) + if version != "" { + return version } if r.mdbSearch.Spec.StatefulSetConfiguration == nil { @@ -461,13 +462,32 @@ func (r *MongoDBSearchReconcileHelper) getMongotImage() string { for _, container := range r.mdbSearch.Spec.StatefulSetConfiguration.SpecWrapper.Spec.Template.Spec.Containers { if container.Name == MongotContainerName { - return container.Image + return extractImageTag(container.Image) } } return "" } +func extractImageTag(image string) string { + image = strings.TrimSpace(image) + if image == "" { + return "" + } + + if at := strings.Index(image, "@"); at != -1 { + image = image[:at] + } + + lastSlash := strings.LastIndex(image, "/") + lastColon := strings.LastIndex(image, ":") + if lastColon > lastSlash { + return image[lastColon+1:] + } + + return "" +} + func SearchCoordinatorRole() mdbv1.MongoDBRole { // direct translation of https://github.com/10gen/mongo/blob/6f8d95a513eea8f91ea9f5d895dd8a288dfcf725/src/mongo/db/auth/builtin_roles.yml#L652 return mdbv1.MongoDBRole{