Skip to content

Commit d5e0598

Browse files
authored
Merge pull request #485 from fluxcd/helmchart-reconciler-dev
2 parents cc2bc56 + 2392326 commit d5e0598

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+5419
-1727
lines changed

controllers/helmchart_controller.go

+222-418
Large diffs are not rendered by default.

controllers/helmchart_controller_test.go

+1-24
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
"path"
2626
"path/filepath"
2727
"strings"
28-
"testing"
2928
"time"
3029

3130
"github.com/fluxcd/pkg/apis/meta"
@@ -732,6 +731,7 @@ var _ = Describe("HelmChartReconciler", func() {
732731
}, timeout, interval).Should(BeTrue())
733732
helmChart, err := loader.Load(storage.LocalPath(*now.Status.Artifact))
734733
Expect(err).NotTo(HaveOccurred())
734+
Expect(helmChart.Values).ToNot(BeNil())
735735
Expect(helmChart.Values["testDefault"]).To(BeTrue())
736736
Expect(helmChart.Values["testOverride"]).To(BeFalse())
737737

@@ -1326,26 +1326,3 @@ var _ = Describe("HelmChartReconciler", func() {
13261326
})
13271327
})
13281328
})
1329-
1330-
func Test_validHelmChartName(t *testing.T) {
1331-
tests := []struct {
1332-
name string
1333-
chart string
1334-
expectErr bool
1335-
}{
1336-
{"valid", "drupal", false},
1337-
{"valid dash", "nginx-lego", false},
1338-
{"valid dashes", "aws-cluster-autoscaler", false},
1339-
{"valid alphanum", "ng1nx-leg0", false},
1340-
{"invalid slash", "artifactory/invalid", true},
1341-
{"invalid dot", "in.valid", true},
1342-
{"invalid uppercase", "inValid", true},
1343-
}
1344-
for _, tt := range tests {
1345-
t.Run(tt.name, func(t *testing.T) {
1346-
if err := validHelmChartName(tt.chart); (err != nil) != tt.expectErr {
1347-
t.Errorf("validHelmChartName() error = %v, expectErr %v", err, tt.expectErr)
1348-
}
1349-
})
1350-
}
1351-
}

controllers/helmrepository_controller.go

+60-48
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ limitations under the License.
1717
package controllers
1818

