diff --git a/api/v1/search/mongodbsearch_types.go b/api/v1/search/mongodbsearch_types.go index 602cf467f..f9aa90111 100644 --- a/api/v1/search/mongodbsearch_types.go +++ b/api/v1/search/mongodbsearch_types.go @@ -105,6 +105,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 { @@ -145,6 +146,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 904424df5..385f05d7c 100644 --- a/controllers/operator/mongodbsearch_controller_test.go +++ b/controllers/operator/mongodbsearch_controller_test.go @@ -178,24 +178,17 @@ func TestMongoDBSearchReconcile_MissingSource(t *testing.T) { } func TestMongoDBSearchReconcile_Success(t *testing.T) { - ctx := context.Background() - tests := []struct { name string withWireproto bool }{ - { - name: "grpc only (default)", - withWireproto: false, - }, - { - name: "grpc + wireproto via annotation", - withWireproto: true, - }, + {name: "grpc only (default)", withWireproto: false}, + {name: "grpc + wireproto via annotation", withWireproto: true}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() search := newMongoDBSearch("search", mock.TestNamespace, "mdb") search.Spec.LogLevel = "WARN" search.Annotations = map[string]string{ @@ -203,7 +196,12 @@ func TestMongoDBSearchReconcile_Success(t *testing.T) { } 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, @@ -213,6 +211,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) @@ -234,6 +237,19 @@ func TestMongoDBSearchReconcile_Success(t *testing.T) { assert.NoError(t, err) assert.Equal(t, string(configYaml), cm.Data[searchcontroller.MongotConfigFilename]) + assert.NoError(t, mock.MarkAllStatefulSetsAsReady(ctx, search.StatefulSetNamespacedName().Namespace, c)) + + 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) + sts := &appsv1.StatefulSet{} err = c.Get(ctx, search.StatefulSetNamespacedName(), sts) assert.NoError(t, err) diff --git a/controllers/searchcontroller/mongodbsearch_reconcile_helper.go b/controllers/searchcontroller/mongodbsearch_reconcile_helper.go index d3a5c32a0..e1926b80f 100644 --- a/controllers/searchcontroller/mongodbsearch_reconcile_helper.go +++ b/controllers/searchcontroller/mongodbsearch_reconcile_helper.go @@ -87,7 +87,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)) } // This is called only if the wireproto server is enabled, to set up they keyfile necessary for authentication. @@ -449,9 +451,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) } @@ -459,14 +459,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 { @@ -475,9 +476,28 @@ 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 "" +} diff --git a/scripts/release/build/image_build_configuration.py b/scripts/release/build/image_build_configuration.py index 6cfb11e86..16c8f7766 100644 --- a/scripts/release/build/image_build_configuration.py +++ b/scripts/release/build/image_build_configuration.py @@ -1,8 +1,8 @@ from dataclasses import dataclass from typing import List, Optional -from scripts.release.build.image_build_process import ImageBuilder from scripts.release.build.build_scenario import BuildScenario +from scripts.release.build.image_build_process import ImageBuilder SUPPORTED_PLATFORMS = ["darwin/amd64", "darwin/arm64", "linux/amd64", "linux/arm64", "linux/s390x", "linux/ppc64le"]