Skip to content

Commit ec50a07

Browse files
committed
feat: add --klusterlet-file flag for arbitrary overrides during join/upgrade
Signed-off-by: Tyler Gillson <[email protected]>
1 parent e26cc6b commit ec50a07

File tree

8 files changed

+199
-1
lines changed

8 files changed

+199
-1
lines changed

pkg/cmd/join/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,6 @@ func NewCmd(clusteradmFlags *genericclioptionsclusteradm.ClusteradmFlags, stream
8787
cmd.Flags().StringVar(&o.hubClusterArn, "hub-cluster-arn", "", "The arn of the hub cluster(i.e. EKS cluster) to which managed-cluster will join")
8888
cmd.Flags().StringVar(&o.managedClusterArn, "managed-cluster-arn", "", "The arn of the managed cluster(i.e. EKS cluster) which will be joining the hub")
8989
cmd.Flags().StringArrayVar(&o.klusterletAnnotations, "klusterlet-annotation", []string{}, fmt.Sprintf("Annotations to set on the ManagedCluster, in key=value format. Note: each key will be automatically prefixed with '%s/', if not set.", operatorv1.ClusterAnnotationsKeyPrefix))
90+
cmd.Flags().StringVar(&o.klusterletFile, "klusterlet-file", "", "The path to a YAML file containing a Klusterlet custom resource. The spec from the file will be merged into the klusterlet chart configuration.")
9091
return cmd
9192
}

