Skip to content

Commit 2e0b6cb

Browse files
committed
Allow overwriting chart values from GitRepository
1 parent fa37eea commit 2e0b6cb

File tree

4 files changed

+107
-37
lines changed

4 files changed

+107
-37
lines changed

api/v1alpha1/helmchart_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ type HelmChartSpec struct {
4141
// The interval at which to check the Source for updates.
4242
// +required
4343
Interval metav1.Duration `json:"interval"`
44+
45+
// Alternative values file to use as the default chart values, expected to be
46+
// a relative path in the SourceRef. Ignored when omitted.
47+
// +optional
48+
ValuesFile string `json:"valuesFile,omitempty"`
4449
}
4550

4651
// LocalHelmChartSourceReference contains enough information to let you locate the

config/crd/bases/source.toolkit.fluxcd.io_helmcharts.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ spec:
8585
- kind
8686
- name
8787
type: object
88+
valuesFile:
89+
description: Alternative values file to use as the default chart values,
90+
expected to be a relative path in the SourceRef. Ignored when omitted.
91+
type: string
8892
version:
8993
description: The chart version semver expression, ignored for charts
9094
from GitRepository sources. Defaults to latest when omitted.

controllers/helmchart_controller.go

Lines changed: 72 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package controllers
1919
import (
2020
"context"
2121
"fmt"
22+
"io"
2223
"io/ioutil"
2324
"net/url"
2425
"os"
@@ -104,7 +105,7 @@ func (r *HelmChartReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
104105
}
105106
}
106107

107-
// set initial status
108+
// Conditionally set progressing condition in status
108109
if resetChart, ok := r.resetStatus(chart); ok {
109110
chart = resetChart
110111
if err := r.Status().Update(ctx, &chart); err != nil {
@@ -113,11 +114,12 @@ func (r *HelmChartReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
113114
}
114115
}
115116

116-
// purge old artifacts from storage
117+
// Purge all but current artifact from storage
117118
if err := r.gc(chart, false); err != nil {
118119
log.Error(err, "unable to purge old artifacts")
119120
}
120121

122+
// Perform the reconciliation for the chart source type
121123
var reconciledChart sourcev1.HelmChart
122124
var reconcileErr error
123125
switch chart.Spec.SourceRef.Kind {
@@ -146,19 +148,19 @@ func (r *HelmChartReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
146148
return ctrl.Result{}, err
147149
}
148150

149-
// update status with the reconciliation result
151+
// Update status with the reconciliation result
150152
if err := r.Status().Update(ctx, &reconciledChart); err != nil {
151153
log.Error(err, "unable to update status")
152154
return ctrl.Result{Requeue: true}, err
153155
}
154156

155-
// if reconciliation failed, record the failure and requeue immediately
157+
// If reconciliation failed, record the failure and requeue immediately
156158
if reconcileErr != nil {
157159
r.event(reconciledChart, recorder.EventSeverityError, reconcileErr.Error())
158160
return ctrl.Result{Requeue: true}, reconcileErr
159161
}
160162

161-
// emit revision change event
163+
// Emit an event if we did not have an artifact before, or the revision has changed
162164
if chart.Status.Artifact == nil || reconciledChart.Status.Artifact.Revision != chart.Status.Artifact.Revision {
163165
r.event(reconciledChart, recorder.EventSeverityInfo, sourcev1.HelmChartReadyMessage(reconciledChart))
164166
}
@@ -167,7 +169,6 @@ func (r *HelmChartReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
167169
time.Now().Sub(start).String(),
168170
chart.GetInterval().Duration.String(),
169171
))
170-
171172
return ctrl.Result{RequeueAfter: chart.GetInterval().Duration}, nil
172173
}
173174

@@ -195,8 +196,9 @@ func (r *HelmChartReconciler) reconcileFromHelmRepository(ctx context.Context,
195196
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err
196197
}
197198

