Skip to content

Commit 412399f

Browse files
authored
CLOUDP: 327089, CLOUDP: 317939 add status field (#554)
# Summary This pull request introduces support for tracking and updating the reconciled MongoDB Search version within the operator and its CRD status. The changes ensure that the `Status.Version` field is set only after the StatefulSet becomes ready, and add mechanisms to pass and validate the version throughout the reconciliation workflow. Additional test coverage verifies the correct behavior before and after readiness. **CRD and Status Enhancements:** * Added a new `Version` column to the `MongoDBSearch` CRD for visibility in `kubectl get` output. * Updated the `UpdateStatus` method to accept and set the `MongoDBSearchVersionOption`, allowing the controller to update the reconciled version in status. * Introduced `MongoDBSearchVersionOption` and related helpers in `status_options.go` to encapsulate version updates for status. **Controller and Reconciliation Logic:** * Refactored reconciliation to extract and validate the mongot version, and propagate it to status only after the StatefulSet is marked ready. [[1]](diffhunk://#diff-9b1183581cc6af6723f6445d7a14ae4ae7b435dc3b93ca5d82247cda13f7ca39L92-R94) [[2]](diffhunk://#diff-9b1183581cc6af6723f6445d7a14ae4ae7b435dc3b93ca5d82247cda13f7ca39L140-R142) * Improved image tag extraction logic to reliably determine the version from container image references. [[1]](diffhunk://#diff-9b1183581cc6af6723f6445d7a14ae4ae7b435dc3b93ca5d82247cda13f7ca39L438-R456) [[2]](diffhunk://#diff-9b1183581cc6af6723f6445d7a14ae4ae7b435dc3b93ca5d82247cda13f7ca39L464-R485) **Testing Improvements:** * Enhanced the reconciliation test to verify that `Status.Version` remains empty before readiness and is set correctly after the StatefulSet is ready; added a helper to simulate readiness. [[1]](diffhunk://#diff-99deace5d42a2301ad297432b8bc207298dcbd4dd94974ec95bcdbc0f4fcd885L175-R180) [[2]](diffhunk://#diff-99deace5d42a2301ad297432b8bc207298dcbd4dd94974ec95bcdbc0f4fcd885R190-R194) [[3]](diffhunk://#diff-99deace5d42a2301ad297432b8bc207298dcbd4dd94974ec95bcdbc0f4fcd885L197-R218) [[4]](diffhunk://#diff-99deace5d42a2301ad297432b8bc207298dcbd4dd94974ec95bcdbc0f4fcd885R318-R332) ## Proof of Work Tests pass
1 parent 7272a43 commit 412399f

File tree

6 files changed

+85
-21
lines changed

6 files changed

+85
-21
lines changed

api/v1/search/mongodbsearch_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ type MongoDBSearchStatus struct {
105105
// +k8s:openapi-gen=true
106106
// +kubebuilder:subresource:status
107107
// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase",description="Current state of the MongoDB deployment."
108+
// +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".status.version",description="MongoDB Search version reconciled by the operator."
108109
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The time since the MongoDB resource was created."
109110
// +kubebuilder:resource:path=mongodbsearch,scope=Namespaced,shortName=mdbs
110111
type MongoDBSearch struct {
@@ -145,6 +146,9 @@ func (s *MongoDBSearch) UpdateStatus(phase status.Phase, statusOptions ...status
145146
if option, exists := status.GetOption(statusOptions, status.WarningsOption{}); exists {
146147
s.Status.Warnings = append(s.Status.Warnings, option.(status.WarningsOption).Warnings...)
147148
}
149+
if option, exists := status.GetOption(statusOptions, MongoDBSearchVersionOption{}); exists {
150+
s.Status.Version = option.(MongoDBSearchVersionOption).Version
151+
}
148152
}
149153

150154
func (s *MongoDBSearch) NamespacedName() types.NamespacedName {

api/v1/search/status_options.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package search
2+
3+
import "github.com/mongodb/mongodb-kubernetes/api/v1/status"
4+
5+
type MongoDBSearchVersionOption struct {
6+
Version string
7+
}
8+
9+
var _ status.Option = MongoDBSearchVersionOption{}
10+
11+
func NewMongoDBSearchVersionOption(version string) MongoDBSearchVersionOption {
12+
return MongoDBSearchVersionOption{Version: version}
13+
}
14+
15+
func (o MongoDBSearchVersionOption) Value() interface{} {
16+
return o.Version
17+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: Surface reconciled MongoDBSearch version
3+
kind: fix
4+
date: 2025-10-24
5+
---
6+
7+
* MongoDBSearch now records the reconciled mongot version in status and exposes it via a dedicated kubectl print column.

controllers/operator/mongodbsearch_controller_test.go

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -178,32 +178,30 @@ func TestMongoDBSearchReconcile_MissingSource(t *testing.T) {
178178
}
179179

180180
func TestMongoDBSearchReconcile_Success(t *testing.T) {
181-
ctx := context.Background()
182-
183181
tests := []struct {
184182
name string
185183
withWireproto bool
186184
}{
187-
{
188-
name: "grpc only (default)",
189-
withWireproto: false,
190-
},
191-
{
192-
name: "grpc + wireproto via annotation",
193-
withWireproto: true,
194-
},
185+
{name: "grpc only (default)", withWireproto: false},
186+
{name: "grpc + wireproto via annotation", withWireproto: true},
195187
}
196188

197189
for _, tc := range tests {
198190
t.Run(tc.name, func(t *testing.T) {
191+
ctx := context.Background()
199192
search := newMongoDBSearch("search", mock.TestNamespace, "mdb")
200193
search.Spec.LogLevel = "WARN"
201194
search.Annotations = map[string]string{
202195
searchv1.ForceWireprotoAnnotation: strconv.FormatBool(tc.withWireproto),
203196
}
204197

205198
mdbc := newMongoDBCommunity("mdb", mock.TestNamespace)
206-
reconciler, c := newSearchReconciler(mdbc, search)
199+
operatorConfig := searchcontroller.OperatorSearchConfig{
200+
SearchRepo: "testrepo",
201+
SearchName: "mongot",
202+
SearchVersion: "1.48.0",
203+
}
204+
reconciler, c := newSearchReconcilerWithOperatorConfig(mdbc, operatorConfig, search)
207205

208206
res, err := reconciler.Reconcile(
209207
ctx,
@@ -213,6 +211,11 @@ func TestMongoDBSearchReconcile_Success(t *testing.T) {
213211
assert.NoError(t, err)
214212
assert.Equal(t, expected, res)
215213

214+
// BEFORE readiness: version should still be empty (controller sets Version only after StatefulSet ready)
215+
searchPending := &searchv1.MongoDBSearch{}
216+
assert.NoError(t, c.Get(ctx, types.NamespacedName{Name: search.Name, Namespace: search.Namespace}, searchPending))
217+
assert.Empty(t, searchPending.Status.Version, "Status.Version must be empty before StatefulSet is marked ready")
218+
216219
svc := &corev1.Service{}
217220
err = c.Get(ctx, search.SearchServiceNamespacedName(), svc)
218221
assert.NoError(t, err)
@@ -234,6 +237,19 @@ func TestMongoDBSearchReconcile_Success(t *testing.T) {
234237
assert.NoError(t, err)
235238
assert.Equal(t, string(configYaml), cm.Data[searchcontroller.MongotConfigFilename])
236239

240+
assert.NoError(t, mock.MarkAllStatefulSetsAsReady(ctx, search.StatefulSetNamespacedName().Namespace, c))
241+
242+
res, err = reconciler.Reconcile(
243+
ctx,
244+
reconcile.Request{NamespacedName: types.NamespacedName{Name: search.Name, Namespace: search.Namespace}},
245+
)
246+
assert.NoError(t, err)
247+
assert.Equal(t, expected, res)
248+
249+
updatedSearch := &searchv1.MongoDBSearch{}
250+
assert.NoError(t, c.Get(ctx, types.NamespacedName{Name: search.Name, Namespace: search.Namespace}, updatedSearch))
251+
assert.Equal(t, operatorConfig.SearchVersion, updatedSearch.Status.Version)
252+
237253
sts := &appsv1.StatefulSet{}
238254
err = c.Get(ctx, search.StatefulSetNamespacedName(), sts)
239255
assert.NoError(t, err)

controllers/searchcontroller/mongodbsearch_reconcile_helper.go

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ func (r *MongoDBSearchReconcileHelper) reconcile(ctx context.Context, log *zap.S
8787
return workflow.Failed(err)
8888
}
8989

90-
if err := r.ValidateSearchImageVersion(); err != nil {
90+
version := r.getMongotVersion()
91+
92+
if err := r.ValidateSearchImageVersion(version); err != nil {
9193
return workflow.Failed(err)
9294
}
9395

@@ -137,7 +139,7 @@ func (r *MongoDBSearchReconcileHelper) reconcile(ctx context.Context, log *zap.S
137139
return statefulSetStatus
138140
}
139141

140-
return workflow.OK()
142+
return workflow.OK().WithAdditionalOptions(searchv1.NewMongoDBSearchVersionOption(version))
141143
}
142144

143145
// This is called only if the wireproto server is enabled, to set up they keyfile necessary for authentication.
@@ -449,24 +451,23 @@ func (r *MongoDBSearchReconcileHelper) ValidateSingleMongoDBSearchForSearchSourc
449451
return nil
450452
}
451453

452-
func (r *MongoDBSearchReconcileHelper) ValidateSearchImageVersion() error {
453-
version := r.getMongotImage()
454-
454+
func (r *MongoDBSearchReconcileHelper) ValidateSearchImageVersion(version string) error {
455455
if strings.Contains(version, unsupportedSearchVersion) {
456456
return xerrors.Errorf(unsupportedSearchVersionErrorFmt, unsupportedSearchVersion)
457457
}
458458

459459
return nil
460460
}
461461

462-
func (r *MongoDBSearchReconcileHelper) getMongotImage() string {
462+
func (r *MongoDBSearchReconcileHelper) getMongotVersion() string {
463463
version := strings.TrimSpace(r.mdbSearch.Spec.Version)
464464
if version != "" {
465465
return version
466466
}
467467

468-
if r.operatorSearchConfig.SearchVersion != "" {
469-
return r.operatorSearchConfig.SearchVersion
468+
version = strings.TrimSpace(r.operatorSearchConfig.SearchVersion)
469+
if version != "" {
470+
return version
470471
}
471472

472473
if r.mdbSearch.Spec.StatefulSetConfiguration == nil {
@@ -475,9 +476,28 @@ func (r *MongoDBSearchReconcileHelper) getMongotImage() string {
475476

476477
for _, container := range r.mdbSearch.Spec.StatefulSetConfiguration.SpecWrapper.Spec.Template.Spec.Containers {
477478
if container.Name == MongotContainerName {
478-
return container.Image
479+
return extractImageTag(container.Image)
479480
}
480481
}
481482

482483
return ""
483484
}
485+
486+
func extractImageTag(image string) string {
487+
image = strings.TrimSpace(image)
488+
if image == "" {
489+
return ""
490+
}
491+
492+
if at := strings.Index(image, "@"); at != -1 {
493+
image = image[:at]
494+
}
495+
496+
lastSlash := strings.LastIndex(image, "/")
497+
lastColon := strings.LastIndex(image, ":")
498+
if lastColon > lastSlash {
499+
return image[lastColon+1:]
500+
}
501+
502+
return ""
503+
}

scripts/release/build/image_build_configuration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from dataclasses import dataclass
22
from typing import List, Optional
33

4-
from scripts.release.build.image_build_process import ImageBuilder
54
from scripts.release.build.build_scenario import BuildScenario
5+
from scripts.release.build.image_build_process import ImageBuilder
66

77
SUPPORTED_PLATFORMS = ["darwin/amd64", "darwin/arm64", "linux/amd64", "linux/arm64", "linux/s390x",
88
"linux/ppc64le"]

0 commit comments

Comments
 (0)