Repository: kubean-io/kubean
Pull request #1348 (2024-08-20 08:20:59)

add SprayJobImageRegistry to kubean-config and update manifest
<!--  

Thanks for sending a pull request!  Here are some tips for you:

* make sure your commit is signed off

-->

#### What type of PR is this?
/kind feature
<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind enhancement
/kind feature

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind design
/kind regression
-->

#### What this PR does / why we need it:

update SprayJobImageRegistry

![image](https://github.com/user-attachments/assets/2d3b16f2-15cf-44de-ad09-5088b47f02df)


#### Which issue(s) this PR fixes:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #

#### Special notes for your reviewer:



Patch:
diff --git a/api/cluster/cluster.go b/api/cluster/cluster.go
new file mode 100644
index 000000000..b3a0c8b4a
--- /dev/null
+++ b/api/cluster/cluster.go
@@ -0,0 +1,25 @@
+package cluster
+
+import (
+	"github.com/kubean-io/kubean-api/constants"
+	"k8s.io/klog/v2"
+	"strconv"
+)
+
+type ConfigProperty struct {
+	ClusterOperationsBackEndLimit string `json:"CLUSTER_OPERATIONS_BACKEND_LIMIT"`
+	SprayJobImageRegistry         string `json:"SPRAY_JOB_IMAGE_REGISTRY"`
+}
+
+func (config *ConfigProperty) GetClusterOperationsBackEndLimit() int {
+	value, _ := strconv.Atoi(config.ClusterOperationsBackEndLimit)
+	if value <= 0 {
+		klog.Warningf("GetClusterOperationsBackEndLimit and use default value %d", constants.DefaultClusterOperationsBackEndLimit)
+		return constants.DefaultClusterOperationsBackEndLimit
+	}
+	if value >= constants.MaxClusterOperationsBackEndLimit {
+		klog.Warningf("GetClusterOperationsBackEndLimit and use max value %d", constants.MaxClusterOperationsBackEndLimit)
+		return constants.MaxClusterOperationsBackEndLimit
+	}
+	return value
+}
diff --git a/api/constants/constants.go b/api/constants/constants.go
index b489ad3ab..22eb7d313 100644
--- a/api/constants/constants.go
+++ b/api/constants/constants.go
@@ -1,17 +1,22 @@
 package constants
 
-const InfoManifestGlobal = "manifest-global"
+const (
+	InfoManifestGlobal = "manifest-global"
 
-const KubeanClusterLabelKey = "clusterName"
+	KubeanClusterLabelKey = "clusterName"
 
-const Hosts_yml = "hosts.yml"
+	Hosts_yml = "hosts.yml"
 
-const Group_vars_yml = "group_vars.yml"
+	Group_vars_yml = "group_vars.yml"
 
-const SSH_privatekey = "ssh-privatekey"
+	SSH_privatekey = "ssh-privatekey"
 
-const KubeanClusterHasCompleted = "hasCompleted"
+	KubeanClusterHasCompleted = "hasCompleted"
 
-const KeySprayRelease = "kubean.io/sprayRelease"
+	KeySprayRelease = "kubean.io/sprayRelease"
+	KeySprayCommit  = "kubean.io/sprayCommit"
 
-const KeySprayCommit = "kubean.io/sprayCommit"
+	KubeanConfigMapName                  = "kubean-config"
+	DefaultClusterOperationsBackEndLimit = 30
+	MaxClusterOperationsBackEndLimit     = 200
+)
diff --git a/charts/kubean/templates/configmap.yaml b/charts/kubean/templates/configmap.yaml
index ea3e53ede..4983c1e26 100644
--- a/charts/kubean/templates/configmap.yaml
+++ b/charts/kubean/templates/configmap.yaml
@@ -8,3 +8,4 @@ metadata:
   {{- include "kubean.labels" . | nindent 4}}
 data:
   CLUSTER_OPERATIONS_BACKEND_LIMIT: "{{ .Values.kubeanOperator.operationsBackendLimit }}"
+  SPRAY_JOB_IMAGE_REGISTRY: "{{ .Values.sprayJob.image.registry }}"
diff --git a/pkg/controllers/cluster/controller.go b/pkg/controllers/cluster/controller.go
index a67df56d7..419a8a3b8 100644
--- a/pkg/controllers/cluster/controller.go
+++ b/pkg/controllers/cluster/controller.go
@@ -5,7 +5,6 @@ package cluster
 
 import (
 	"context"
-	"encoding/json"
 	"fmt"
 	"sort"
 	"strconv"
@@ -27,11 +26,9 @@ import (
 )
 
 const (
-	RequeueAfter                         = time.Second * 15
-	KubeanConfigMapName                  = "kubean-config"
-	DefaultClusterOperationsBackEndLimit = 30
-	MaxClusterOperationsBackEndLimit     = 200
-	EliminateScoreAnno                   = "kubean.io/eliminate-score"
+	RequeueAfter        = time.Second * 15
+	KubeanConfigMapName = "kubean-config"
+	EliminateScoreAnno  = "kubean.io/eliminate-score"
 )
 
 type Controller struct {
@@ -149,40 +146,6 @@ func (c *Controller) CleanExcessClusterOps(cluster *clusterv1alpha1.Cluster, Ops
 	return true, nil
 }
 
-type ConfigProperty struct {
-	ClusterOperationsBackEndLimit string `json:"CLUSTER_OPERATIONS_BACKEND_LIMIT"`
-}
-
-func (config *ConfigProperty) GetClusterOperationsBackEndLimit() int {
-	value, _ := strconv.Atoi(config.ClusterOperationsBackEndLimit)
-	if value <= 0 {
-		klog.Warningf("GetClusterOperationsBackEndLimit and use default value %d", DefaultClusterOperationsBackEndLimit)
-		return DefaultClusterOperationsBackEndLimit
-	}
-	if value >= MaxClusterOperationsBackEndLimit {
-		klog.Warningf("GetClusterOperationsBackEndLimit and use max value %d", MaxClusterOperationsBackEndLimit)
-		return MaxClusterOperationsBackEndLimit
-	}
-	return value
-}
-
-func (c *Controller) FetchKubeanConfigProperty() *ConfigProperty {
-	configData, err := c.ClientSet.CoreV1().ConfigMaps(util.GetCurrentNSOrDefault()).Get(context.Background(), KubeanConfigMapName, metav1.GetOptions{})
-	if err != nil {
-		return &ConfigProperty{}
-	}
-	jsonData, err := json.Marshal(configData.Data)
-	if err != nil {
-		return &ConfigProperty{}
-	}
-	result := &ConfigProperty{}
-	err = json.Unmarshal(jsonData, result)
-	if err != nil {
-		return &ConfigProperty{}
-	}
-	return result
-}
-
 func (c *Controller) Reconcile(ctx context.Context, req controllerruntime.Request) (controllerruntime.Result, error) {
 	cluster := &clusterv1alpha1.Cluster{}
 	if err := c.Client.Get(ctx, req.NamespacedName, cluster); err != nil {
@@ -192,7 +155,7 @@ func (c *Controller) Reconcile(ctx context.Context, req controllerruntime.Reques
 		klog.ErrorS(err, "failed to get cluster", "cluster", req.String())
 		return controllerruntime.Result{RequeueAfter: RequeueAfter}, nil
 	}
-	OpsBackupNum := c.FetchKubeanConfigProperty().GetClusterOperationsBackEndLimit()
+	OpsBackupNum := util.FetchKubeanConfigProperty(c.ClientSet).GetClusterOperationsBackEndLimit()
 	needRequeue, err := c.CleanExcessClusterOps(cluster, OpsBackupNum)
 	if err != nil {
 		klog.ErrorS(err, "failed to clean excess cluster ops", "cluster", cluster.Name)
diff --git a/pkg/controllers/infomanifest/controller.go b/pkg/controllers/infomanifest/controller.go
index 2c70385f8..87b9e3a34 100644
--- a/pkg/controllers/infomanifest/controller.go
+++ b/pkg/controllers/infomanifest/controller.go
@@ -190,11 +190,8 @@ func (c *Controller) UpdateLocalService(manifests []manifestv1alpha1.Manifest) b
 
 // UpdateLocalAvailableImage update image infos into status.
 func (c *Controller) UpdateLocalAvailableImage(manifests []manifestv1alpha1.Manifest) {
+	imageRepo := util.FetchKubeanConfigProperty(c.ClientSet).SprayJobImageRegistry
 	for _, manifest := range manifests {
-		imageRepo := "ghcr.m.daocloud.io"
-		if len(manifest.Spec.LocalService.GetGHCRImageRepo()) != 0 {
-			imageRepo = manifest.Spec.LocalService.GetGHCRImageRepo() // ghcr.io
-		}
 		var newImageName string
 		sprayRelease := manifest.ObjectMeta.Annotations[constants.KeySprayRelease]
 		sprayCommit := manifest.ObjectMeta.Annotations[constants.KeySprayCommit]
diff --git a/pkg/util/cluster.go b/pkg/util/cluster.go
index 4dee83d3e..fc8296b3c 100644
--- a/pkg/util/cluster.go
+++ b/pkg/util/cluster.go
@@ -5,22 +5,24 @@ package util
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"os"
 	"strings"
 
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/kubernetes/scheme"
 
 	"github.com/kubean-io/kubean-api/apis"
 	clusterv1alpha1 "github.com/kubean-io/kubean-api/apis/cluster/v1alpha1"
 	clusteroperationv1alpha1 "github.com/kubean-io/kubean-api/apis/clusteroperation/v1alpha1"
 	localartifactsetv1alpha1 "github.com/kubean-io/kubean-api/apis/localartifactset/v1alpha1"
 	manifestv1alpha1 "github.com/kubean-io/kubean-api/apis/manifest/v1alpha1"
-
-	"k8s.io/apimachinery/pkg/runtime"
-	"k8s.io/client-go/kubernetes/scheme"
+	"github.com/kubean-io/kubean-api/cluster"
+	"github.com/kubean-io/kubean-api/constants"
 )
 
 // aggregatedScheme aggregates Kubernetes and extended schemes.
@@ -119,3 +121,20 @@ func GetCurrentRunningPodName() string {
 	}
 	return "default"
 }
+
+func FetchKubeanConfigProperty(client kubernetes.Interface) *cluster.ConfigProperty {
+	configData, err := client.CoreV1().ConfigMaps(GetCurrentNSOrDefault()).Get(context.Background(), constants.KubeanConfigMapName, metav1.GetOptions{})
+	if err != nil {
+		return &cluster.ConfigProperty{}
+	}
+	jsonData, err := json.Marshal(configData.Data)
+	if err != nil {
+		return &cluster.ConfigProperty{}
+	}
+	result := &cluster.ConfigProperty{}
+	err = json.Unmarshal(jsonData, result)
+	if err != nil {
+		return &cluster.ConfigProperty{}
+	}
+	return result
+}
diff --git a/vendor/github.com/kubean-io/kubean-api/cluster/cluster.go b/vendor/github.com/kubean-io/kubean-api/cluster/cluster.go
new file mode 100644
index 000000000..b3a0c8b4a
--- /dev/null
+++ b/vendor/github.com/kubean-io/kubean-api/cluster/cluster.go
@@ -0,0 +1,25 @@
+package cluster
+
+import (
+	"github.com/kubean-io/kubean-api/constants"
+	"k8s.io/klog/v2"
+	"strconv"
+)
+
+type ConfigProperty struct {
+	ClusterOperationsBackEndLimit string `json:"CLUSTER_OPERATIONS_BACKEND_LIMIT"`
+	SprayJobImageRegistry         string `json:"SPRAY_JOB_IMAGE_REGISTRY"`
+}
+
+func (config *ConfigProperty) GetClusterOperationsBackEndLimit() int {
+	value, _ := strconv.Atoi(config.ClusterOperationsBackEndLimit)
+	if value <= 0 {
+		klog.Warningf("GetClusterOperationsBackEndLimit and use default value %d", constants.DefaultClusterOperationsBackEndLimit)
+		return constants.DefaultClusterOperationsBackEndLimit
+	}
+	if value >= constants.MaxClusterOperationsBackEndLimit {
+		klog.Warningf("GetClusterOperationsBackEndLimit and use max value %d", constants.MaxClusterOperationsBackEndLimit)
+		return constants.MaxClusterOperationsBackEndLimit
+	}
+	return value
+}
diff --git a/vendor/github.com/kubean-io/kubean-api/constants/constants.go b/vendor/github.com/kubean-io/kubean-api/constants/constants.go
index b489ad3ab..22eb7d313 100644
--- a/vendor/github.com/kubean-io/kubean-api/constants/constants.go
+++ b/vendor/github.com/kubean-io/kubean-api/constants/constants.go
@@ -1,17 +1,22 @@
 package constants
 
-const InfoManifestGlobal = "manifest-global"
+const (
+	InfoManifestGlobal = "manifest-global"
 
-const KubeanClusterLabelKey = "clusterName"
+	KubeanClusterLabelKey = "clusterName"
 
-const Hosts_yml = "hosts.yml"
+	Hosts_yml = "hosts.yml"
 
-const Group_vars_yml = "group_vars.yml"
+	Group_vars_yml = "group_vars.yml"
 
-const SSH_privatekey = "ssh-privatekey"
+	SSH_privatekey = "ssh-privatekey"
 
-const KubeanClusterHasCompleted = "hasCompleted"
+	KubeanClusterHasCompleted = "hasCompleted"
 
-const KeySprayRelease = "kubean.io/sprayRelease"
+	KeySprayRelease = "kubean.io/sprayRelease"
+	KeySprayCommit  = "kubean.io/sprayCommit"
 
-const KeySprayCommit = "kubean.io/sprayCommit"
+	KubeanConfigMapName                  = "kubean-config"
+	DefaultClusterOperationsBackEndLimit = 30
+	MaxClusterOperationsBackEndLimit     = 200
+)
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 15ccd939b..de08e1517 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -149,6 +149,7 @@ github.com/kubean-io/kubean-api/apis/cluster/v1alpha1
 github.com/kubean-io/kubean-api/apis/clusteroperation/v1alpha1
 github.com/kubean-io/kubean-api/apis/localartifactset/v1alpha1
 github.com/kubean-io/kubean-api/apis/manifest/v1alpha1
+github.com/kubean-io/kubean-api/cluster
 github.com/kubean-io/kubean-api/constants
 github.com/kubean-io/kubean-api/generated/cluster/clientset/versioned
 github.com/kubean-io/kubean-api/generated/cluster/clientset/versioned/fake


Test patch:
diff --git a/pkg/controllers/cluster/controller_test.go b/pkg/controllers/cluster/controller_test.go
index 5fad4eb3b..c762bea80 100644
--- a/pkg/controllers/cluster/controller_test.go
+++ b/pkg/controllers/cluster/controller_test.go
@@ -7,16 +7,10 @@ import (
 	"context"
 	"fmt"
 	"net/http"
-	"os"
 	"reflect"
 	"testing"
 	"time"
 
-	localartifactsetv1alpha1 "github.com/kubean-io/kubean-api/apis/localartifactset/v1alpha1"
-	manifestv1alpha1 "github.com/kubean-io/kubean-api/apis/manifest/v1alpha1"
-	"github.com/kubean-io/kubean/pkg/util"
-
-	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/meta"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
@@ -33,6 +27,8 @@ import (
 	"github.com/kubean-io/kubean-api/apis"
 	clusterv1alpha1 "github.com/kubean-io/kubean-api/apis/cluster/v1alpha1"
 	clusteroperationv1alpha1 "github.com/kubean-io/kubean-api/apis/clusteroperation/v1alpha1"
+	localartifactsetv1alpha1 "github.com/kubean-io/kubean-api/apis/localartifactset/v1alpha1"
+	manifestv1alpha1 "github.com/kubean-io/kubean-api/apis/manifest/v1alpha1"
 	"github.com/kubean-io/kubean-api/constants"
 	clusterv1alpha1fake "github.com/kubean-io/kubean-api/generated/cluster/clientset/versioned/fake"
 	clusteroperationv1alpha1fake "github.com/kubean-io/kubean-api/generated/clusteroperation/clientset/versioned/fake"
@@ -177,66 +173,6 @@ func TestCompareClusterConditions(t *testing.T) {
 	}
 }
 
-func TestFetchKubeanConfigProperty(t *testing.T) {
-	genController := func() *Controller {
-		return &Controller{
-			Client:              newFakeClient(),
-			ClientSet:           clientsetfake.NewSimpleClientset(),
-			KubeanClusterSet:    clusterv1alpha1fake.NewSimpleClientset(),
-			KubeanClusterOpsSet: clusteroperationv1alpha1fake.NewSimpleClientset(),
-		}
-	}
-	tests := []struct {
-		name       string
-		controller *Controller
-		args       func(controller *Controller)
-		want       int
-	}{
-		{
-			name:       "no config and default value",
-			controller: genController(),
-			args: func(controller *Controller) {
-				// do nothing
-			},
-			want: 30,
-		},
-		{
-			name:       "the config has been set to 25",
-			controller: genController(),
-			args: func(controller *Controller) {
-				os.Setenv("POD_NAMESPACE", "")
-				configMap := &corev1.ConfigMap{}
-				configMap.Name = KubeanConfigMapName
-				configMap.Namespace = util.GetCurrentNSOrDefault()
-				configMap.Data = map[string]string{"CLUSTER_OPERATIONS_BACKEND_LIMIT": "25"}
-				controller.ClientSet.CoreV1().ConfigMaps(util.GetCurrentNSOrDefault()).Create(context.Background(), configMap, metav1.CreateOptions{})
-			},
-			want: 25,
-		},
-		{
-			name:       "the config has been set to 10000",
-			controller: genController(),
-			args: func(controller *Controller) {
-				os.Setenv("POD_NAMESPACE", "")
-				configMap := &corev1.ConfigMap{}
-				configMap.Name = KubeanConfigMapName
-				configMap.Namespace = util.GetCurrentNSOrDefault()
-				configMap.Data = map[string]string{"CLUSTER_OPERATIONS_BACKEND_LIMIT": "10000"}
-				controller.ClientSet.CoreV1().ConfigMaps(util.GetCurrentNSOrDefault()).Create(context.Background(), configMap, metav1.CreateOptions{})
-			},
-			want: 200,
-		},
-	}
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			test.args(test.controller)
-			if test.controller.FetchKubeanConfigProperty().GetClusterOperationsBackEndLimit() != test.want {
-				t.Fatal()
-			}
-		})
-	}
-}
-
 func TestSortClusterOperationsByCreation(t *testing.T) {
 	controller := &Controller{
 		Client:              newFakeClient(),
diff --git a/pkg/controllers/infomanifest/controller_test.go b/pkg/controllers/infomanifest/controller_test.go
index b58d6855d..af61e0335 100644
--- a/pkg/controllers/infomanifest/controller_test.go
+++ b/pkg/controllers/infomanifest/controller_test.go
@@ -7,6 +7,7 @@ import (
 	"context"
 	"fmt"
 	"net/http"
+	"os"
 	"reflect"
 	"testing"
 	"time"
@@ -37,6 +38,7 @@ import (
 	"github.com/kubean-io/kubean-api/constants"
 	localartifactsetv1alpha1fake "github.com/kubean-io/kubean-api/generated/localartifactset/clientset/versioned/fake"
 	manifestv1alpha1fake "github.com/kubean-io/kubean-api/generated/manifest/clientset/versioned/fake"
+	"github.com/kubean-io/kubean/pkg/util"
 )
 
 func newFakeClient() client.Client {
@@ -252,33 +254,27 @@ func Test_UpdateLocalAvailableImage(t *testing.T) {
 		want string
 	}{
 		{
-			name: "update local kubespray image with ghcr.io",
+			name: "update local kubespray image with ghcr.m.daocloud.io",
 			arg: func() {
 				manifest.Spec = manifestv1alpha1.Spec{
 					KubeanVersion: "123",
 				}
 				manifest, _ := controller.InfoManifestClientSet.KubeanV1alpha1().Manifests().Update(context.Background(), manifest, metav1.UpdateOptions{})
-				controller.UpdateLocalAvailableImage([]manifestv1alpha1.Manifest{*manifest})
-			},
-			want: "ghcr.m.daocloud.io/kubean-io/spray-job:123",
-		},
-		{
-			name: "update local kubespray image with local registry",
-			arg: func() {
-				manifest.Spec = manifestv1alpha1.Spec{
-					LocalService: manifestv1alpha1.LocalService{ImageRepo: map[manifestv1alpha1.ImageRepoType]string{
-						"dockerImageRepo": "abc.io",
-						"githubImageRepo": "ghcr.io",
-					}},
-					KubeanVersion: "123456",
-				}
-				manifest, err := controller.InfoManifestClientSet.KubeanV1alpha1().Manifests().Update(context.Background(), manifest, metav1.UpdateOptions{})
-				if err != nil {
-					t.Error(err)
+				os.Setenv("POD_NAMESPACE", "")
+				configMap := &corev1.ConfigMap{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: util.GetCurrentNSOrDefault(),
+						Name:      "kubean-config",
+					},
+					Data: map[string]string{
+						"CLUSTER_OPERATIONS_BACKEND_LIMIT": "10000",
+						"SPRAY_JOB_IMAGE_REGISTRY":         "ghcr.m.daocloud.io",
+					},
 				}
+				controller.ClientSet.CoreV1().ConfigMaps(util.GetCurrentNSOrDefault()).Create(context.Background(), configMap, metav1.CreateOptions{})
 				controller.UpdateLocalAvailableImage([]manifestv1alpha1.Manifest{*manifest})
 			},
-			want: "ghcr.io/kubean-io/spray-job:123456",
+			want: "ghcr.m.daocloud.io/kubean-io/spray-job:123",
 		},
 	}
 
diff --git a/pkg/util/cluster_test.go b/pkg/util/cluster_test.go
index 18895d0ff..9ef33a41b 100644
--- a/pkg/util/cluster_test.go
+++ b/pkg/util/cluster_test.go
@@ -13,6 +13,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/client-go/kubernetes"
 	clientsetfake "k8s.io/client-go/kubernetes/fake"
 
 	"github.com/kubean-io/kubean-api/apis"
@@ -20,6 +21,8 @@ import (
 	clusteroperationv1alpha1 "github.com/kubean-io/kubean-api/apis/clusteroperation/v1alpha1"
 	localartifactsetv1alpha1 "github.com/kubean-io/kubean-api/apis/localartifactset/v1alpha1"
 	manifestv1alpha1 "github.com/kubean-io/kubean-api/apis/manifest/v1alpha1"
+	"github.com/kubean-io/kubean-api/cluster"
+	"github.com/kubean-io/kubean-api/constants"
 )
 
 func TestNewSchema(t *testing.T) {
@@ -203,3 +206,105 @@ func TestUpdateOwnReference(t *testing.T) {
 		})
 	}
 }
+
+func TestFetchKubeanConfigProperty(t *testing.T) {
+	genClient := func() kubernetes.Interface {
+		return clientsetfake.NewSimpleClientset()
+	}
+	tests := []struct {
+		name   string
+		client kubernetes.Interface
+		args   func(client kubernetes.Interface)
+		want   cluster.ConfigProperty
+	}{
+		{
+			name:   "no config and default value",
+			client: genClient(),
+			args: func(client kubernetes.Interface) {
+				// do nothing
+			},
+			want: cluster.ConfigProperty{},
+		},
+		{
+			name:   "the config has been set to 10000",
+			client: genClient(),
+			args: func(client kubernetes.Interface) {
+				os.Setenv("POD_NAMESPACE", "")
+				configMap := &corev1.ConfigMap{}
+				configMap.Name = constants.KubeanConfigMapName
+				configMap.Namespace = GetCurrentNSOrDefault()
+				configMap.Data = map[string]string{"CLUSTER_OPERATIONS_BACKEND_LIMIT": "10000"}
+				client.CoreV1().ConfigMaps(GetCurrentNSOrDefault()).Create(context.Background(), configMap, metav1.CreateOptions{})
+			},
+			want: cluster.ConfigProperty{
+				ClusterOperationsBackEndLimit: "10000",
+				SprayJobImageRegistry:         "",
+			},
+		},
+		{
+			name:   "the config has been set to 10000",
+			client: genClient(),
+			args: func(client kubernetes.Interface) {
+				os.Setenv("POD_NAMESPACE", "")
+				configMap := &corev1.ConfigMap{}
+				configMap.Name = constants.KubeanConfigMapName
+				configMap.Namespace = GetCurrentNSOrDefault()
+				configMap.Data = map[string]string{"CLUSTER_OPERATIONS_BACKEND_LIMIT": "10000", "SPRAY_JOB_IMAGE_REGISTRY": "ghcr.io"}
+				client.CoreV1().ConfigMaps(GetCurrentNSOrDefault()).Create(context.Background(), configMap, metav1.CreateOptions{})
+			},
+			want: cluster.ConfigProperty{
+				ClusterOperationsBackEndLimit: "10000",
+				SprayJobImageRegistry:         "ghcr.io",
+			},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			test.args(test.client)
+			result := FetchKubeanConfigProperty(test.client)
+			if result.ClusterOperationsBackEndLimit != test.want.ClusterOperationsBackEndLimit || result.SprayJobImageRegistry != test.want.SprayJobImageRegistry {
+				t.Fatal()
+			}
+		})
+	}
+}
+
+func TestGetClusterOperationsBackEndLimit(t *testing.T) {
+	tests := []struct {
+		name     string
+		limit    string
+		expected int
+	}{
+		{
+			name:     "valid limit",
+			limit:    "5",
+			expected: 5,
+		},
+		{
+			name:     "invalid limit, use default",
+			limit:    "0",
+			expected: constants.DefaultClusterOperationsBackEndLimit,
+		},
+		{
+			name:     "invalid limit, use max",
+			limit:    "300",
+			expected: constants.MaxClusterOperationsBackEndLimit,
+		},
+		{
+			name:     "invalid format, use default",
+			limit:    "abc",
+			expected: constants.DefaultClusterOperationsBackEndLimit,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			config := &cluster.ConfigProperty{
+				ClusterOperationsBackEndLimit: tt.limit,
+			}
+			if got := config.GetClusterOperationsBackEndLimit(); got != tt.expected {
+				t.Errorf("GetClusterOperationsBackEndLimit() = %v, want %v", got, tt.expected)
+			}
+		})
+	}
+}