Skip to content

Commit 41e8f21

Browse files
authored
Merge pull request #151 from fluxcd/helmchart-bucket-source
2 parents ce5fc3e + 8cdb821 commit 41e8f21

File tree

10 files changed

+156
-116
lines changed

10 files changed

+156
-116
lines changed

.github/workflows/e2e.yaml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,22 @@ jobs:
8181
wget -q https://dl.min.io/client/mc/release/linux-amd64/mc
8282
chmod +x mc
8383
./mc alias set minio http://localhost:9000 myaccesskey mysecretkey --api S3v4
84-
./mc mb minio/podinfo
85-
./mc cp --recursive ./config/testdata/minio/manifests minio/podinfo
86-
- name: Run S3 tests
84+
kubectl -n source-system apply -f ./config/testdata/minio/secret.yaml
85+
- name: Run Bucket tests
8786
run: |
88-
kubectl -n source-system apply -f ./config/testdata/minio/source.yaml
87+
./mc mb minio/podinfo
88+
./mc mirror ./config/testdata/minio/manifests/ minio/podinfo
89+
90+
kubectl -n source-system apply -f ./config/testdata/bucket/source.yaml
8991
kubectl -n source-system wait bucket/podinfo --for=condition=ready --timeout=1m
92+
- name: Run HelmChart from Bucket tests
93+
run: |
94+
./mc mb minio/charts
95+
./mc mirror ./controllers/testdata/helmchart/ minio/charts/helmchart
96+
97+
kubectl -n source-system apply -f ./config/testdata/helmchart-from-bucket/source.yaml
98+
kubectl -n source-system wait bucket/charts --for=condition=ready --timeout=1m
99+
kubectl -n source-system wait helmchart/helmchart-bucket --for=condition=ready --timeout=1m
90100
- name: Debug failure
91101
if: failure()
92102
run: |