pkg/cmd/join/exec.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ import (
3636
clusterv1 "open-cluster-management.io/api/cluster/v1"
3737
ocmfeature "open-cluster-management.io/api/feature"
3838
operatorv1 "open-cluster-management.io/api/operator/v1"
39+
3940
"open-cluster-management.io/clusteradm/pkg/cmd/join/preflight"
4041
genericclioptionsclusteradm "open-cluster-management.io/clusteradm/pkg/genericclioptions"
4142
"open-cluster-management.io/clusteradm/pkg/helpers"
43+
"open-cluster-management.io/clusteradm/pkg/helpers/klusterlet"
4244
preflightinterface "open-cluster-management.io/clusteradm/pkg/helpers/preflight"
4345
"open-cluster-management.io/clusteradm/pkg/helpers/printer"
4446
"open-cluster-management.io/clusteradm/pkg/helpers/reader"
@@ -247,8 +249,15 @@ func (o *Options) complete(cmd *cobra.Command, args []string) (err error) {
247249
if err := o.capiOptions.Complete(cmd, args); err != nil {
248250
return err
249251
}
250-
return nil
251252

253+
// If a klusterlet file was provided, read and merge it
254+
if o.klusterletFile != "" {
255+
if err := klusterlet.MergeKlusterletFile(o.klusterletFile, o.klusterletChartConfig); err != nil {
256+
return fmt.Errorf("failed to merge klusterlet file: %v", err)
257+
}
258+
}
259+
260+
return nil
252261
}
253262

254263
func (o *Options) validate() error {

pkg/cmd/join/options.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ type Options struct {
100100

101101
// Annotations for registration controller to set on the ManagedCluster
102102
klusterletAnnotations []string
103+
104+
// klusterletFile is the path to a YAML file containing a Klusterlet custom resource
105+
// whose spec will be merged into the klusterletChartConfig
106+
klusterletFile string
103107
}
104108

105109
func newOptions(clusteradmFlags *genericclioptionsclusteradm.ClusteradmFlags, streams genericiooptions.IOStreams) *Options {

pkg/cmd/upgrade/klusterlet/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,6 @@ func NewCmd(clusteradmFlags *genericclioptionsclusteradm.ClusteradmFlags, stream
5151
"Path to a file containing version bundle overrides. Optional. If provided, overrides component versions within the selected version bundle.")
5252
cmd.Flags().BoolVar(&o.wait, "wait", false,
5353
"If set, the command will initialize the OCM control plan in foreground.")
54+
cmd.Flags().StringVar(&o.klusterletFile, "klusterlet-file", "", "The path to a YAML file containing a Klusterlet custom resource. The spec from the file will be merged into the klusterlet chart configuration.")
5455
return cmd
5556
}

pkg/cmd/upgrade/klusterlet/exec.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import (
1010
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1111
"k8s.io/klog/v2"
1212
operatorclient "open-cluster-management.io/api/client/operator/clientset/versioned"
13+
1314
"open-cluster-management.io/clusteradm/pkg/helpers"
15+
"open-cluster-management.io/clusteradm/pkg/helpers/klusterlet"
1416
"open-cluster-management.io/clusteradm/pkg/helpers/reader"
1517
"open-cluster-management.io/clusteradm/pkg/helpers/wait"
1618
"open-cluster-management.io/clusteradm/pkg/version"
@@ -72,6 +74,13 @@ func (o *Options) complete(_ *cobra.Command, _ []string) (err error) {
7274
o.klusterletChartConfig.Klusterlet.WorkConfiguration = *k.Spec.WorkConfiguration
7375
}
7476

77+
// If a klusterlet file was provided, read and merge it
78+
if o.klusterletFile != "" {
79+
if err := klusterlet.MergeKlusterletFile(o.klusterletFile, o.klusterletChartConfig); err != nil {
80+
return fmt.Errorf("failed to merge klusterlet file: %v", err)
81+
}
82+
}
83+
7584
return nil
7685
}
7786

pkg/cmd/upgrade/klusterlet/options.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ type Options struct {
2323
//If set, the command will hold until the OCM control plane initialized
2424
wait bool
2525

26+
// klusterletFile is the path to a YAML file containing a Klusterlet custom resource
27+
// whose spec will be merged into the klusterletChartConfig
28+
klusterletFile string
29+
2630
Streams genericiooptions.IOStreams
2731
}
2832

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright Contributors to the Open Cluster Management project
2+
package klusterlet
3+
4+
import (
5+
"fmt"
6+
"os"
7+
"reflect"
8+
9+
"github.com/ghodss/yaml"
10+
"k8s.io/klog/v2"
11+
operatorv1 "open-cluster-management.io/api/operator/v1"
12+
"open-cluster-management.io/ocm/pkg/operator/helpers/chart"
13+
)
14+
15+
// MergeKlusterletFile reads a Klusterlet YAML file and merges its spec into the provided klusterletChartConfig.
16+
func MergeKlusterletFile(klusterletFile string, klusterletChartConfig *chart.KlusterletChartConfig) error {
17+
yamlBytes, err := os.ReadFile(klusterletFile)
18+
if err != nil {
19+
return fmt.Errorf("failed to read klusterlet file %s: %v", klusterletFile, err)
20+
}
21+
var klusterlet operatorv1.Klusterlet
22+
if err := yaml.Unmarshal(yamlBytes, &klusterlet); err != nil {
23+
return fmt.Errorf("failed to parse klusterlet YAML: %v", err)
24+
}
25+
26+
// merge the spec fields into the chart config
27+
spec := klusterlet.Spec
28+
29+
if spec.Namespace != "" {
30+
klusterletChartConfig.Klusterlet.Namespace = spec.Namespace
31+
}
32+
if spec.ClusterName != "" {
33+
klusterletChartConfig.Klusterlet.ClusterName = spec.ClusterName
34+
}
35+
if spec.DeployOption.Mode != "" {
36+
klusterletChartConfig.Klusterlet.Mode = spec.DeployOption.Mode
37+
}
38+
if len(spec.ExternalServerURLs) > 0 {
39+
klusterletChartConfig.Klusterlet.ExternalServerURLs = spec.ExternalServerURLs
40+
}
41+
if !reflect.DeepEqual(spec.NodePlacement, operatorv1.NodePlacement{}) {
42+
klusterletChartConfig.Klusterlet.NodePlacement = spec.NodePlacement
43+
}
44+
if spec.RegistrationConfiguration != nil {
45+
klusterletChartConfig.Klusterlet.RegistrationConfiguration = *spec.RegistrationConfiguration
46+
}
47+
if spec.WorkConfiguration != nil {
48+
klusterletChartConfig.Klusterlet.WorkConfiguration = *spec.WorkConfiguration
49+
}
50+
if spec.ResourceRequirement != nil {
51+
klusterletChartConfig.Klusterlet.ResourceRequirement = spec.ResourceRequirement
52+
}
53+
if spec.PriorityClassName != "" {
54+
klusterletChartConfig.PriorityClassName = spec.PriorityClassName
55+
}
56+
57+
klog.V(2).InfoS("Successfully merged klusterlet file into klusterlet chart config", "file", klusterletFile)
58+
return nil
59+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright Contributors to the Open Cluster Management project
2+
package klusterlet
3+
4+
import (
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
operatorv1 "open-cluster-management.io/api/operator/v1"
10+
"open-cluster-management.io/ocm/pkg/operator/helpers/chart"
11+
)
12+
13+
func TestMergeKlusterletFile(t *testing.T) {
14+
// Create a temporary klusterlet YAML file
15+
tmpDir := t.TempDir()
16+
klusterletFile := filepath.Join(tmpDir, "test-klusterlet.yaml")
17+
18+
klusterletYAML := `apiVersion: operator.open-cluster-management.io/v1
19+
kind: Klusterlet
20+
metadata:
21+
name: test-klusterlet
22+
spec:
23+
clusterName: test-cluster
24+
namespace: test-namespace
25+
deployOption:
26+
mode: Singleton
27+
registrationConfiguration:
28+
clientCertExpirationSeconds: 86400
29+
featureGates:
30+
- feature: DefaultClusterSet
31+
mode: Enable
32+
workConfiguration:
33+
featureGates:
34+
- feature: ManifestWorkReplicaSet
35+
mode: Enable
36+
resourceRequirement:
37+
type: ResourceRequirement
38+
resourceRequirements:
39+
requests:
40+
cpu: 100m
41+
memory: 128Mi
42+
priorityClassName: system-cluster-critical`
43+
44+
if err := os.WriteFile(klusterletFile, []byte(klusterletYAML), 0644); err != nil {
45+
t.Fatalf("Failed to write test klusterlet file: %v", err)
46+
}
47+
48+
// Create a default chart config
49+
chartConfig := chart.NewDefaultKlusterletChartConfig()
50+
51+
// Test the merge function
52+
err := MergeKlusterletFile(klusterletFile, chartConfig)
53+
if err != nil {
54+
t.Fatalf("MergeKlusterletFile failed: %v", err)
55+
}
56+
57+
// Verify the merge results
58+
if chartConfig.Klusterlet.ClusterName != "test-cluster" {
59+
t.Errorf("Expected ClusterName to be 'test-cluster', got '%s'", chartConfig.Klusterlet.ClusterName)
60+
}
61+
62+
if chartConfig.Klusterlet.Namespace != "test-namespace" {
63+
t.Errorf("Expected Namespace to be 'test-namespace', got '%s'", chartConfig.Klusterlet.Namespace)
64+
}
65+
66+
if chartConfig.Klusterlet.Mode != operatorv1.InstallModeSingleton {
67+
t.Errorf("Expected Mode to be 'Singleton', got '%s'", chartConfig.Klusterlet.Mode)
68+
}
69+
70+
if chartConfig.Klusterlet.RegistrationConfiguration.ClientCertExpirationSeconds != 86400 {
71+
t.Errorf("Expected ClientCertExpirationSeconds to be 86400, got %d", chartConfig.Klusterlet.RegistrationConfiguration.ClientCertExpirationSeconds)
72+
}
73+
74+
if len(chartConfig.Klusterlet.RegistrationConfiguration.FeatureGates) != 1 {
75+
t.Errorf("Expected 1 registration feature gate, got %d", len(chartConfig.Klusterlet.RegistrationConfiguration.FeatureGates))
76+
}
77+
78+
if len(chartConfig.Klusterlet.WorkConfiguration.FeatureGates) != 1 {
79+
t.Errorf("Expected 1 work feature gate, got %d", len(chartConfig.Klusterlet.WorkConfiguration.FeatureGates))
80+
}
81+
82+
if chartConfig.PriorityClassName != "system-cluster-critical" {
83+
t.Errorf("Expected PriorityClassName to be 'system-cluster-critical', got '%s'", chartConfig.PriorityClassName)
84+
}
85+
}
86+
87+
func TestMergeKlusterletFileNotFound(t *testing.T) {
88+
chartConfig := chart.NewDefaultKlusterletChartConfig()
89+
90+
err := MergeKlusterletFile("/nonexistent/file.yaml", chartConfig)
91+
if err == nil {
92+
t.Error("Expected error for non-existent file, got nil")
93+
}
94+
}
95+
96+
func TestMergeKlusterletFileInvalidYAML(t *testing.T) {
97+
tmpDir := t.TempDir()
98+
klusterletFile := filepath.Join(tmpDir, "invalid-klusterlet.yaml")
99+
100+
invalidYAML := `invalid: yaml: content: [unclosed`
101+
if err := os.WriteFile(klusterletFile, []byte(invalidYAML), 0644); err != nil {
102+
t.Fatalf("Failed to write invalid klusterlet file: %v", err)
103+
}
104+
105+
chartConfig := chart.NewDefaultKlusterletChartConfig()
106+
107+
err := MergeKlusterletFile(klusterletFile, chartConfig)
108+
if err == nil {
109+
t.Error("Expected error for invalid YAML, got nil")
110+
}
111+
}

0 commit comments

Comments
 (0)