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/build_info.json b/build_info.json index 21efc0bd3..b702816ae 100644 --- a/build_info.json +++ b/build_info.json @@ -185,7 +185,9 @@ ] }, "release": { - "repositories": ["268558157000.dkr.ecr.us-east-1.amazonaws.com/staging/mongodb-kubernetes-tests"], + "repositories": [ + "268558157000.dkr.ecr.us-east-1.amazonaws.com/staging/mongodb-kubernetes-tests" + ], "platforms": [ "linux/amd64" ] diff --git a/changelog/20251015_other_remove_legacy_search_coordinator_polyfill.md b/changelog/20251015_other_remove_legacy_search_coordinator_polyfill.md index 0616cc062..18d891f06 100644 --- a/changelog/20251015_other_remove_legacy_search_coordinator_polyfill.md +++ b/changelog/20251015_other_remove_legacy_search_coordinator_polyfill.md @@ -3,4 +3,6 @@ kind: other date: 2025-10-15 --- -* Simplified MongoDB Search setup: Removed the custom Search Coordinator polyfill (a piece of compatibility code previously needed to add the required permissions), as MongoDB 8.2.0 and later now include the necessary permissions via the built-in searchCoordinator role. +* Simplified MongoDB Search setup: Removed the custom Search Coordinator polyfill (a piece of compatibility code + previously needed to add the required permissions), as MongoDB 8.2.0 and later now include the necessary permissions + via the built-in searchCoordinator role. 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/changelog/20251027_other_cosign_version_upgrade.md b/changelog/20251027_other_cosign_version_upgrade.md index eca5bbc2f..0e725d392 100644 --- a/changelog/20251027_other_cosign_version_upgrade.md +++ b/changelog/20251027_other_cosign_version_upgrade.md @@ -3,4 +3,7 @@ kind: other date: 2025-10-27 --- -* **kubectl-mongodb plugin**: `cosign`, the signing tool that is used to sign `kubectl-mongodb` plugin binaries, has been updated to version `3.0.2`. With this change, released binaries will be bundled with `.bundle` files containing both signature and certificate information. For more information on how to verify signatures using new `cosign` version please refer to -> https://github.com/sigstore/cosign/blob/v3.0.2/doc/cosign_verify-blob.md +* **kubectl-mongodb plugin**: `cosign`, the signing tool that is used to sign `kubectl-mongodb` plugin binaries, has + been updated to version `3.0.2`. With this change, released binaries will be bundled with `.bundle` files containing + both signature and certificate information. For more information on how to verify signatures using new `cosign` + version please refer to -> https://github.com/sigstore/cosign/blob/v3.0.2/doc/cosign_verify-blob.md diff --git a/changelog/20251030_feature_update_mongodb_search_to_use_grpc_and_mtls_for.md b/changelog/20251030_feature_update_mongodb_search_to_use_grpc_and_mtls_for.md index 1bcecd340..171e0d696 100644 --- a/changelog/20251030_feature_update_mongodb_search_to_use_grpc_and_mtls_for.md +++ b/changelog/20251030_feature_update_mongodb_search_to_use_grpc_and_mtls_for.md @@ -4,4 +4,9 @@ date: 2025-10-30 --- * **MongoDBSearch**: Switch to gRPC and mTLS for internal communication - Since MCK 1.4 the `mongod` and `mongot` processess communicated using the MongoDB Wire Protocol and used keyfile authentication. This release switches that to gRPC with mTLS authentication. gRPC will allow for load-balancing search queries against multiple `mongot` processes in the future, and mTLS decouples the internal cluster authentication mode and credentials among `mongod` processes from the connection to the `mongot` process. The Operator will automatically enable gRPC for existing and new workloads, and will enable mTLS authentication if both Database Server and `MongoDBSearch` resource are configured for TLS. \ No newline at end of file + Since MCK 1.4 the `mongod` and `mongot` processess communicated using the MongoDB Wire Protocol and used keyfile + authentication. This release switches that to gRPC with mTLS authentication. gRPC will allow for load-balancing search + queries against multiple `mongot` processes in the future, and mTLS decouples the internal cluster authentication mode + and credentials among `mongod` processes from the connection to the `mongot` process. The Operator will automatically + enable gRPC for existing and new workloads, and will enable mTLS authentication if both Database Server and + `MongoDBSearch` resource are configured for TLS. diff --git a/changelog/20251103_feature_mongodbsearch_mongodb_deployments_using_x509.md b/changelog/20251103_feature_mongodbsearch_mongodb_deployments_using_x509.md index 01362c4c0..567543392 100644 --- a/changelog/20251103_feature_mongodbsearch_mongodb_deployments_using_x509.md +++ b/changelog/20251103_feature_mongodbsearch_mongodb_deployments_using_x509.md @@ -3,5 +3,6 @@ kind: feature date: 2025-11-03 --- -* **MongoDBSearch**: MongoDB deployments using X509 internal cluster authentication are now supported. Previously MongoDB Search required SCRAM authentication among members of a MongoDB replica set. Note: SCRAM client authentication is still required, this change merely relaxes the requirements on internal cluster authentication. - +* **MongoDBSearch**: MongoDB deployments using X509 internal cluster authentication are now supported. Previously + MongoDB Search required SCRAM authentication among members of a MongoDB replica set. Note: SCRAM client authentication + is still required, this change merely relaxes the requirements on internal cluster authentication. diff --git a/changelog/20251106_feature_mongodbsearch_updated_the_default_mongodbmongodb.md b/changelog/20251106_feature_mongodbsearch_updated_the_default_mongodbmongodb.md index 540075ffd..410dd45c6 100644 --- a/changelog/20251106_feature_mongodbsearch_updated_the_default_mongodbmongodb.md +++ b/changelog/20251106_feature_mongodbsearch_updated_the_default_mongodbmongodb.md @@ -3,4 +3,5 @@ kind: feature date: 2025-11-06 --- -* **MongoDBSearch**: Updated the default `mongodb/mongodb-search` image version to 0.55.0. This is the version MCK uses if `.spec.version` is not specified. +* **MongoDBSearch**: Updated the default `mongodb/mongodb-search` image version to 0.55.0. This is the version MCK uses + if `.spec.version` is not specified. 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..3432502cd 100644 --- a/controllers/searchcontroller/mongodbsearch_reconcile_helper.go +++ b/controllers/searchcontroller/mongodbsearch_reconcile_helper.go @@ -7,6 +7,7 @@ import ( "fmt" "strings" + "github.com/blang/semver" "github.com/ghodss/yaml" "go.uber.org/zap" "golang.org/x/xerrors" @@ -21,6 +22,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + mdbv1 "github.com/mongodb/mongodb-kubernetes/api/v1/mdb" searchv1 "github.com/mongodb/mongodb-kubernetes/api/v1/search" "github.com/mongodb/mongodb-kubernetes/controllers/operator/workflow" "github.com/mongodb/mongodb-kubernetes/mongodb-community-operator/pkg/automationconfig" @@ -87,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 +141,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 +453,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 +461,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 +478,90 @@ 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{ + Role: "searchCoordinator", + Db: "admin", + Roles: []mdbv1.InheritedRole{ + { + Role: "clusterMonitor", + Db: "admin", + }, + { + Role: "directShardOperations", + Db: "admin", + }, + { + Role: "readAnyDatabase", + Db: "admin", + }, + }, + Privileges: []mdbv1.Privilege{ + { + Resource: mdbv1.Resource{ + Db: "__mdb_internal_search", + }, + Actions: []string{ + "changeStream", "collStats", "dbHash", "dbStats", "find", + "killCursors", "listCollections", "listIndexes", "listSearchIndexes", + // performRawDataOperations is available only on mongod master + // "performRawDataOperations", + "planCacheRead", "cleanupStructuredEncryptionData", + "compactStructuredEncryptionData", "convertToCapped", "createCollection", + "createIndex", "createSearchIndexes", "dropCollection", "dropIndex", + "dropSearchIndex", "insert", "remove", "renameCollectionSameDB", + "update", "updateSearchIndex", + }, + }, + // TODO: this causes the error "(BadValue) resource: {cluster: true} conflicts with resource type 'db'" + // { + // Resource: mdbv1.Resource{ + // Cluster: ptr.To(true), + // }, + // Actions: []string{"bypassDefaultMaxTimeMS"}, + // }, + }, + AuthenticationRestrictions: nil, + } +} + +// Because the first Search Public Preview support MongoDB Server 8.0.10 we need to polyfill the searchCoordinator role +// TODO: Remove once we drop support for <8.2 in Search +func NeedsSearchCoordinatorRolePolyfill(mongodbVersion string) bool { + version, err := semver.ParseTolerant(mongodbVersion) + if err != nil { + // if we can't determine the version, assume no need to polyfill + return false + } + + // 8.0.10+ and 8.1.x need the polyfill, anything older is not supported and execution will never reach here, + // and anything newer already has the role built-in + return version.Major == 8 && version.Minor < 2 +} diff --git a/docker/mongodb-kubernetes-tests/kubetester/helm.py b/docker/mongodb-kubernetes-tests/kubetester/helm.py index 276e8ad51..5d8be4d75 100644 --- a/docker/mongodb-kubernetes-tests/kubetester/helm.py +++ b/docker/mongodb-kubernetes-tests/kubetester/helm.py @@ -1,5 +1,4 @@ import glob -import logging import os import re import subprocess diff --git a/docker/mongodb-kubernetes-tests/tests/authentication/fixtures/cluster-mongodb-role-without-empty-strings.yaml b/docker/mongodb-kubernetes-tests/tests/authentication/fixtures/cluster-mongodb-role-without-empty-strings.yaml index fddf16832..0d107941c 100644 --- a/docker/mongodb-kubernetes-tests/tests/authentication/fixtures/cluster-mongodb-role-without-empty-strings.yaml +++ b/docker/mongodb-kubernetes-tests/tests/authentication/fixtures/cluster-mongodb-role-without-empty-strings.yaml @@ -23,7 +23,7 @@ spec: - "update" - "insert" - "remove" - - resource: {} + - resource: { } actions: - "find" authenticationRestrictions: diff --git a/docker/mongodb-kubernetes-tests/tests/authentication/mongodb_custom_roles.py b/docker/mongodb-kubernetes-tests/tests/authentication/mongodb_custom_roles.py index 142a04e2d..4dfbdae3a 100644 --- a/docker/mongodb-kubernetes-tests/tests/authentication/mongodb_custom_roles.py +++ b/docker/mongodb-kubernetes-tests/tests/authentication/mongodb_custom_roles.py @@ -65,6 +65,8 @@ def get_expected_role(role_name: str) -> dict: } ], } + + # fmt: on diff --git a/docker/mongodb-kubernetes-tests/tests/multicluster_appdb/multicluster_appdb_s3_based_backup_restore.py b/docker/mongodb-kubernetes-tests/tests/multicluster_appdb/multicluster_appdb_s3_based_backup_restore.py index 0d573066c..daac6c8a8 100644 --- a/docker/mongodb-kubernetes-tests/tests/multicluster_appdb/multicluster_appdb_s3_based_backup_restore.py +++ b/docker/mongodb-kubernetes-tests/tests/multicluster_appdb/multicluster_appdb_s3_based_backup_restore.py @@ -11,7 +11,6 @@ from kubetester.omtester import OMTester from kubetester.opsmanager import MongoDBOpsManager from kubetester.phase import Phase -from pymongo.errors import ServerSelectionTimeoutError from pytest import fixture, mark from tests.common.constants import ( MONGODB_PORT, diff --git a/docker/mongodb-kubernetes-tests/tests/upgrades/sharded_cluster_operator_upgrade_v1_27_to_mck.py b/docker/mongodb-kubernetes-tests/tests/upgrades/sharded_cluster_operator_upgrade_v1_27_to_mck.py index a6ce7cc04..9360c104e 100644 --- a/docker/mongodb-kubernetes-tests/tests/upgrades/sharded_cluster_operator_upgrade_v1_27_to_mck.py +++ b/docker/mongodb-kubernetes-tests/tests/upgrades/sharded_cluster_operator_upgrade_v1_27_to_mck.py @@ -1,8 +1,7 @@ -from typing import Dict, Optional +from typing import Dict import pytest from kubeobject import CustomObject -from kubernetes import client from kubetester import create_or_update_configmap, read_configmap from kubetester.certs import create_sharded_cluster_certs from kubetester.kubetester import ensure_nested_objects diff --git a/docker/mongodb-kubernetes-tests/tests/vaultintegration/om_backup_vault.py b/docker/mongodb-kubernetes-tests/tests/vaultintegration/om_backup_vault.py index 8c29f8751..4da91d515 100644 --- a/docker/mongodb-kubernetes-tests/tests/vaultintegration/om_backup_vault.py +++ b/docker/mongodb-kubernetes-tests/tests/vaultintegration/om_backup_vault.py @@ -31,7 +31,7 @@ OM_SA_NAME, OPERATOR_NAME, ) -from . import assert_secret_in_vault, run_command_in_vault, store_secret_in_vault +from . import run_command_in_vault, store_secret_in_vault OM_NAME = "om-basic" S3_RS_NAME = "my-mongodb-s3" diff --git a/docker/mongodb-kubernetes-tests/tests/vaultintegration/om_deployment_vault.py b/docker/mongodb-kubernetes-tests/tests/vaultintegration/om_deployment_vault.py index 422217047..926232937 100644 --- a/docker/mongodb-kubernetes-tests/tests/vaultintegration/om_deployment_vault.py +++ b/docker/mongodb-kubernetes-tests/tests/vaultintegration/om_deployment_vault.py @@ -21,7 +21,7 @@ from pytest import fixture, mark from ..constants import APPDB_SA_NAME, OM_SA_NAME, OPERATOR_NAME -from . import assert_secret_in_vault, run_command_in_vault, store_secret_in_vault +from . import run_command_in_vault, store_secret_in_vault OM_NAME = "om-basic" diff --git a/docker/mongodb-kubernetes-tests/tests/vaultintegration/vault_tls.py b/docker/mongodb-kubernetes-tests/tests/vaultintegration/vault_tls.py index c933a7ead..ca4591887 100644 --- a/docker/mongodb-kubernetes-tests/tests/vaultintegration/vault_tls.py +++ b/docker/mongodb-kubernetes-tests/tests/vaultintegration/vault_tls.py @@ -1,7 +1,6 @@ from typing import Optional from kubernetes import client -from kubernetes.client import V1ConfigMap from kubetester import create_secret, delete_secret, get_statefulset, read_secret from kubetester.certs import Certificate from kubetester.kubetester import KubernetesTester @@ -14,7 +13,7 @@ from pytest import fixture, mark from ..constants import APPDB_SA_NAME, DATABASE_SA_NAME, OM_SA_NAME, OPERATOR_NAME -from . import assert_secret_in_vault, run_command_in_vault, store_secret_in_vault +from . import run_command_in_vault, store_secret_in_vault MDB_RESOURCE = "my-replica-set" OM_NAME = "om-basic" diff --git a/scripts/release/build/build_scenario.py b/scripts/release/build/build_scenario.py index 99760ea89..606a9a9ba 100644 --- a/scripts/release/build/build_scenario.py +++ b/scripts/release/build/build_scenario.py @@ -6,4 +6,5 @@ class BuildScenario(StrEnum): STAGING = "staging" # CI build from a merge to the master DEVELOPMENT = "development" # Local build on a developer machine + SUPPORTED_SCENARIOS = supported_scenarios = list(BuildScenario) 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"]