diff --git a/go.mod b/go.mod index 679819d..47229db 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( k8s.io/klog/v2 v2.100.1 k8s.io/kubectl v0.28.4 k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 + sigs.k8s.io/yaml v1.4.0 ) require ( @@ -143,5 +144,4 @@ require ( sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index fdd7f8e..0769c5f 100644 --- a/go.sum +++ b/go.sum @@ -529,5 +529,5 @@ sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/helmcli/release.go b/helmcli/release.go index 78b5f5a..9db6d06 100644 --- a/helmcli/release.go +++ b/helmcli/release.go @@ -6,6 +6,7 @@ import ( "fmt" "time" + "gopkg.in/yaml.v2" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/storage/driver" @@ -40,6 +41,39 @@ func StringPathValuesApplier(values ...string) ValuesApplier { } } +func YAMLValuesApplier(yamlValues string) (ValuesApplier, error) { + values := make(map[string]interface{}) + err := yaml.Unmarshal([]byte(yamlValues), &values) + if err != nil { + return nil, err + } + return func(to map[string]interface{}) error { + for k, v := range values { + // If 'to' doesn't have key 'k' + if _, checkKey := to[k]; !checkKey { + to[k] = v + } else { + // If 'to' has key 'k' + switch v := v.(type) { + case map[string]interface{}: + // If 'v' is of type map[string]interface{} + if toMap, checkKey := to[k].(map[string]interface{}); checkKey { + for subKey, subValue := range v { + toMap[subKey] = subValue + } + } else { + to[k] = v + } + default: + // If 'v' is not of type map[string]interface{} + to[k] = v + } + } + } + return nil + }, nil +} + // ReleaseCli is a client to deploy helm chart with secret storage. type ReleaseCli struct { namespace string diff --git a/virtualcluster/nodes_common.go b/virtualcluster/nodes_common.go index 63d8a2c..17abf15 100644 --- a/virtualcluster/nodes_common.go +++ b/virtualcluster/nodes_common.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/Azure/kperf/helmcli" + "sigs.k8s.io/yaml" ) var ( @@ -98,12 +99,33 @@ func WithNodepoolNodeControllerAffinity(nodeSelectors map[string][]string) Nodep // NOTE: Please align with ../manifests/virtualcluster/nodes/values.yaml // // TODO: Add YAML ValuesAppliers to support array type. -func (cfg *nodepoolConfig) toHelmValuesAppliers(nodepoolName string) []helmcli.ValuesApplier { +func (cfg *nodepoolConfig) toHelmValuesAppliers(nodepoolName string) ([]helmcli.ValuesApplier, error) { res := make([]string, 0, 4) res = append(res, fmt.Sprintf("name=%s", nodepoolName)) res = append(res, fmt.Sprintf("replicas=%d", cfg.count)) res = append(res, fmt.Sprintf("cpu=%d", cfg.cpu)) res = append(res, fmt.Sprintf("memory=%d", cfg.memory)) - return []helmcli.ValuesApplier{helmcli.StringPathValuesApplier(res...)} + + stringPathApplier := helmcli.StringPathValuesApplier(res...) + + // Marshal labels and nodeSelectors to YAML + labelsYaml, err := yaml.Marshal(cfg.labels) + if err != nil { + return nil, err + } + nodeSelectorsYaml, err := yaml.Marshal(cfg.nodeSelectors) + if err != nil { + return nil, err + } + // Create YAML ValuesAppliers for labels and nodeSelectors + labelsApplier, err := helmcli.YAMLValuesApplier(string(labelsYaml)) + if err != nil { + return nil, err + } + nodeSelectorsApplier, err := helmcli.YAMLValuesApplier(string(nodeSelectorsYaml)) + if err != nil { + return nil, err + } + return []helmcli.ValuesApplier{stringPathApplier, labelsApplier, nodeSelectorsApplier}, nil } diff --git a/virtualcluster/nodes_create.go b/virtualcluster/nodes_create.go index 57e0e1b..1404faf 100644 --- a/virtualcluster/nodes_create.go +++ b/virtualcluster/nodes_create.go @@ -39,13 +39,17 @@ func CreateNodepool(ctx context.Context, kubeconfigPath string, nodepoolName str return fmt.Errorf("failed to load virtual node chart: %w", err) } + cfgValues, err := cfg.toHelmValuesAppliers(nodepoolName) + if err != nil { + return fmt.Errorf("failed to convert to helm values: %w", err) + } releaseCli, err := helmcli.NewReleaseCli( kubeconfigPath, virtualnodeReleaseNamespace, nodepoolName, ch, virtualnodeReleaseLabels, - cfg.toHelmValuesAppliers(nodepoolName)..., + cfgValues..., ) if err != nil { return fmt.Errorf("failed to create helm release client: %w", err)