Compose values from references

This commit is contained in:
Hidde Beydals 2020-07-23 22:06:43 +02:00
parent 50ab012a28
commit 6dba659c3c
8 changed files with 151 additions and 8 deletions

View File

@ -66,6 +66,16 @@ jobs:
kubectl -n helm-system apply -f config/testdata/dependencies
kubectl -n helm-system wait helmreleases/backend --for=condition=ready --timeout=4m
kubectl -n helm-system wait helmreleases/frontend --for=condition=ready --timeout=4m
- name: Run values test
run: |
kubectl -n helm-system apply -f config/testdata/valuesfrom
kubectl -n helm-system wait helmreleases/valuesfrom --for=condition=ready --timeout=4m
RESULT=$(helm -n helm-system get values valuesfrom)
EXPECTED=$(cat config/testdata/valuesfrom/result.txt)
if [ "$RESULT" != "$EXPECTED" ]; then
echo -e "$RESULT\n\ndoes not equal\n\n$EXPECTED"
fi
- name: Logs
run: |
kubectl -n helm-system logs deploy/source-controller

View File

@ -55,3 +55,11 @@ type ValuesReference struct {
// +optional
ValuesKey string `json:"valuesKey,omitempty"`
}
// GetValuesKey returns the defined ValuesKey or the default ('values.yaml').
func (in ValuesReference) GetValuesKey() string {
if in.ValuesKey == "" {
return "values.yaml"
}
return in.ValuesKey
}

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: valuesfrom-config
data:
resources.limits: |
resources:
limits:
cpu: 200m
memory: 128Mi
resources.requests: |
resources:
requests:
cpu: 100m
memory: 64Mi

View File

@ -0,0 +1,28 @@
apiVersion: helm.fluxcd.io/v2alpha1
kind: HelmRelease
metadata:
name: valuesfrom
spec:
interval: 5m
chart:
name: podinfo
version: '^4.0.0'
sourceRef:
kind: HelmRepository
name: valuesfrom
interval: 1m
test:
enable: true
rollback:
enable: true
valuesFrom:
- kind: ConfigMap
name: valuesfrom-config
valuesKey: resources.limits
- kind: ConfigMap
name: valuesfrom-config
valuesKey: resources.requests
- kind: Secret
name: valuesfrom-secret
values:
replicaCount: 2

View File

@ -0,0 +1,7 @@
apiVersion: source.fluxcd.io/v1alpha1
kind: HelmRepository
metadata:
name: valuesfrom
spec:
interval: 1m
url: https://stefanprodan.github.io/podinfo

11
config/testdata/valuesfrom/result.txt vendored Normal file
View File

@ -0,0 +1,11 @@
USER-SUPPLIED VALUES:
replicaCount: 2
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 100m
memory: 64Mi
serviceAccount:
enabled: true

View File

@ -0,0 +1,7 @@
apiVersion: v1
kind: Secret
metadata:
name: valuesfrom-secret
type: Opaque
data:
values.yaml: c2VydmljZUFjY291bnQ6CiAgZW5hYmxlZDogdHJ1ZQo=

View File