api/v1alpha1/helmchart_types.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ type HelmChartSpec struct {
3030
Chart string `json:"chart"`
3131

3232
// The chart version semver expression, ignored for charts from GitRepository
33-
// sources. Defaults to latest when omitted.
33+
// and Bucket sources. Defaults to latest when omitted.
3434
// +optional
3535
Version string `json:"version,omitempty"`
3636

@@ -55,8 +55,8 @@ type LocalHelmChartSourceReference struct {
5555
// +optional
5656
APIVersion string `json:"apiVersion,omitempty"`
5757

58-
// Kind of the referent, valid values are ('HelmRepository', 'GitRepository').
59-
// +kubebuilder:validation:Enum=HelmRepository;GitRepository
58+
// Kind of the referent, valid values are ('HelmRepository', 'GitRepository', 'Bucket').
59+
// +kubebuilder:validation:Enum=HelmRepository;GitRepository;Bucket
6060
// +required
6161
Kind string `json:"kind"`
6262

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,11 @@ spec:
7373
type: string
7474
kind:
7575
description: Kind of the referent, valid values are ('HelmRepository',
76-
'GitRepository').
76+
'GitRepository', 'Bucket').
7777
enum:
7878
- HelmRepository
7979
- GitRepository
80+
- Bucket
8081
type: string
8182
name:
8283
description: Name of the referent.
@@ -91,7 +92,7 @@ spec:
9192
type: string
9293
version:
9394
description: The chart version semver expression, ignored for charts
94-
from GitRepository sources. Defaults to latest when omitted.
95+
from GitRepository and Bucket sources. Defaults to latest when omitted.
9596
type: string
9697
required:
9798
- chart
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
---
12
apiVersion: source.toolkit.fluxcd.io/v1alpha1
23
kind: Bucket
34
metadata:
@@ -11,11 +12,3 @@ spec:
1112
insecure: true
1213
secretRef:
1314
name: minio-credentials
14-
---
15-
apiVersion: v1
16-
kind: Secret
17-
metadata:
18-
name: minio-credentials
19-
data:
20-
accesskey: bXlhY2Nlc3NrZXk=
21-
secretkey: bXlzZWNyZXRrZXk=
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
apiVersion: source.toolkit.fluxcd.io/v1alpha1
3+
kind: Bucket
4+
metadata:
5+
name: charts
6+
spec:
7+
interval: 1m
8+
provider: generic
9+
bucketName: charts
10+
endpoint: minio.minio.svc.cluster.local:9000
11+
region: us-east-1
12+
insecure: true
13+
secretRef:
14+
name: minio-credentials
15+
---
16+
apiVersion: source.toolkit.fluxcd.io/v1alpha1
17+
kind: HelmChart
18+
metadata:
19+
name: helmchart-bucket
20+
spec:
21+
chart: ./helmchart
22+
sourceRef:
23+
kind: Bucket
24+
name: charts
25+
interval: 1m

config/testdata/minio/secret.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
apiVersion: v1
3+
kind: Secret
4+
metadata:
5+
name: minio-credentials
6+
data:
7+
accesskey: bXlhY2Nlc3NrZXk=
8+
secretkey: bXlzZWNyZXRrZXk=

controllers/helmchart_controller.go

Lines changed: 79 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -119,33 +119,39 @@ func (r *HelmChartReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
119119
log.Error(err, "unable to purge old artifacts")
120120
}
121121

122+
// Retrieve the source
123+
source, err := r.getSource(ctx, chart)
124+
if err != nil {
125+
chart = sourcev1.HelmChartNotReady(*chart.DeepCopy(), sourcev1.ChartPullFailedReason, err.Error())
126+
if err := r.Status().Update(ctx, &chart); err != nil {
127+
log.Error(err, "unable to update status")
128+
}
129+
return ctrl.Result{Requeue: true}, err
130+
}
131+
132+
// Assert source is ready
133+
if source.GetArtifact() == nil {
134+
err = fmt.Errorf("no artifact found for source `%s` kind '%s'",
135+
chart.Spec.SourceRef.Name, chart.Spec.SourceRef.Kind)
136+
chart = sourcev1.HelmChartNotReady(*chart.DeepCopy(), sourcev1.ChartPullFailedReason, err.Error())
137+
if err := r.Status().Update(ctx, &chart); err != nil {
138+
log.Error(err, "unable to update status")
139+
}
140+
return ctrl.Result{Requeue: true}, err
141+
}
142+
122143
// Perform the reconciliation for the chart source type
123144
var reconciledChart sourcev1.HelmChart
124145
var reconcileErr error
125-
switch chart.Spec.SourceRef.Kind {
126-
case sourcev1.HelmRepositoryKind:
127-
repository, err := r.getChartRepositoryWithArtifact(ctx, chart)
128-
if err != nil {
129-
chart = sourcev1.HelmChartNotReady(*chart.DeepCopy(), sourcev1.ChartPullFailedReason, err.Error())
130-
if err := r.Status().Update(ctx, &chart); err != nil {
131-
log.Error(err, "unable to update status")
132-
}
133-
return ctrl.Result{Requeue: true}, err
134-
}
135-
reconciledChart, reconcileErr = r.reconcileFromHelmRepository(ctx, repository, *chart.DeepCopy(), changed)
136-
case sourcev1.GitRepositoryKind:
137-
repository, err := r.getGitRepositoryWithArtifact(ctx, chart)
138-
if err != nil {
139-
chart = sourcev1.HelmChartNotReady(*chart.DeepCopy(), sourcev1.ChartPullFailedReason, err.Error())
140-
if err := r.Status().Update(ctx, &chart); err != nil {
141-
log.Error(err, "unable to update status")
142-
}
143-
return ctrl.Result{Requeue: true}, err
144-
}
145-
reconciledChart, reconcileErr = r.reconcileFromGitRepository(ctx, repository, *chart.DeepCopy(), changed)
146+
switch typedSource := source.(type) {
147+
case *sourcev1.HelmRepository:
148+
reconciledChart, reconcileErr = r.reconcileFromHelmRepository(ctx, *typedSource, *chart.DeepCopy(), changed)
149+
case *sourcev1.GitRepository, *sourcev1.Bucket:
150+
reconciledChart, reconcileErr = r.reconcileFromTarballArtifact(ctx, *typedSource.GetArtifact(),
151+
*chart.DeepCopy(), changed)
146152
default:
147153
err := fmt.Errorf("unable to reconcile unsupported source reference kind '%s'", chart.Spec.SourceRef.Kind)
148-
return ctrl.Result{}, err
154+
return ctrl.Result{Requeue: false}, err
149155
}
150156

151157
// Update status with the reconciliation result
@@ -188,6 +194,41 @@ func (r *HelmChartReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts
188194
Complete(r)
189195
}
190196

197+
func (r *HelmChartReconciler) getSource(ctx context.Context, chart sourcev1.HelmChart) (sourcev1.Source, error) {
198+
var source sourcev1.Source
199+
namespacedName := types.NamespacedName{
200+
Namespace: chart.GetNamespace(),
201+
Name: chart.Spec.SourceRef.Name,
202+
}
203+
switch chart.Spec.SourceRef.Kind {
204+
case sourcev1.HelmRepositoryKind:
205+
var repository sourcev1.HelmRepository
206+
err := r.Client.Get(ctx, namespacedName, &repository)
207+
if err != nil {
208+
return source, fmt.Errorf("failed to retrieve source: %w", err)
209+
}
210+
source = &repository
211+
case sourcev1.GitRepositoryKind:
212+
var repository sourcev1.GitRepository
213+
err := r.Client.Get(ctx, namespacedName, &repository)
214+
if err != nil {
215+
return source, fmt.Errorf("failed to retrieve source: %w", err)
216+
}
217+
source = &repository
218+
case sourcev1.BucketKind:
219+
var bucket sourcev1.Bucket
220+
err := r.Client.Get(ctx, namespacedName, &bucket)
221+
if err != nil {
222+
return source, fmt.Errorf("failed to retrieve source: %w", err)
223+
}
224+
source = &bucket
225+
default:
226+
return source, fmt.Errorf("source `%s` kind '%s' not supported",
227+
chart.Spec.SourceRef.Name, chart.Spec.SourceRef.Kind)
228+
}
229+
return source, nil
230+
}
231+
191232
func (r *HelmChartReconciler) reconcileFromHelmRepository(ctx context.Context,
192233
repository sourcev1.HelmRepository, chart sourcev1.HelmChart, force bool) (sourcev1.HelmChart, error) {
193234
cv, err := helm.GetDownloadableChartVersionFromIndex(r.Storage.LocalPath(*repository.GetArtifact()),
@@ -201,8 +242,8 @@ func (r *HelmChartReconciler) reconcileFromHelmRepository(ctx context.Context,
201242
fmt.Sprintf("%s-%s.tgz", cv.Name, cv.Version))
202243
if !force && repository.GetArtifact() != nil && repository.GetArtifact().Revision == cv.Version {
203244
if artifact.URL != repository.GetArtifact().URL {
204-
r.Storage.SetArtifactURL(repository.GetArtifact())
205-
repository.Status.URL = r.Storage.SetHostname(repository.Status.URL)
245+
r.Storage.SetArtifactURL(chart.GetArtifact())
246+
repository.Status.URL = r.Storage.SetHostname(chart.Status.URL)
206247
}
207248
return chart, nil
208249
}
@@ -353,35 +394,8 @@ func (r *HelmChartReconciler) reconcileFromHelmRepository(ctx context.Context,
353394
return sourcev1.HelmChartReady(chart, artifact, chartUrl, readyReason, readyMessage), nil
354395
}
355396

356-
// getChartRepositoryWithArtifact attempts to get the v1alpha1.HelmRepository
357-
// for the given chart. It returns an error if the HelmRepository could
358-
// not be retrieved or if does not have an artifact.
359-
func (r *HelmChartReconciler) getChartRepositoryWithArtifact(ctx context.Context, chart sourcev1.HelmChart) (sourcev1.HelmRepository, error) {
360-
if chart.Spec.SourceRef.Name == "" {
361-
return sourcev1.HelmRepository{}, fmt.Errorf("no HelmRepository reference given")
362-
}
363-
364-
name := types.NamespacedName{
365-
Namespace: chart.GetNamespace(),
366-
Name: chart.Spec.SourceRef.Name,
367-
}
368-
369-
var repository sourcev1.HelmRepository
370-
err := r.Client.Get(ctx, name, &repository)
371-
if err != nil {
372-
err = fmt.Errorf("failed to get HelmRepository '%s': %w", name, err)
373-
return repository, err
374-
}
375-
376-
if repository.GetArtifact() == nil {
377-
err = fmt.Errorf("no repository index artifact found for HelmRepository '%s'", name)
378-
}
379-
380-
return repository, err
381-
}
382-
383-
func (r *HelmChartReconciler) reconcileFromGitRepository(ctx context.Context,
384-
repository sourcev1.GitRepository, chart sourcev1.HelmChart, force bool) (sourcev1.HelmChart, error) {
397+
func (r *HelmChartReconciler) reconcileFromTarballArtifact(ctx context.Context,
398+
artifact sourcev1.Artifact, chart sourcev1.HelmChart, force bool) (sourcev1.HelmChart, error) {
385399
// Create temporary working directory
386400
tmpDir, err := ioutil.TempDir("", fmt.Sprintf("%s-%s-", chart.Namespace, chart.Name))
387401
if err != nil {
@@ -390,8 +404,8 @@ func (r *HelmChartReconciler) reconcileFromGitRepository(ctx context.Context,
390404
}
391405
defer os.RemoveAll(tmpDir)
392406

393-
// Open GitRepository artifact file and untar files into working directory
394-
f, err := os.Open(r.Storage.LocalPath(*repository.GetArtifact()))
407+
// Open the tarball artifact file and untar files into working directory
408+
f, err := os.Open(r.Storage.LocalPath(artifact))
395409
if err != nil {
396410
err = fmt.Errorf("artifact open error: %w", err)
397411
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
@@ -418,12 +432,12 @@ func (r *HelmChartReconciler) reconcileFromGitRepository(ctx context.Context,
418432
}
419433

420434
// Return early if the revision is still the same as the current chart artifact
421-
artifact := r.Storage.NewArtifactFor(chart.Kind, chart.ObjectMeta.GetObjectMeta(), chartMetadata.Version,
435+
chartArtifact := r.Storage.NewArtifactFor(chart.Kind, chart.ObjectMeta.GetObjectMeta(), chartMetadata.Version,
422436
fmt.Sprintf("%s-%s.tgz", chartMetadata.Name, chartMetadata.Version))
423437
if !force && chart.GetArtifact() != nil && chart.GetArtifact().Revision == chartMetadata.Version {
424-
if artifact.URL != repository.GetArtifact().URL {
425-
r.Storage.SetArtifactURL(repository.GetArtifact())
426-
repository.Status.URL = r.Storage.SetHostname(repository.Status.URL)
438+
if chartArtifact.URL != artifact.URL {
439+
r.Storage.SetArtifactURL(&chartArtifact)
440+
chart.Status.URL = r.Storage.SetHostname(chart.Status.URL)
427441
}
428442
return chart, nil
429443
}
@@ -436,14 +450,14 @@ func (r *HelmChartReconciler) reconcileFromGitRepository(ctx context.Context,
436450
}
437451

438452
// Ensure artifact directory exists
439-
err = r.Storage.MkdirAll(artifact)
453+
err = r.Storage.MkdirAll(chartArtifact)
440454
if err != nil {
441455
err = fmt.Errorf("unable to create artifact directory: %w", err)
442456
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
443457
}
444458

445459
// Acquire a lock for the artifact
446-
unlock, err := r.Storage.Lock(artifact)
460+
unlock, err := r.Storage.Lock(chartArtifact)
447461
if err != nil {
448462
err = fmt.Errorf("unable to acquire lock: %w", err)
449463
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
@@ -466,49 +480,22 @@ func (r *HelmChartReconciler) reconcileFromGitRepository(ctx context.Context,
466480
err = fmt.Errorf("failed to open chart package: %w", err)
467481
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
468482
}
469-
if err := r.Storage.Copy(&artifact, cf); err != nil {
483+
if err := r.Storage.Copy(&chartArtifact, cf); err != nil {
470484
cf.Close()
471485
err = fmt.Errorf("failed to copy chart package to storage: %w", err)
472486
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
473487
}
474488
cf.Close()
475489

476490
// Update symlink
477-
cUrl, err := r.Storage.Symlink(artifact, fmt.Sprintf("%s-latest.tgz", chartMetadata.Name))
491+
cUrl, err := r.Storage.Symlink(chartArtifact, fmt.Sprintf("%s-latest.tgz", chartMetadata.Name))
478492
if err != nil {
479493
err = fmt.Errorf("storage error: %w", err)
480494
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
481495
}
482496

483-
message := fmt.Sprintf("Fetched and packaged revision: %s", artifact.Revision)
484-
return sourcev1.HelmChartReady(chart, artifact, cUrl, sourcev1.ChartPackageSucceededReason, message), nil
485-
}
486-
487-
// getGitRepositoryWithArtifact attempts to get the GitRepository for the given
488-
// chart. It returns an error if the v1alpha1.GitRepository could not be retrieved
489-
// or does not have an artifact.
490-
func (r *HelmChartReconciler) getGitRepositoryWithArtifact(ctx context.Context, chart sourcev1.HelmChart) (sourcev1.GitRepository, error) {
491-
if chart.Spec.SourceRef.Name == "" {
492-
return sourcev1.GitRepository{}, fmt.Errorf("no GitRepository reference given")
493-
}
494-
495-
name := types.NamespacedName{
496-
Namespace: chart.GetNamespace(),
497-
Name: chart.Spec.SourceRef.Name,
498-
}
499-
500-
var repository sourcev1.GitRepository
501-
err := r.Client.Get(ctx, name, &repository)
502-
if err != nil {
503-
err = fmt.Errorf("failed to get GitRepository '%s': %w", name, err)
504-
return repository, err
505-
}
506-
507-
if repository.GetArtifact() == nil {
508-
err = fmt.Errorf("no artifact found for GitRepository '%s'", repository.Name)
509-
}
510-
511-
return repository, err
497+
message := fmt.Sprintf("Fetched and packaged revision: %s", chartArtifact.Revision)
498+
return sourcev1.HelmChartReady(chart, chartArtifact, cUrl, sourcev1.ChartPackageSucceededReason, message), nil
512499
}
513500

514501
// resetStatus returns a modified v1alpha1.HelmChart and a boolean indicating

controllers/helmchart_controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ var _ = Describe("HelmChartReconciler", func() {
152152
_ = k8sClient.Get(context.Background(), key, updated)
153153
for _, c := range updated.Status.Conditions {
154154
if c.Reason == sourcev1.ChartPullFailedReason &&
155-
strings.Contains(c.Message, "failed to get HelmRepository") {
155+
strings.Contains(c.Message, "failed to retrieve source") {
156156
return true
157157
}
158158
}

0 commit comments

Comments
 (0)