1919
import (
20-
"bytes"
2120
"context"
2221
"fmt"
2322
"net/url"
23+
"os"
2424
"time"
2525

2626
"github.com/go-logr/logr"
27-
"helm.sh/helm/v3/pkg/getter"
27+
helmgetter "helm.sh/helm/v3/pkg/getter"
2828
corev1 "k8s.io/api/core/v1"
2929
apimeta "k8s.io/apimachinery/pkg/api/meta"
3030
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -37,15 +37,15 @@ import (
3737
"sigs.k8s.io/controller-runtime/pkg/controller"
3838
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3939
"sigs.k8s.io/controller-runtime/pkg/predicate"
40-
"sigs.k8s.io/yaml"
4140

4241
"github.com/fluxcd/pkg/apis/meta"
4342
"github.com/fluxcd/pkg/runtime/events"
4443
"github.com/fluxcd/pkg/runtime/metrics"
4544
"github.com/fluxcd/pkg/runtime/predicates"
4645

4746
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
48-
"github.com/fluxcd/source-controller/internal/helm"
47+
"github.com/fluxcd/source-controller/internal/helm/getter"
48+
"github.com/fluxcd/source-controller/internal/helm/repository"
4949
)
5050

5151
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmrepositories,verbs=get;list;watch;create;update;patch;delete
@@ -58,7 +58,7 @@ type HelmRepositoryReconciler struct {
5858
client.Client
5959
Scheme *runtime.Scheme
6060
Storage *Storage
61-
Getters getter.Providers
61+
Getters helmgetter.Providers
6262
EventRecorder kuberecorder.EventRecorder
6363
ExternalEventRecorder *events.Recorder
6464
MetricsRecorder *metrics.Recorder
@@ -170,96 +170,108 @@ func (r *HelmRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reque
170170
return ctrl.Result{RequeueAfter: repository.GetInterval().Duration}, nil
171171
}
172172

173-
func (r *HelmRepositoryReconciler) reconcile(ctx context.Context, repository sourcev1.HelmRepository) (sourcev1.HelmRepository, error) {
174-
clientOpts := []getter.Option{
175-
getter.WithURL(repository.Spec.URL),
176-
getter.WithTimeout(repository.Spec.Timeout.Duration),
177-
getter.WithPassCredentialsAll(repository.Spec.PassCredentials),
173+
func (r *HelmRepositoryReconciler) reconcile(ctx context.Context, repo sourcev1.HelmRepository) (sourcev1.HelmRepository, error) {
174+
clientOpts := []helmgetter.Option{
175+
helmgetter.WithURL(repo.Spec.URL),
176+
helmgetter.WithTimeout(repo.Spec.Timeout.Duration),
177+
helmgetter.WithPassCredentialsAll(repo.Spec.PassCredentials),
178178
}
179-
if repository.Spec.SecretRef != nil {
179+
if repo.Spec.SecretRef != nil {
180180
name := types.NamespacedName{
181-
Namespace: repository.GetNamespace(),
182-
Name: repository.Spec.SecretRef.Name,
181+
Namespace: repo.GetNamespace(),
182+
Name: repo.Spec.SecretRef.Name,
183183
}
184184

185185
var secret corev1.Secret
186186
err := r.Client.Get(ctx, name, &secret)
187187
if err != nil {
188188
err = fmt.Errorf("auth secret error: %w", err)
189-
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
189+
return sourcev1.HelmRepositoryNotReady(repo, sourcev1.AuthenticationFailedReason, err.Error()), err
190190
}
191191

192-
opts, cleanup, err := helm.ClientOptionsFromSecret(secret)
192+
authDir, err := os.MkdirTemp("", repo.Kind+"-"+repo.Namespace+"-"+repo.Name+"-")
193+
if err != nil {
194+
err = fmt.Errorf("failed to create temporary working directory for credentials: %w", err)
195+
return sourcev1.HelmRepositoryNotReady(repo, sourcev1.AuthenticationFailedReason, err.Error()), err
196+
}
197+
defer os.RemoveAll(authDir)
198+
199+
opts, err := getter.ClientOptionsFromSecret(authDir, secret)
193200
if err != nil {
194201
err = fmt.Errorf("auth options error: %w", err)
195-
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
202+
return sourcev1.HelmRepositoryNotReady(repo, sourcev1.AuthenticationFailedReason, err.Error()), err
196203
}
197-
defer cleanup()
198204
clientOpts = append(clientOpts, opts...)
199205
}
200206

201-
chartRepo, err := helm.NewChartRepository(repository.Spec.URL, r.Getters, clientOpts)
207+
chartRepo, err := repository.NewChartRepository(repo.Spec.URL, "", r.Getters, clientOpts)
202208
if err != nil {
203209
switch err.(type) {
204210
case *url.Error:
205-
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.URLInvalidReason, err.Error()), err
211+
return sourcev1.HelmRepositoryNotReady(repo, sourcev1.URLInvalidReason, err.Error()), err
206212
default:
207-
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.IndexationFailedReason, err.Error()), err
213+
return sourcev1.HelmRepositoryNotReady(repo, sourcev1.IndexationFailedReason, err.Error()), err
208214
}
209215
}
210-
if err := chartRepo.DownloadIndex(); err != nil {
216+
checksum, err := chartRepo.CacheIndex()
217+
if err != nil {
211218
err = fmt.Errorf("failed to download repository index: %w", err)
212-
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.IndexationFailedReason, err.Error()), err
219+
return sourcev1.HelmRepositoryNotReady(repo, sourcev1.IndexationFailedReason, err.Error()), err
213220
}
221+
defer chartRepo.RemoveCache()
214222

215-
indexBytes, err := yaml.Marshal(&chartRepo.Index)
216-
if err != nil {
217-
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
218-
}
219-
hash := r.Storage.Checksum(bytes.NewReader(indexBytes))
220-
artifact := r.Storage.NewArtifactFor(repository.Kind,
221-
repository.ObjectMeta.GetObjectMeta(),
222-
hash,
223-
fmt.Sprintf("index-%s.yaml", hash))
224-
// return early on unchanged index
225-
if apimeta.IsStatusConditionTrue(repository.Status.Conditions, meta.ReadyCondition) && repository.GetArtifact().HasRevision(artifact.Revision) {
226-
if artifact.URL != repository.GetArtifact().URL {
227-
r.Storage.SetArtifactURL(repository.GetArtifact())
228-
repository.Status.URL = r.Storage.SetHostname(repository.Status.URL)
223+
artifact := r.Storage.NewArtifactFor(repo.Kind,
224+
repo.ObjectMeta.GetObjectMeta(),
225+
"",
226+
fmt.Sprintf("index-%s.yaml", checksum))
227+
228+
// Return early on unchanged index
229+
if apimeta.IsStatusConditionTrue(repo.Status.Conditions, meta.ReadyCondition) &&
230+
(repo.GetArtifact() != nil && repo.GetArtifact().Checksum == checksum) {
231+
if artifact.URL != repo.GetArtifact().URL {
232+
r.Storage.SetArtifactURL(repo.GetArtifact())
233+
repo.Status.URL = r.Storage.SetHostname(repo.Status.URL)
229234
}
230-
return repository, nil
235+
return repo, nil
236+
}
237+
238+
// Load the cached repository index to ensure it passes validation
239+
if err := chartRepo.LoadFromCache(); err != nil {
240+
return sourcev1.HelmRepositoryNotReady(repo, sourcev1.IndexationFailedReason, err.Error()), err
231241
}
242+
// The repository checksum is the SHA256 of the loaded bytes, after sorting
243+
artifact.Revision = chartRepo.Checksum
244+
chartRepo.Unload()
232245

233-
// create artifact dir
246+
// Create artifact dir
234247
err = r.Storage.MkdirAll(artifact)
235248
if err != nil {
236249
err = fmt.Errorf("unable to create repository index directory: %w", err)
237-
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
250+
return sourcev1.HelmRepositoryNotReady(repo, sourcev1.StorageOperationFailedReason, err.Error()), err
238251
}
239252

240-
// acquire lock
253+
// Acquire lock
241254
unlock, err := r.Storage.Lock(artifact)
242255
if err != nil {
243256
err = fmt.Errorf("unable to acquire lock: %w", err)
244-
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
257+
return sourcev1.HelmRepositoryNotReady(repo, sourcev1.StorageOperationFailedReason, err.Error()), err
245258
}
246259
defer unlock()
247260

248-
// save artifact to storage
249-
if err := r.Storage.AtomicWriteFile(&artifact, bytes.NewReader(indexBytes), 0644); err != nil {
250-
err = fmt.Errorf("unable to write repository index file: %w", err)
251-
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
261+
// Save artifact to storage
262+
if err = r.Storage.CopyFromPath(&artifact, chartRepo.CachePath); err != nil {
263+
return sourcev1.HelmRepositoryNotReady(repo, sourcev1.StorageOperationFailedReason, err.Error()), err
252264
}
253265

254-
// update index symlink
266+
// Update index symlink
255267
indexURL, err := r.Storage.Symlink(artifact, "index.yaml")
256268
if err != nil {
257269
err = fmt.Errorf("storage error: %w", err)
258-
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
270+
return sourcev1.HelmRepositoryNotReady(repo, sourcev1.StorageOperationFailedReason, err.Error()), err
259271
}
260272

261273
message := fmt.Sprintf("Fetched revision: %s", artifact.Revision)
262-
return sourcev1.HelmRepositoryReady(repository, artifact, indexURL, sourcev1.IndexationSucceededReason, message), nil
274+
return sourcev1.HelmRepositoryReady(repo, artifact, indexURL, sourcev1.IndexationSucceededReason, message), nil
263275
}
264276

265277
func (r *HelmRepositoryReconciler) reconcileDelete(ctx context.Context, repository sourcev1.HelmRepository) (ctrl.Result, error) {

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ require (
3838
github.com/minio/minio-go/v7 v7.0.10
3939
github.com/onsi/ginkgo v1.16.4
4040
github.com/onsi/gomega v1.14.0
41+
github.com/otiai10/copy v1.7.0
4142
github.com/spf13/pflag v1.0.5
4243
github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 // indirect
4344
github.com/yvasiyarov/gorelic v0.0.7 // indirect

go.sum

+7
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,13 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS
738738
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
739739
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
740740
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
741+
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
742+
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
743+
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
744+
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
745+
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
746+
github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI=
747+
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
741748
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
742749
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
743750
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=

internal/helm/chart.go

-59
This file was deleted.

0 commit comments

Comments
 (0)