diff --git a/deploy/cluster-manager/chart/config.go b/deploy/cluster-manager/chart/config.go index d40b87c58..f7ab8eeac 100644 --- a/deploy/cluster-manager/chart/config.go +++ b/deploy/cluster-manager/chart/config.go @@ -17,6 +17,8 @@ var ChartFiles embed.FS const ChartName = "cluster-manager" type ChartConfig struct { + // CreateNamespace is used in the render function to append the release ns in the objects. + CreateNamespace bool `json:"createNamespace,omitempty"` // ReplicaCount is the replicas for the clusterManager operator deployment. ReplicaCount int `json:"replicaCount,omitempty"` // Images is the configurations for all images used in operator deployment and clusterManager CR. diff --git a/deploy/klusterlet/chart/config.go b/deploy/klusterlet/chart/config.go index 3ade2f5b5..5b9eccbca 100644 --- a/deploy/klusterlet/chart/config.go +++ b/deploy/klusterlet/chart/config.go @@ -17,6 +17,8 @@ var ChartFiles embed.FS const ChartName = "klusterlet" type ChartConfig struct { + // CreateNamespace is used in the render function to append the release ns in the objects. + CreateNamespace bool `json:"createNamespace,omitempty"` // ReplicaCount is the replicas for the klusterlet operator deployment. ReplicaCount int `json:"replicaCount,omitempty"` // Images is the configurations for all images used in operator deployment and klusterlet CR. diff --git a/pkg/operator/helpers/chart/render.go b/pkg/operator/helpers/chart/render.go index 7d657e574..e75d77805 100644 --- a/pkg/operator/helpers/chart/render.go +++ b/pkg/operator/helpers/chart/render.go @@ -11,10 +11,12 @@ import ( "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/engine" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/json" - "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/klog/v2" + "sigs.k8s.io/yaml" clustermanagerchart "open-cluster-management.io/ocm/deploy/cluster-manager/chart" klusterletchart "open-cluster-management.io/ocm/deploy/klusterlet/chart" @@ -33,8 +35,24 @@ func NewDefaultKlusterletChartConfig() *klusterletchart.ChartConfig { } } -func RenderChart[T *clustermanagerchart.ChartConfig | *klusterletchart.ChartConfig](config T, - namespace string, chartName string, fs embed.FS) ([][]byte, error) { +func RenderClusterManagerChart(config *clustermanagerchart.ChartConfig, namespace string) ([][]byte, error) { + if namespace == "" { + return nil, fmt.Errorf("cluster manager chart namespace is required") + } + return renderChart(config, namespace, config.CreateNamespace, + clustermanagerchart.ChartName, clustermanagerchart.ChartFiles) +} + +func RenderKlusterletChart(config *klusterletchart.ChartConfig, namespace string) ([][]byte, error) { + if namespace == "" { + return nil, fmt.Errorf("klusterlet chart namespace is required") + } + return renderChart(config, namespace, config.CreateNamespace, + klusterletchart.ChartName, klusterletchart.ChartFiles) +} + +func renderChart[T *clustermanagerchart.ChartConfig | *klusterletchart.ChartConfig](config T, + namespace string, createNamespace bool, chartName string, fs embed.FS) ([][]byte, error) { // chartName is the prefix of chart path here operatorChart, err := LoadChart(fs, chartName) if err != nil { @@ -63,7 +81,18 @@ func RenderChart[T *clustermanagerchart.ChartConfig | *klusterletchart.ChartConf return nil, fmt.Errorf("error rendering cluster manager chart: %v", err) } - return rawObjects, nil + // make sure the ns object is at the top of slice when createNamespace is true. + rstObjects := [][]byte{} + if createNamespace { + nsObj, err := newNamespaceRawObject(namespace) + if err != nil { + return nil, err + } + rstObjects = [][]byte{nsObj} + } + rstObjects = append(rstObjects, rawObjects...) + + return rstObjects, nil } func getFiles(manifestFS embed.FS) ([]string, error) { @@ -172,3 +201,17 @@ func renderManifests(chart *chart.Chart, values chartutil.Values) ([][]byte, err } return rawObjects, nil } + +func newNamespaceRawObject(namespace string) ([]byte, error) { + ns := &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Namespace", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } + + return yaml.Marshal(ns) +} diff --git a/pkg/operator/helpers/chart/render_test.go b/pkg/operator/helpers/chart/render_test.go index acc2deae5..1e1e2c6dc 100644 --- a/pkg/operator/helpers/chart/render_test.go +++ b/pkg/operator/helpers/chart/render_test.go @@ -84,6 +84,17 @@ func TestClusterManagerConfig(t *testing.T) { }, expectedObjCnt: 7, }, + { + name: "create namespace", + namespace: "multicluster-engine", + chartConfig: func() *clustermanagerchart.ChartConfig { + config := NewDefaultClusterManagerChartConfig() + config.CreateBootstrapToken = true + config.CreateNamespace = true + return config + }, + expectedObjCnt: 10, + }, } for _, c := range cases { @@ -99,7 +110,7 @@ func TestClusterManagerConfig(t *testing.T) { version = config.Images.Tag } - objects, err := RenderChart(config, c.namespace, clustermanagerchart.ChartName, clustermanagerchart.ChartFiles) + objects, err := RenderClusterManagerChart(config, c.namespace) if err != nil { t.Errorf("error rendering chart: %v", err) } @@ -115,6 +126,10 @@ func TestClusterManagerConfig(t *testing.T) { } outputObjs = append(outputObjs, obj) switch object := obj.(type) { + case *corev1.Namespace: + if object.Name != c.namespace { + t.Errorf("expected namespace %s, got %s", c.namespace, object.Name) + } case *appsv1.Deployment: if object.Namespace != c.namespace { t.Errorf("expected namespace is %s, but got %s", c.namespace, object.Namespace) @@ -135,7 +150,6 @@ func TestClusterManagerConfig(t *testing.T) { t.Errorf("failed to render images") } } - } // output is for debug @@ -147,7 +161,6 @@ func TestClusterManagerConfig(t *testing.T) { } func TestKlusterletConfig(t *testing.T) { - cases := []struct { name string namespace string @@ -213,6 +226,18 @@ func TestKlusterletConfig(t *testing.T) { }, expectedObjCnt: 2, }, + { + name: "create namespace", + namespace: "open-cluster-management", + chartConfig: func() *klusterletchart.ChartConfig { + config := NewDefaultKlusterletChartConfig() + config.Klusterlet.ClusterName = "testCluster" + config.Klusterlet.Mode = operatorv1.InstallModeSingleton + config.CreateNamespace = true + return config + }, + expectedObjCnt: 7, + }, } for _, c := range cases { @@ -227,7 +252,7 @@ func TestKlusterletConfig(t *testing.T) { version = config.Images.Tag } - objects, err := RenderChart(config, c.namespace, klusterletchart.ChartName, klusterletchart.ChartFiles) + objects, err := RenderKlusterletChart(config, c.namespace) if err != nil { t.Errorf("error rendering chart: %v", err) } @@ -243,6 +268,10 @@ func TestKlusterletConfig(t *testing.T) { } outputObjs = append(outputObjs, obj) switch object := obj.(type) { + case *corev1.Namespace: + if object.Name != c.namespace { + t.Errorf("expected namespace %s, got %s", c.namespace, object.Name) + } case *appsv1.Deployment: if object.Namespace != c.namespace { t.Errorf("expected namespace is %s, but got %s", c.namespace, object.Namespace) @@ -306,7 +335,6 @@ func TestKlusterletConfig(t *testing.T) { fmt.Sprintf("open-cluster-management-%s", object.Spec.ClusterName), object.Spec.Namespace) } } - } }