Skip to content

Commit

Permalink
add protected/dynamic values to charts before reconcile (#163)
Browse files Browse the repository at this point in the history
* add protected/dynamic values to charts before reconcile

* remove protected values handling

* update unit tests

* use logger
  • Loading branch information
laverya authored May 3, 2024
1 parent 97f030d commit a67d252
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 331 deletions.
143 changes: 62 additions & 81 deletions controllers/helm.go
Original file line number Diff line number Diff line change
@@ -1,71 +1,48 @@
package controllers

import (
"context"
"fmt"
"path/filepath"

"github.com/k0sproject/dig"
k0shelm "github.com/k0sproject/k0s/pkg/apis/helm/v1beta1"
k0sv1beta1 "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1"
"github.com/ohler55/ojg/jp"
"github.com/ohler55/ojg/oj"
ectypes "github.com/replicatedhq/embedded-cluster-kinds/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/yaml"

"github.com/replicatedhq/embedded-cluster-kinds/apis/v1beta1"
)

const DEFAULT_VENDOR_CHART_ORDER = 10

// MergeValues takes two helm values in the form of dig.Mapping{} and a list of values (in jsonpath notation) to not override
// and combines the values. it returns the resultant yaml string
func MergeValues(oldValues, newValues string, protectedValues []string) (string, error) {

func setHelmValue(valuesYaml, path, newValue string) (string, error) {
newValuesMap := dig.Mapping{}
if err := yaml.Unmarshal([]byte(newValues), &newValuesMap); err != nil {
return "", fmt.Errorf("failed to unmarshal new chart values: %w", err)
if err := yaml.Unmarshal([]byte(valuesYaml), &newValuesMap); err != nil {
return "", fmt.Errorf("failed to unmarshal initial values: %w", err)
}

// merge the known fields from the current chart values to the new chart values
for _, path := range protectedValues {
x, err := jp.ParseString(path)
if err != nil {
return "", fmt.Errorf("failed to parse json path: %w", err)
}

valuesJson, err := yaml.YAMLToJSON([]byte(oldValues))
if err != nil {
return "", fmt.Errorf("failed to convert yaml to json: %w", err)
}

obj, err := oj.ParseString(string(valuesJson))
if err != nil {
return "", fmt.Errorf("failed to parse json: %w", err)
}

value := x.Get(obj)

// if the value is empty, skip it
if len(value) < 1 {
continue
}
x, err := jp.ParseString(path)
if err != nil {
return "", fmt.Errorf("failed to parse json path %q: %w", path, err)
}

err = x.Set(newValuesMap, value[0])
if err != nil {
return "", fmt.Errorf("failed to set json path: %w", err)
}
err = x.Set(newValuesMap, newValue)
if err != nil {
return "", fmt.Errorf("failed to set json path %q to %q: %w", path, newValue, err)
}

newValuesYaml, err := yaml.Marshal(newValuesMap)
if err != nil {
return "", fmt.Errorf("failed to marshal new chart values: %w", err)
return "", fmt.Errorf("failed to marshal updated values: %w", err)
}
return string(newValuesYaml), nil

}

// merge the default helm charts and repositories (from meta.Configs) with vendor helm charts (from in.Spec.Config.Extensions.Helm)
func mergeHelmConfigs(meta *ectypes.ReleaseMetadata, in *v1beta1.Installation) *k0sv1beta1.HelmExtensions {
func mergeHelmConfigs(ctx context.Context, meta *ectypes.ReleaseMetadata, in *v1beta1.Installation) *k0sv1beta1.HelmExtensions {
// merge default helm charts (from meta.Configs) with vendor helm charts (from in.Spec.Config.Extensions.Helm)
combinedConfigs := &k0sv1beta1.HelmExtensions{ConcurrencyLevel: 1}
if meta != nil {
Expand Down Expand Up @@ -100,6 +77,9 @@ func mergeHelmConfigs(meta *ectypes.ReleaseMetadata, in *v1beta1.Installation) *
combinedConfigs.Repositories = append(combinedConfigs.Repositories, meta.BuiltinConfigs["velero"].Repositories...)
}

// update the infrastructure charts from the install spec
combinedConfigs.Charts = updateInfraChartsFromInstall(ctx, in, combinedConfigs.Charts)

// k0s sorts order numbers alphabetically because they're used in file names,
// which means double digits can be sorted before single digits (e.g. "10" comes before "5").
// We add 100 to the order of each chart to work around this.
Expand All @@ -109,6 +89,51 @@ func mergeHelmConfigs(meta *ectypes.ReleaseMetadata, in *v1beta1.Installation) *
return combinedConfigs
}

// update the 'admin-console' and 'embedded-cluster-operator' charts to add cluster ID, binary name, and airgap status
func updateInfraChartsFromInstall(ctx context.Context, in *v1beta1.Installation, charts k0sv1beta1.ChartsSettings) k0sv1beta1.ChartsSettings {
log := ctrl.LoggerFrom(ctx)

if in == nil {
return charts
}

for i, chart := range charts {
if chart.Name == "admin-console" {
// admin-console has "embeddedClusterID" and "isAirgap" as dynamic values
newVals, err := setHelmValue(chart.Values, "embeddedClusterID", in.Spec.ClusterID)
if err != nil {
log.Info("failed to set embeddedClusterID for %s: %v", chart.Name, err)
continue
}

newVals, err = setHelmValue(newVals, "isAirgap", fmt.Sprintf("%t", in.Spec.AirGap))
if err != nil {
log.Info("failed to set isAirgap for %s: %v", chart.Name, err)
continue
}

charts[i].Values = newVals
}
if chart.Name == "embedded-cluster-operator" {
// embedded-cluster-operator has "embeddedBinaryName" and "embeddedClusterID" as dynamic values
newVals, err := setHelmValue(chart.Values, "embeddedBinaryName", in.Spec.BinaryName)
if err != nil {
log.Info("failed to set embeddedBinaryName for %s: %v", err)
continue
}

newVals, err = setHelmValue(newVals, "embeddedClusterID", in.Spec.ClusterID)
if err != nil {
log.Info("failed to set embeddedClusterID for %s: %v", err)
continue
}

charts[i].Values = newVals
}
}
return charts
}

// detect if the charts currently installed in the cluster (currentConfigs) match the desired charts (combinedConfigs)
func detectChartDrift(combinedConfigs, currentConfigs *k0sv1beta1.HelmExtensions) (bool, []string, error) {
chartDrift := false
Expand Down Expand Up @@ -250,50 +275,6 @@ func applyUserProvidedAddonOverrides(in *v1beta1.Installation, combinedConfigs *
return patchedConfigs, nil
}

// merge the helmcharts in the cluster with the charts we desire to be in the cluster
// if the chart is already in the cluster, merge the values
func generateDesiredCharts(meta *ectypes.ReleaseMetadata, clusterconfig k0sv1beta1.ClusterConfig, combinedConfigs *k0sv1beta1.HelmExtensions) ([]k0sv1beta1.Chart, error) {
// get the protected values from the release metadata
protectedValues := map[string][]string{}
if meta != nil && meta.Protected != nil {
protectedValues = meta.Protected
}

// TODO - apply unsupported override from installation config
finalConfigs := map[string]k0sv1beta1.Chart{}
// include charts in the final spec that are already in the cluster (with merged values)
for _, chart := range clusterconfig.Spec.Extensions.Helm.Charts {
for _, newChart := range combinedConfigs.Charts {
// check if we can skip this chart
_, ok := protectedValues[chart.Name]
if chart.Name != newChart.Name || !ok {
continue
}
// if we have known fields, we need to merge them forward
newValuesYaml, err := MergeValues(chart.Values, newChart.Values, protectedValues[chart.Name])
if err != nil {
return nil, fmt.Errorf("failed to merge chart values: %w", err)
}
newChart.Values = newValuesYaml
finalConfigs[newChart.Name] = newChart
break
}
}
// include new charts in the final spec that are not yet in the cluster
for _, newChart := range combinedConfigs.Charts {
if _, ok := finalConfigs[newChart.Name]; !ok {
finalConfigs[newChart.Name] = newChart
}
}

// flatten chart map
finalChartList := []k0sv1beta1.Chart{}
for _, chart := range finalConfigs {
finalChartList = append(finalChartList, chart)
}
return finalChartList, nil
}

// patchExtensionsForAirGap makes sure we do not have any external repository reference and also makes
// sure that all helm charts point to a chart stored on disk as a tgz file. These files are already
// expected to be present on the disk and, during an upgrade, are laid down on disk by the artifact
Expand Down
Loading

0 comments on commit a67d252

Please sign in to comment.