198-
// return early on unchanged chart version
199-
artifact := r.Storage.NewArtifactFor(chart.Kind, chart.GetObjectMeta(), cv.Version, fmt.Sprintf("%s-%s.tgz", cv.Name, cv.Version))
199+
// Return early if the revision is still the same as the current artifact
200+
artifact := r.Storage.NewArtifactFor(chart.Kind, chart.GetObjectMeta(), cv.Version,
201+
fmt.Sprintf("%s-%s.tgz", cv.Name, cv.Version))
200202
if repository.GetArtifact() != nil && repository.GetArtifact().Revision == cv.Version {
201203
if artifact.URL != repository.GetArtifact().URL {
202204
r.Storage.SetArtifactURL(repository.GetArtifact())
@@ -228,6 +230,7 @@ func (r *HelmChartReconciler) reconcileFromHelmRepository(ctx context.Context,
228230
u.RawQuery = q.Encode()
229231
}
230232

233+
// Get the getter for the protocol
231234
c, err := r.Getters.ByScheme(u.Scheme)
232235
if err != nil {
233236
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err
@@ -258,35 +261,35 @@ func (r *HelmChartReconciler) reconcileFromHelmRepository(ctx context.Context,
258261
clientOpts = opts
259262
}
260263

261-
// TODO(hidde): implement timeout from the HelmRepository
262-
// https://github.com/helm/helm/pull/7950
263-
res, err := c.Get(u.String(), clientOpts...)
264-
if err != nil {
265-
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err
266-
}
267-
268-
// create artifact dir
264+
// Ensure artifact directory exists
269265
err = r.Storage.MkdirAll(artifact)
270266
if err != nil {
271267
err = fmt.Errorf("unable to create chart directory: %w", err)
272268
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
273269
}
274270

275-
// acquire lock
271+
// Acquire a lock for the artifact
276272
unlock, err := r.Storage.Lock(artifact)
277273
if err != nil {
278274
err = fmt.Errorf("unable to acquire lock: %w", err)
279275
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
280276
}
281277
defer unlock()
282278

283-
// save artifact to storage
279+
// TODO(hidde): implement timeout from the HelmRepository
280+
// https://github.com/helm/helm/pull/7950
281+
res, err := c.Get(u.String(), clientOpts...)
282+
if err != nil {
283+
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err
284+
}
285+
286+
// Write the artifact to storage
284287
if err := r.Storage.AtomicWriteFile(&artifact, res, 0644); err != nil {
285288
err = fmt.Errorf("unable to write chart file: %w", err)
286289
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
287290
}
288291

289-
// update symlink
292+
// Update symlink
290293
chartUrl, err := r.Storage.Symlink(artifact, fmt.Sprintf("%s-latest.tgz", cv.Name))
291294
if err != nil {
292295
err = fmt.Errorf("storage error: %w", err)
@@ -297,7 +300,7 @@ func (r *HelmChartReconciler) reconcileFromHelmRepository(ctx context.Context,
297300
return sourcev1.HelmChartReady(chart, artifact, chartUrl, sourcev1.ChartPullSucceededReason, message), nil
298301
}
299302

300-
// getChartRepositoryWithArtifact attempts to get the ChartRepository
303+
// getChartRepositoryWithArtifact attempts to get the v1alpha1.HelmRepository
301304
// for the given chart. It returns an error if the HelmRepository could
302305
// not be retrieved or if does not have an artifact.
303306
func (r *HelmChartReconciler) getChartRepositoryWithArtifact(ctx context.Context, chart sourcev1.HelmChart) (sourcev1.HelmRepository, error) {
@@ -318,50 +321,50 @@ func (r *HelmChartReconciler) getChartRepositoryWithArtifact(ctx context.Context
318321
}
319322

320323
if repository.GetArtifact() == nil {
321-
err = fmt.Errorf("no repository index artifact found in HelmRepository '%s'", repository.Name)
324+
err = fmt.Errorf("no repository index artifact found in HelmRepository '%s'", name)
322325
}
323326

324327
return repository, err
325328
}
326329

327330
func (r *HelmChartReconciler) reconcileFromGitRepository(ctx context.Context,
328331
repository sourcev1.GitRepository, chart sourcev1.HelmChart) (sourcev1.HelmChart, error) {
329-
// create tmp dir
332+
// Create temporary working directory
330333
tmpDir, err := ioutil.TempDir("", fmt.Sprintf("%s-%s", chart.Namespace, chart.Name))
331334
if err != nil {
332335
err = fmt.Errorf("tmp dir error: %w", err)
333336
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
334337
}
335338
defer os.RemoveAll(tmpDir)
336339

337-
// open file
340+
// Open GitRepository artifact file and untar files into working directory
338341
f, err := os.Open(r.Storage.LocalPath(*repository.GetArtifact()))
339342
if err != nil {
340343
err = fmt.Errorf("artifact open error: %w", err)
341344
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
342345
}
343-
344-
// extract artifact files
345346
if _, err = untar.Untar(f, tmpDir); err != nil {
347+
f.Close()
346348
err = fmt.Errorf("artifact untar error: %w", err)
347349
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
348350
}
351+
f.Close()
349352

350-
// ensure configured path is a chart directory
353+
// Ensure configured path is a chart directory
351354
chartPath := path.Join(tmpDir, chart.Spec.Chart)
352355
if _, err := chartutil.IsChartDir(chartPath); err != nil {
353356
err = fmt.Errorf("chart path error: %w", err)
354357
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
355358
}
356359

357-
// read chart metadata
360+
// Read the chart metadata
358361
chartMetadata, err := chartutil.LoadChartfile(path.Join(chartPath, chartutil.ChartfileName))
359362
if err != nil {
360363
err = fmt.Errorf("load chart metadata error: %w", err)
361364
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
362365
}
363366

364-
// return early on unchanged chart version
367+
// Return early if the revision is still the same as the current chart artifact
365368
artifact := r.Storage.NewArtifactFor(chart.Kind, chart.ObjectMeta.GetObjectMeta(), chartMetadata.Version, fmt.Sprintf("%s-%s.tgz", chartMetadata.Name, chartMetadata.Version))
366369
if chart.GetArtifact() != nil && chart.GetArtifact().Revision == chartMetadata.Version {
367370
if artifact.URL != repository.GetArtifact().URL {
@@ -371,22 +374,51 @@ func (r *HelmChartReconciler) reconcileFromGitRepository(ctx context.Context,
371374
return chart, nil
372375
}
373376

374-
// create artifact dir
377+
// Overwrite default values if instructed to
378+
if chart.Spec.ValuesFile != "" {
379+
srcPath := path.Join(tmpDir, chart.Spec.ValuesFile)
380+
if f, err := os.Stat(srcPath); os.IsNotExist(err) || !f.Mode().IsRegular() {
381+
err = fmt.Errorf("invalid values file path: %s", chart.Spec.ValuesFile)
382+
return chart, err
383+
}
384+
src, err := os.Open(srcPath)
385+
if err != nil {
386+
err = fmt.Errorf("failed to open values file '%s': %w", chart.Spec.ValuesFile, err)
387+
return chart, err
388+
}
389+
t, err := os.OpenFile(path.Join(tmpDir, chartutil.ValuesfileName), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
390+
if err != nil {
391+
src.Close()
392+
err = fmt.Errorf("failed to open values file '%s': %w", chartutil.ValuesfileName, err)
393+
return chart, err
394+
}
395+
if _, err := io.Copy(t, src); err != nil {
396+
t.Close()
397+
src.Close()
398+
err = fmt.Errorf("failed to copy values file '%s' to '%s: %w", chart.Spec.ValuesFile, chartutil.ValuesfileName, err)
399+
return chart, err
400+
}
401+
t.Close()
402+
src.Close()
403+
}
404+
405+
// Ensure artifact directory exists
375406
err = r.Storage.MkdirAll(artifact)
376407
if err != nil {
377408
err = fmt.Errorf("unable to create artifact directory: %w", err)
378409
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
379410
}
380411

381-
// acquire lock
412+
// Acquire a lock for the artifact
382413
unlock, err := r.Storage.Lock(artifact)
383414
if err != nil {
384415
err = fmt.Errorf("unable to acquire lock: %w", err)
385416
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
386417
}
387418
defer unlock()
388419

389-
// package chart
420+
// Package the chart, we use the action here instead of relying on the
421+
// chartutil.Save method as the action performs a dependency check for us
390422
pkg := action.NewPackage()
391423
pkg.Destination = tmpDir
392424
src, err := pkg.Run(chartPath, nil)
@@ -395,7 +427,7 @@ func (r *HelmChartReconciler) reconcileFromGitRepository(ctx context.Context,
395427
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPackageFailedReason, err.Error()), err
396428
}
397429

398-
// copy chart package
430+
// Copy the packaged chart to the artifact path
399431
cf, err := os.Open(src)
400432
if err != nil {
401433
err = fmt.Errorf("failed to open chart package: %w", err)
@@ -408,7 +440,7 @@ func (r *HelmChartReconciler) reconcileFromGitRepository(ctx context.Context,
408440
}
409441
cf.Close()
410442

411-
// update symlink
443+
// Update symlink
412444
cUrl, err := r.Storage.Symlink(artifact, fmt.Sprintf("%s-latest.tgz", chartMetadata.Name))
413445
if err != nil {
414446
err = fmt.Errorf("storage error: %w", err)
@@ -420,8 +452,8 @@ func (r *HelmChartReconciler) reconcileFromGitRepository(ctx context.Context,
420452
}
421453

422454
// getGitRepositoryWithArtifact attempts to get the GitRepository for the given
423-
// chart. It returns an error if the GitRepository could not be retrieved or
424-
// does not have an artifact.
455+
// chart. It returns an error if the v1alpha1.GitRepository could not be retrieved
456+
// or does not have an artifact.
425457
func (r *HelmChartReconciler) getGitRepositoryWithArtifact(ctx context.Context, chart sourcev1.HelmChart) (sourcev1.GitRepository, error) {
426458
if chart.Spec.SourceRef.Name == "" {
427459
return sourcev1.GitRepository{}, fmt.Errorf("no GitRepository reference given")
@@ -449,18 +481,20 @@ func (r *HelmChartReconciler) getGitRepositoryWithArtifact(ctx context.Context,
449481
// resetStatus returns a modified v1alpha1.HelmChart and a boolean indicating
450482
// if the status field has been reset.
451483
func (r *HelmChartReconciler) resetStatus(chart sourcev1.HelmChart) (sourcev1.HelmChart, bool) {
484+
// The artifact does no longer exist
452485
if chart.GetArtifact() != nil && !r.Storage.ArtifactExist(*chart.GetArtifact()) {
453486
chart = sourcev1.HelmChartProgressing(chart)
454487
chart.Status.Artifact = nil
455488
return chart, true
456489
}
490+
// The chart specification has changed
457491
if chart.Generation != chart.Status.ObservedGeneration {
458492
return sourcev1.HelmChartProgressing(chart), true
459493
}
460494
return chart, false
461495
}
462496

463-
// gc performs a garbage collection on all but current artifacts of
497+
// gc performs a garbage collection on all but the current artifact of
464498
// the given chart.
465499
func (r *HelmChartReconciler) gc(chart sourcev1.HelmChart, all bool) error {
466500
if all {
@@ -472,7 +506,8 @@ func (r *HelmChartReconciler) gc(chart sourcev1.HelmChart, all bool) error {
472506
return nil
473507
}
474508

475-
// event emits a Kubernetes event and forwards the event to notification controller if configured
509+
// event emits a Kubernetes event and forwards the event to notification
510+
// controller if configured.
476511
func (r *HelmChartReconciler) event(chart sourcev1.HelmChart, severity, msg string) {
477512
if r.EventRecorder != nil {
478513
r.EventRecorder.Eventf(&chart, "Normal", severity, msg)

docs/api/source.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,19 @@ Kubernetes meta/v1.Duration
298298
<p>The interval at which to check the Source for updates.</p>
299299
</td>
300300
</tr>
301+
<tr>
302+
<td>
303+
<code>valuesFile</code><br>
304+
<em>
305+
string
306+
</em>
307+
</td>
308+
<td>
309+
<em>(Optional)</em>
310+
<p>Alternative values file to use as the default chart values, expected to be
311+
a relative path in the SourceRef. Ignored when omitted.</p>
312+
</td>
313+
</tr>
301314
</table>
302315
</td>
303316
</tr>
@@ -911,6 +924,19 @@ Kubernetes meta/v1.Duration
911924
<p>The interval at which to check the Source for updates.</p>
912925
</td>
913926
</tr>
927+
<tr>
928+
<td>
929+
<code>valuesFile</code><br>
930+
<em>
931+
string
932+
</em>
933+
</td>
934+
<td>
935+
<em>(Optional)</em>
936+
<p>Alternative values file to use as the default chart values, expected to be
937+
a relative path in the SourceRef. Ignored when omitted.</p>
938+
</td>
939+
</tr>
914940
</tbody>
915941
</table>
916942
</div>

0 commit comments

Comments
 (0)