@@ -18,6 +18,7 @@ package controllers
1818
1919import (
2020 "context"
21+ "crypto/tls"
2122 "errors"
2223 "fmt"
2324 "net/http"
@@ -33,6 +34,7 @@ import (
3334 "github.com/fluxcd/pkg/runtime/conditions"
3435 "github.com/fluxcd/pkg/runtime/patch"
3536 . "github.com/onsi/gomega"
37+ helmgetter "helm.sh/helm/v3/pkg/getter"
3638 corev1 "k8s.io/api/core/v1"
3739 apierrors "k8s.io/apimachinery/pkg/api/errors"
3840 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -43,6 +45,7 @@ import (
4345 fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
4446
4547 sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
48+ "github.com/fluxcd/source-controller/internal/helm/getter"
4649 "github.com/fluxcd/source-controller/internal/helm/repository"
4750 sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
4851 "github.com/fluxcd/source-controller/internal/reconcile/summarize"
@@ -288,8 +291,8 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
288291 protocol string
289292 server options
290293 secret * corev1.Secret
291- beforeFunc func (t * WithT , obj * sourcev1.HelmRepository )
292- afterFunc func (t * WithT , obj * sourcev1.HelmRepository )
294+ beforeFunc func (t * WithT , obj * sourcev1.HelmRepository , checksum string )
295+ afterFunc func (t * WithT , obj * sourcev1.HelmRepository , artifact sourcev1. Artifact , chartRepo repository. ChartRepository )
293296 want sreconcile.Result
294297 wantErr bool
295298 assertConditions []metav1.Condition
@@ -302,6 +305,12 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
302305 * conditions .TrueCondition (sourcev1 .ArtifactOutdatedCondition , "NewRevision" , "new index revision" ),
303306 * conditions .TrueCondition (meta .ReconcilingCondition , "NewRevision" , "new index revision" ),
304307 },
308+ afterFunc : func (t * WithT , obj * sourcev1.HelmRepository , artifact sourcev1.Artifact , chartRepo repository.ChartRepository ) {
309+ t .Expect (chartRepo .Checksum ).ToNot (BeEmpty ())
310+ t .Expect (chartRepo .CachePath ).ToNot (BeEmpty ())
311+ t .Expect (artifact .Checksum ).To (BeEmpty ())
312+ t .Expect (artifact .Revision ).ToNot (BeEmpty ())
313+ },
305314 },
306315 {
307316 name : "HTTP with Basic Auth secret makes ArtifactOutdated=True" ,
@@ -319,14 +328,20 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
319328 "password" : []byte ("1234" ),
320329 },
321330 },
322- beforeFunc : func (t * WithT , obj * sourcev1.HelmRepository ) {
331+ beforeFunc : func (t * WithT , obj * sourcev1.HelmRepository , checksum string ) {
323332 obj .Spec .SecretRef = & meta.LocalObjectReference {Name : "basic-auth" }
324333 },
325334 want : sreconcile .ResultSuccess ,
326335 assertConditions : []metav1.Condition {
327336 * conditions .TrueCondition (sourcev1 .ArtifactOutdatedCondition , "NewRevision" , "new index revision" ),
328337 * conditions .TrueCondition (meta .ReconcilingCondition , "NewRevision" , "new index revision" ),
329338 },
339+ afterFunc : func (t * WithT , obj * sourcev1.HelmRepository , artifact sourcev1.Artifact , chartRepo repository.ChartRepository ) {
340+ t .Expect (chartRepo .Checksum ).ToNot (BeEmpty ())
341+ t .Expect (chartRepo .CachePath ).ToNot (BeEmpty ())
342+ t .Expect (artifact .Checksum ).To (BeEmpty ())
343+ t .Expect (artifact .Revision ).ToNot (BeEmpty ())
344+ },
330345 },
331346 {
332347 name : "HTTPS with CAFile secret makes ArtifactOutdated=True" ,
@@ -344,14 +359,20 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
344359 "caFile" : tlsCA ,
345360 },
346361 },
347- beforeFunc : func (t * WithT , obj * sourcev1.HelmRepository ) {
362+ beforeFunc : func (t * WithT , obj * sourcev1.HelmRepository , checksum string ) {
348363 obj .Spec .SecretRef = & meta.LocalObjectReference {Name : "ca-file" }
349364 },
350365 want : sreconcile .ResultSuccess ,
351366 assertConditions : []metav1.Condition {
352367 * conditions .TrueCondition (sourcev1 .ArtifactOutdatedCondition , "NewRevision" , "new index revision" ),
353368 * conditions .TrueCondition (meta .ReconcilingCondition , "NewRevision" , "new index revision" ),
354369 },
370+ afterFunc : func (t * WithT , obj * sourcev1.HelmRepository , artifact sourcev1.Artifact , chartRepo repository.ChartRepository ) {
371+ t .Expect (chartRepo .Checksum ).ToNot (BeEmpty ())
372+ t .Expect (chartRepo .CachePath ).ToNot (BeEmpty ())
373+ t .Expect (artifact .Checksum ).To (BeEmpty ())
374+ t .Expect (artifact .Revision ).ToNot (BeEmpty ())
375+ },
355376 },
356377 {
357378 name : "HTTPS with invalid CAFile secret makes FetchFailed=True and returns error" ,
@@ -369,48 +390,76 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
369390 "caFile" : []byte ("invalid" ),
370391 },
371392 },
372- beforeFunc : func (t * WithT , obj * sourcev1.HelmRepository ) {
393+ beforeFunc : func (t * WithT , obj * sourcev1.HelmRepository , checksum string ) {
373394 obj .Spec .SecretRef = & meta.LocalObjectReference {Name : "invalid-ca" }
374395 },
375396 wantErr : true ,
376397 assertConditions : []metav1.Condition {
377398 * conditions .TrueCondition (sourcev1 .FetchFailedCondition , sourcev1 .AuthenticationFailedReason , "failed to create TLS client config with secret data: cannot append certificate into certificate pool: invalid caFile" ),
378399 },
400+ afterFunc : func (t * WithT , obj * sourcev1.HelmRepository , artifact sourcev1.Artifact , chartRepo repository.ChartRepository ) {
401+ // No repo index due to fetch fail.
402+ t .Expect (chartRepo .Checksum ).To (BeEmpty ())
403+ t .Expect (chartRepo .CachePath ).To (BeEmpty ())
404+ t .Expect (artifact .Checksum ).To (BeEmpty ())
405+ t .Expect (artifact .Revision ).To (BeEmpty ())
406+ },
379407 },
380408 {
381409 name : "Invalid URL makes FetchFailed=True and returns stalling error" ,
382410 protocol : "http" ,
383- beforeFunc : func (t * WithT , obj * sourcev1.HelmRepository ) {
411+ beforeFunc : func (t * WithT , obj * sourcev1.HelmRepository , checksum string ) {
384412 obj .Spec .URL = strings .ReplaceAll (obj .Spec .URL , "http://" , "" )
385413 },
386414 want : sreconcile .ResultEmpty ,
387415 wantErr : true ,
388416 assertConditions : []metav1.Condition {
389417 * conditions .TrueCondition (sourcev1 .FetchFailedCondition , sourcev1 .URLInvalidReason , "first path segment in URL cannot contain colon" ),
390418 },
419+ afterFunc : func (t * WithT , obj * sourcev1.HelmRepository , artifact sourcev1.Artifact , chartRepo repository.ChartRepository ) {
420+ // No repo index due to fetch fail.
421+ t .Expect (chartRepo .Checksum ).To (BeEmpty ())
422+ t .Expect (chartRepo .CachePath ).To (BeEmpty ())
423+ t .Expect (artifact .Checksum ).To (BeEmpty ())
424+ t .Expect (artifact .Revision ).To (BeEmpty ())
425+ },
391426 },
392427 {
393428 name : "Unsupported scheme makes FetchFailed=True and returns stalling error" ,
394429 protocol : "http" ,
395- beforeFunc : func (t * WithT , obj * sourcev1.HelmRepository ) {
430+ beforeFunc : func (t * WithT , obj * sourcev1.HelmRepository , checksum string ) {
396431 obj .Spec .URL = strings .ReplaceAll (obj .Spec .URL , "http://" , "ftp://" )
397432 },
398433 want : sreconcile .ResultEmpty ,
399434 wantErr : true ,
400435 assertConditions : []metav1.Condition {
401436 * conditions .TrueCondition (sourcev1 .FetchFailedCondition , meta .FailedReason , "scheme \" ftp\" not supported" ),
402437 },
438+ afterFunc : func (t * WithT , obj * sourcev1.HelmRepository , artifact sourcev1.Artifact , chartRepo repository.ChartRepository ) {
439+ // No repo index due to fetch fail.
440+ t .Expect (chartRepo .Checksum ).To (BeEmpty ())
441+ t .Expect (chartRepo .CachePath ).To (BeEmpty ())
442+ t .Expect (artifact .Checksum ).To (BeEmpty ())
443+ t .Expect (artifact .Revision ).To (BeEmpty ())
444+ },
403445 },
404446 {
405447 name : "Missing secret returns FetchFailed=True and returns error" ,
406448 protocol : "http" ,
407- beforeFunc : func (t * WithT , obj * sourcev1.HelmRepository ) {
449+ beforeFunc : func (t * WithT , obj * sourcev1.HelmRepository , checksum string ) {
408450 obj .Spec .SecretRef = & meta.LocalObjectReference {Name : "non-existing" }
409451 },
410452 wantErr : true ,
411453 assertConditions : []metav1.Condition {
412454 * conditions .TrueCondition (sourcev1 .FetchFailedCondition , sourcev1 .AuthenticationFailedReason , "secrets \" non-existing\" not found" ),
413455 },
456+ afterFunc : func (t * WithT , obj * sourcev1.HelmRepository , artifact sourcev1.Artifact , chartRepo repository.ChartRepository ) {
457+ // No repo index due to fetch fail.
458+ t .Expect (chartRepo .Checksum ).To (BeEmpty ())
459+ t .Expect (chartRepo .CachePath ).To (BeEmpty ())
460+ t .Expect (artifact .Checksum ).To (BeEmpty ())
461+ t .Expect (artifact .Revision ).To (BeEmpty ())
462+ },
414463 },
415464 {
416465 name : "Malformed secret returns FetchFailed=True and returns error" ,
@@ -423,13 +472,56 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
423472 "username" : []byte ("git" ),
424473 },
425474 },
426- beforeFunc : func (t * WithT , obj * sourcev1.HelmRepository ) {
475+ beforeFunc : func (t * WithT , obj * sourcev1.HelmRepository , checksum string ) {
427476 obj .Spec .SecretRef = & meta.LocalObjectReference {Name : "malformed-basic-auth" }
428477 },
429478 wantErr : true ,
430479 assertConditions : []metav1.Condition {
431480 * conditions .TrueCondition (sourcev1 .FetchFailedCondition , sourcev1 .AuthenticationFailedReason , "required fields 'username' and 'password" ),
432481 },
482+ afterFunc : func (t * WithT , obj * sourcev1.HelmRepository , artifact sourcev1.Artifact , chartRepo repository.ChartRepository ) {
483+ // No repo index due to fetch fail.
484+ t .Expect (chartRepo .Checksum ).To (BeEmpty ())
485+ t .Expect (chartRepo .CachePath ).To (BeEmpty ())
486+ t .Expect (artifact .Checksum ).To (BeEmpty ())
487+ t .Expect (artifact .Revision ).To (BeEmpty ())
488+ },
489+ },
490+ {
491+ name : "cached index with same checksum" ,
492+ protocol : "http" ,
493+ beforeFunc : func (t * WithT , obj * sourcev1.HelmRepository , checksum string ) {
494+ obj .Status .Artifact = & sourcev1.Artifact {
495+ Revision : checksum ,
496+ Checksum : checksum ,
497+ }
498+ },
499+ afterFunc : func (t * WithT , obj * sourcev1.HelmRepository , artifact sourcev1.Artifact , chartRepo repository.ChartRepository ) {
500+ // chartRepo.Checksum isn't populated, artifact.Checksum is
501+ // populated from the cached repo index data.
502+ t .Expect (chartRepo .Checksum ).To (BeEmpty ())
503+ t .Expect (chartRepo .CachePath ).ToNot (BeEmpty ())
504+ t .Expect (artifact .Checksum ).To (Equal (obj .Status .Artifact .Checksum ))
505+ t .Expect (artifact .Revision ).To (Equal (obj .Status .Artifact .Revision ))
506+ },
507+ want : sreconcile .ResultSuccess ,
508+ },
509+ {
510+ name : "cached index with different checksum" ,
511+ protocol : "http" ,
512+ beforeFunc : func (t * WithT , obj * sourcev1.HelmRepository , checksum string ) {
513+ obj .Status .Artifact = & sourcev1.Artifact {
514+ Revision : checksum ,
515+ Checksum : "foo" ,
516+ }
517+ },
518+ afterFunc : func (t * WithT , obj * sourcev1.HelmRepository , artifact sourcev1.Artifact , chartRepo repository.ChartRepository ) {
519+ t .Expect (chartRepo .Checksum ).ToNot (BeEmpty ())
520+ t .Expect (chartRepo .CachePath ).ToNot (BeEmpty ())
521+ t .Expect (artifact .Checksum ).To (BeEmpty ())
522+ t .Expect (artifact .Revision ).To (Equal (obj .Status .Artifact .Revision ))
523+ },
524+ want : sreconcile .ResultSuccess ,
433525 },
434526 }
435527
@@ -481,15 +573,51 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
481573 t .Fatalf ("unsupported protocol %q" , tt .protocol )
482574 }
483575
484- if tt .beforeFunc != nil {
485- tt .beforeFunc (g , obj )
486- }
487-
488576 builder := fakeclient .NewClientBuilder ().WithScheme (testEnv .GetScheme ())
489577 if secret != nil {
490578 builder .WithObjects (secret .DeepCopy ())
491579 }
492580
581+ // Calculate the artifact checksum for valid repos configurations.
582+ clientOpts := []helmgetter.Option {
583+ helmgetter .WithURL (server .URL ()),
584+ }
585+ var newChartRepo * repository.ChartRepository
586+ var tOpts * tls.Config
587+ validSecret := true
588+ if secret != nil {
589+ // Extract the client options from secret, ignoring any invalid
590+ // value. validSecret is used to determine if the indexChecksum
591+ // should be calculated below.
592+ var cOpts []helmgetter.Option
593+ var serr error
594+ cOpts , serr = getter .ClientOptionsFromSecret (* secret )
595+ if serr != nil {
596+ validSecret = false
597+ }
598+ clientOpts = append (clientOpts , cOpts ... )
599+ tOpts , serr = getter .TLSClientConfigFromSecret (* secret , server .URL ())
600+ if serr != nil {
601+ validSecret = false
602+ }
603+ newChartRepo , err = repository .NewChartRepository (obj .Spec .URL , "" , testGetters , tOpts , clientOpts )
604+ } else {
605+ newChartRepo , err = repository .NewChartRepository (obj .Spec .URL , "" , testGetters , nil , nil )
606+ }
607+ g .Expect (err ).ToNot (HaveOccurred ())
608+
609+ // NOTE: checksum will be empty in beforeFunc for invalid repo
610+ // configurations as the client can't get the repo.
611+ var indexChecksum string
612+ if validSecret {
613+ indexChecksum , err = newChartRepo .CacheIndex ()
614+ g .Expect (err ).ToNot (HaveOccurred ())
615+ }
616+
617+ if tt .beforeFunc != nil {
618+ tt .beforeFunc (g , obj , indexChecksum )
619+ }
620+
493621 r := & HelmRepositoryReconciler {
494622 EventRecorder : record .NewFakeRecorder (32 ),
495623 Client : builder .Build (),
@@ -507,7 +635,7 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
507635 g .Expect (got ).To (Equal (tt .want ))
508636
509637 if tt .afterFunc != nil {
510- tt .afterFunc (g , obj )
638+ tt .afterFunc (g , obj , artifact , chartRepo )
511639 }
512640 })
513641 }
0 commit comments