@ -33,6 +33,7 @@ import (
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/storage/driver"
corev1 "k8s.io/api/core/v1"
@ -180,7 +181,19 @@ func (r *HelmReleaseReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
log.Info("all dependencies are ready, proceeding with release")
}
reconciledHr, reconcileErr := r.release(log, *hr.DeepCopy(), hc)
// Compose values
values, err := r.composeValues(ctx, hr)
if err != nil {
hr = v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, v2.InitFailedReason, err.Error())
r.event(hr, hr.Status.LastAttemptedRevision, recorder.EventSeverityError, err.Error())
if err := r.Status().Update(ctx, &hr); err != nil {
log.Error(err, "unable to update status")
return ctrl.Result{Requeue: true}, err
}
return ctrl.Result{}, nil
}
reconciledHr, reconcileErr := r.release(log, *hr.DeepCopy(), hc, values)
if reconcileErr != nil {
r.event(hr, hc.GetArtifact().Revision, recorder.EventSeverityError, fmt.Sprintf("reconciliation failed: %s", reconcileErr.Error()))
}
@ -264,7 +277,7 @@ func (r *HelmReleaseReconciler) reconcileChart(ctx context.Context, hr *v2.HelmR
return &helmChart, true, nil
}
func (r *HelmReleaseReconciler) release(log logr.Logger, hr v2.HelmRelease, source sourcev1.Source) (v2.HelmRelease, error) {
func (r *HelmReleaseReconciler) release(log logr.Logger, hr v2.HelmRelease, source sourcev1.Source, values chartutil.Values) (v2.HelmRelease, error) {
// Acquire lock
unlock, err := lock(fmt.Sprintf("%s-%s", hr.GetName(), hr.GetNamespace()))
if err != nil {
@ -307,11 +320,11 @@ func (r *HelmReleaseReconciler) release(log logr.Logger, hr v2.HelmRelease, sour
// Install or upgrade the release
success := true
if errors.Is(err, driver.ErrNoDeployedReleases) {
rel, err = install(cfg, loadedChart, hr)
rel, err = install(cfg, loadedChart, hr, values)
r.handleHelmActionResult(hr, source, err, "install", v2.InstalledCondition, v2.InstallSucceededReason, v2.InstallFailedReason)
success = err == nil
} else if v2.ShouldUpgrade(hr, source.GetArtifact().Revision, rel.Version) {
rel, err = upgrade(cfg, loadedChart, hr)
rel, err = upgrade(cfg, loadedChart, hr, values)
r.handleHelmActionResult(hr, source, err, "upgrade", v2.UpgradedCondition, v2.UpgradeSucceededReason, v2.UpgradeFailedReason)
success = err == nil
}
@ -421,6 +434,50 @@ func (r *HelmReleaseReconciler) gc(ctx context.Context, log logr.Logger, hr v2.H
}
}
func (r *HelmReleaseReconciler) composeValues(ctx context.Context, hr v2.HelmRelease) (chartutil.Values, error) {
var result chartutil.Values
for _, v := range hr.Spec.ValuesFrom {
namespacedName := types.NamespacedName{Namespace: hr.Namespace, Name: v.Name}
var valsData []byte
switch v.Kind {
case "ConfigMap":
var resource corev1.ConfigMap
if err := r.Get(ctx, namespacedName, &resource); err != nil {
if apierrors.IsNotFound(err) {
return nil, fmt.Errorf("could not find %s '%s'", v.Kind, namespacedName)
}
return nil, err
}
if data, ok := resource.Data[v.GetValuesKey()]; !ok {
return nil, fmt.Errorf("missing key '%s' in %s '%s'", v.GetValuesKey(), v.Kind, namespacedName)
} else {
valsData = []byte(data)
}
case "Secret":
var resource corev1.Secret
if err := r.Get(ctx, namespacedName, &resource); err != nil {
if apierrors.IsNotFound(err) {
return nil, fmt.Errorf("could not find %s '%s'", v.Kind, namespacedName)
}
return nil, err
}
if data, ok := resource.Data[v.GetValuesKey()]; !ok {
return nil, fmt.Errorf("missing key '%s' in %s '%s'", v.GetValuesKey(), v.Kind, namespacedName)
} else {
valsData = data
}
default:
return nil, fmt.Errorf("unsupported ValuesReference kind '%s'", v.Kind)
}
values, err := chartutil.ReadValues(valsData)
if err != nil {
return nil, fmt.Errorf("unable to read values from key '%s' in %s '%s': %w", v.GetValuesKey(), v.Kind, namespacedName, err)
}
result = chartutil.CoalesceTables(result, values)
}
return chartutil.CoalesceTables(result, hr.GetValues()), nil
}
func (r *HelmReleaseReconciler) handleHelmActionResult(hr v2.HelmRelease, source sourcev1.Source, err error, action string, condition string, succeededReason string, failedReason string) {
if err != nil {
v2.SetHelmReleaseCondition(&hr, condition, corev1.ConditionFalse, failedReason, err.Error())
@ -493,7 +550,7 @@ func helmChartRequiresUpdate(hr v2.HelmRelease, chart sourcev1.HelmChart) bool {
}
}
func install(cfg *action.Configuration, chart *chart.Chart, hr v2.HelmRelease) (*release.Release, error) {
func install(cfg *action.Configuration, chart *chart.Chart, hr v2.HelmRelease, values chartutil.Values) (*release.Release, error) {
install := action.NewInstall(cfg)
install.ReleaseName = hr.GetReleaseName()
install.Namespace = hr.GetReleaseNamespace()
@ -504,10 +561,10 @@ func install(cfg *action.Configuration, chart *chart.Chart, hr v2.HelmRelease) (
install.Replace = hr.Spec.Install.Replace
install.SkipCRDs = hr.Spec.Install.SkipCRDs
return install.Run(chart, hr.GetValues())
return install.Run(chart, values.AsMap())
}
func upgrade(cfg *action.Configuration, chart *chart.Chart, hr v2.HelmRelease) (*release.Release, error) {
func upgrade(cfg *action.Configuration, chart *chart.Chart, hr v2.HelmRelease, values chartutil.Values) (*release.Release, error) {
upgrade := action.NewUpgrade(cfg)
upgrade.Namespace = hr.GetReleaseNamespace()
upgrade.ResetValues = !hr.Spec.Upgrade.PreserveValues
@ -519,7 +576,7 @@ func upgrade(cfg *action.Configuration, chart *chart.Chart, hr v2.HelmRelease) (
upgrade.Force = hr.Spec.Upgrade.Force
upgrade.CleanupOnFail = hr.Spec.Upgrade.CleanupOnFail
return upgrade.Run(hr.GetReleaseName(), chart, hr.GetValues())
return upgrade.Run(hr.GetReleaseName(), chart, values.AsMap())
}
func test(cfg *action.Configuration, hr v2.HelmRelease) (*release.Release, error) {