Skip to content

Commit 527fce0

Browse files
committed
Rewrite HelmChartReconciler tests
Signed-off-by: Hidde Beydals <[email protected]>
1 parent 8e107ea commit 527fce0

File tree

3 files changed

+1496
-1295
lines changed

3 files changed

+1496
-1295
lines changed

controllers/helmchart_controller.go

Lines changed: 88 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -314,13 +314,7 @@ func (r *HelmChartReconciler) reconcileStorage(ctx context.Context, obj *sourcev
314314
return sreconcile.ResultSuccess, nil
315315
}
316316

317-
// reconcileSource reconciles the upstream bucket with the client for the given object's Provider, and returns the
318-
// result.
319-
// If a SecretRef is defined, it attempts to fetch the Secret before calling the provider. If the fetch of the Secret
320-
// fails, it records v1beta1.FetchFailedCondition=True and returns early.
321-
//
322-
// The caller should assume a failure if an error is returned, or the BuildResult is zero.
323-
func (r *HelmChartReconciler) reconcileSource(ctx context.Context, obj *sourcev1.HelmChart, build *chart.Build) (sreconcile.Result, error) {
317+
func (r *HelmChartReconciler) reconcileSource(ctx context.Context, obj *sourcev1.HelmChart, build *chart.Build) (_ sreconcile.Result, retErr error) {
324318
// Retrieve the source
325319
s, err := r.getSource(ctx, obj)
326320
if err != nil {
@@ -332,7 +326,7 @@ func (r *HelmChartReconciler) reconcileSource(ctx context.Context, obj *sourcev1
332326

333327
// Return Kubernetes client errors, but ignore others which can only be
334328
// solved by a change in generation
335-
if apierrs.ReasonForError(err) != metav1.StatusReasonUnknown {
329+
if apierrs.ReasonForError(err) == metav1.StatusReasonUnknown {
336330
return sreconcile.ResultEmpty, &serror.Stalling{
337331
Err: fmt.Errorf("failed to get source: %w", err),
338332
Reason: "UnsupportedSourceKind",
@@ -353,20 +347,52 @@ func (r *HelmChartReconciler) reconcileSource(ctx context.Context, obj *sourcev1
353347
// Record current artifact revision as last observed
354348
obj.Status.ObservedSourceArtifactRevision = s.GetArtifact().Revision
355349

356-
// Perform the reconciliation for the chart source type
350+
// Defer observation of build result
351+
defer func() {
352+
// Record both success and error observations on the object
353+
observeChartBuild(obj, build, retErr)
354+
355+
// If we actually build a chart, take a historical note of any dependencies we resolved.
356+
// The reason this is a done conditionally, is because if we have a cached one in storage,
357+
// we can not recover this information (and put it in a condition). Which would result in
358+
// a sudden (partial) disappearance of observed state.
359+
// TODO(hidde): include specific name/version information?
360+
if depNum := build.ResolvedDependencies; build.Complete() && depNum > 0 {
361+
r.Eventf(obj, corev1.EventTypeNormal, "ResolvedDependencies", "Resolved %d chart dependencies", depNum)
362+
}
363+
364+
// Handle any build error
365+
if retErr != nil {
366+
e := fmt.Errorf("failed to build chart from source artifact: %w", retErr)
367+
retErr = &serror.Event{
368+
Err: e,
369+
Reason: meta.FailedReason,
370+
}
371+
if buildErr := new(chart.BuildError); errors.As(e, &buildErr) {
372+
if chart.IsPersistentBuildErrorReason(buildErr.Reason) {
373+
retErr = &serror.Stalling{
374+
Err: e,
375+
Reason: buildErr.Reason.Reason,
376+
}
377+
}
378+
}
379+
}
380+
}()
381+
382+
// Perform the build for the chart source type
357383
switch typedSource := s.(type) {
358384
case *sourcev1.HelmRepository:
359-
return r.reconcileFromHelmRepository(ctx, obj, typedSource, build)
385+
return r.buildFromHelmRepository(ctx, obj, typedSource, build)
360386
case *sourcev1.GitRepository, *sourcev1.Bucket:
361-
return r.reconcileFromTarballArtifact(ctx, obj, *typedSource.GetArtifact(), build)
387+
return r.buildFromTarballArtifact(ctx, obj, *typedSource.GetArtifact(), build)
362388
default:
363389
// Ending up here should generally not be possible
364390
// as getSource already validates
365391
return sreconcile.ResultEmpty, nil
366392
}
367393
}
368394

369-
func (r *HelmChartReconciler) reconcileFromHelmRepository(ctx context.Context, obj *sourcev1.HelmChart,
395+
func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *sourcev1.HelmChart,
370396
repo *sourcev1.HelmRepository, b *chart.Build) (sreconcile.Result, error) {
371397

372398
// Construct the Getter options from the HelmRepository data
@@ -454,34 +480,15 @@ func (r *HelmChartReconciler) reconcileFromHelmRepository(ctx context.Context, o
454480
// Build the chart
455481
ref := chart.RemoteReference{Name: obj.Spec.Chart, Version: obj.Spec.Version}
456482
build, err := cb.Build(ctx, ref, util.TempPathForObj("", ".tgz", obj), opts)
457-
458-
// Record both success _and_ error observations on the object
459-
processChartBuild(obj, build, err)
460-
461-
// Handle any build error
462483
if err != nil {
463-
e := fmt.Errorf("failed to build chart from remote source: %w", err)
464-
reason := meta.FailedReason
465-
if buildErr := new(chart.BuildError); errors.As(err, &buildErr) {
466-
reason = buildErr.Reason.Reason
467-
if chart.IsPersistentBuildErrorReason(buildErr.Reason) {
468-
return sreconcile.ResultEmpty, &serror.Stalling{
469-
Err: e,
470-
Reason: reason,
471-
}
472-
}
473-
}
474-
return sreconcile.ResultEmpty, &serror.Event{
475-
Err: e,
476-
Reason: reason,
477-
}
484+
return sreconcile.ResultEmpty, err
478485
}
479486

480487
*b = *build
481488
return sreconcile.ResultSuccess, nil
482489
}
483490

484-
func (r *HelmChartReconciler) reconcileFromTarballArtifact(ctx context.Context, obj *sourcev1.HelmChart, source sourcev1.Artifact, b *chart.Build) (sreconcile.Result, error) {
491+
func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj *sourcev1.HelmChart, source sourcev1.Artifact, b *chart.Build) (sreconcile.Result, error) {
485492
// Create temporary working directory
486493
tmpDir, err := util.TempDirForObj("", obj)
487494
if err != nil {
@@ -533,7 +540,7 @@ func (r *HelmChartReconciler) reconcileFromTarballArtifact(ctx context.Context,
533540
chartPath, err := securejoin.SecureJoin(sourceDir, obj.Spec.Chart)
534541
if err != nil {
535542
e := &serror.Stalling{
536-
Err: fmt.Errorf("Path calculation for chart '%s' failed: %w", obj.Spec.Chart, err),
543+
Err: fmt.Errorf("path calculation for chart '%s' failed: %w", obj.Spec.Chart, err),
537544
Reason: "IllegalPath",
538545
}
539546
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, "IllegalPath", e.Err.Error())
@@ -563,12 +570,6 @@ func (r *HelmChartReconciler) reconcileFromTarballArtifact(ctx context.Context,
563570
opts.CachedChart = r.Storage.LocalPath(*artifact)
564571
}
565572

566-
// Add revision metadata to chart build
567-
if obj.Spec.ReconcileStrategy == sourcev1.ReconcileStrategyRevision {
568-
// Isolate the commit SHA from GitRepository type artifacts by removing the branch/ prefix.
569-
splitRev := strings.Split(source.Revision, "/")
570-
opts.VersionMetadata = splitRev[len(splitRev)-1]
571-
}
572573
// Configure revision metadata for chart build if we should react to revision changes
573574
if obj.Spec.ReconcileStrategy == sourcev1.ReconcileStrategyRevision {
574575
rev := source.Revision
@@ -593,43 +594,23 @@ func (r *HelmChartReconciler) reconcileFromTarballArtifact(ctx context.Context,
593594
}
594595
opts.VersionMetadata = rev
595596
}
597+
// Set the VersionMetadata to the object's Generation if ValuesFiles is defined,
598+
// this ensures changes can be noticed by the Artifact consumer
599+
if len(opts.GetValuesFiles()) > 0 {
600+
if opts.VersionMetadata != "" {
601+
opts.VersionMetadata += "."
602+
}
603+
opts.VersionMetadata += strconv.FormatInt(obj.Generation, 10)
604+
}
596605

597606
// Build chart
598607
cb := chart.NewLocalBuilder(dm)
599608
build, err := cb.Build(ctx, chart.LocalReference{
600609
WorkDir: sourceDir,
601610
Path: chartPath,
602611
}, util.TempPathForObj("", ".tgz", obj), opts)
603-
604-
// Record both success _and_ error observations on the object
605-
processChartBuild(obj, build, err)
606-
607-
// Handle any build error
608612
if err != nil {
609-
e := fmt.Errorf("failed to build chart from source artifact: %w", err)
610-
reason := meta.FailedReason
611-
if buildErr := new(chart.BuildError); errors.As(err, &buildErr) {
612-
reason = buildErr.Reason.Reason
613-
if chart.IsPersistentBuildErrorReason(buildErr.Reason) {
614-
return sreconcile.ResultEmpty, &serror.Stalling{
615-
Err: e,
616-
Reason: reason,
617-
}
618-
}
619-
}
620-
return sreconcile.ResultEmpty, &serror.Event{
621-
Err: e,
622-
Reason: reason,
623-
}
624-
}
625-
626-
// If we actually build a chart, take a historical note of any dependencies we resolved.
627-
// The reason this is a done conditionally, is because if we have a cached one in storage,
628-
// we can not recover this information (and put it in a condition). Which would result in
629-
// a sudden (partial) disappearance of observed state.
630-
// TODO(hidde): include specific name/version information?
631-
if depNum := build.ResolvedDependencies; depNum > 0 {
632-
r.eventLogf(ctx, obj, corev1.EventTypeNormal, "ResolvedDependencies", "resolved %d chart dependencies", depNum)
613+
return sreconcile.ResultEmpty, err
633614
}
634615

635616
*b = *build
@@ -761,7 +742,7 @@ func (r *HelmChartReconciler) reconcileDelete(ctx context.Context, obj *sourcev1
761742
}
762743

763744
// garbageCollect performs a garbage collection for the given v1beta1.HelmChart. It removes all but the current
764-
// artifact except for when the deletion timestamp is set, which will result in the removal of all artifacts for the
745+
// artifact, unless the deletion timestamp is set. Which will result in the removal of all artifacts for the
765746
// resource.
766747
func (r *HelmChartReconciler) garbageCollect(ctx context.Context, obj *sourcev1.HelmChart) error {
767748
if !obj.DeletionTimestamp.IsZero() {
@@ -839,6 +820,7 @@ func (r *HelmChartReconciler) resolveDependencyRepository(ctx context.Context, u
839820
listOpts := []client.ListOption{
840821
client.InNamespace(namespace),
841822
client.MatchingFields{sourcev1.HelmRepositoryURLIndexKey: url},
823+
client.Limit(1),
842824
}
843825
var list sourcev1.HelmRepositoryList
844826
err := r.Client.List(ctx, &list, listOpts...)
@@ -968,54 +950,60 @@ func (r *HelmChartReconciler) requestsForBucketChange(o client.Object) []reconci
968950
return reqs
969951
}
970952

971-
func processChartBuild(obj *sourcev1.HelmChart, build *chart.Build, err error) {
953+
// eventLogf records event and logs at the same time. This log is different from
954+
// the debug log in the event recorder in the sense that this is a simple log,
955+
// the event recorder debug log contains complete details about the event.
956+
func (r *HelmChartReconciler) eventLogf(ctx context.Context, obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) {
957+
msg := fmt.Sprintf(messageFmt, args...)
958+
// Log and emit event.
959+
if eventType == corev1.EventTypeWarning {
960+
ctrl.LoggerFrom(ctx).Error(errors.New(reason), msg)
961+
} else {
962+
ctrl.LoggerFrom(ctx).Info(msg)
963+
}
964+
r.Eventf(obj, eventType, reason, msg)
965+
}
966+
967+
// observeChartBuild records the observation on the given given build and error on the object.
968+
func observeChartBuild(obj *sourcev1.HelmChart, build *chart.Build, err error) {
972969
if build.HasMetadata() {
973970
if build.Name != obj.Status.ObservedChartName || !obj.GetArtifact().HasRevision(build.Version) {
974971
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "NewChart", build.Summary())
975972
}
976973
}
977974

978-
if err == nil {
975+
if build.Complete() {
979976
conditions.Delete(obj, sourcev1.FetchFailedCondition)
980977
conditions.Delete(obj, sourcev1.BuildFailedCondition)
981-
return
982978
}
983979

984-
var buildErr *chart.BuildError
985-
if ok := errors.As(err, &buildErr); !ok {
986-
buildErr = &chart.BuildError{
987-
Reason: chart.ErrUnknown,
988-
Err: err,
980+
if err != nil {
981+
var buildErr *chart.BuildError
982+
if ok := errors.As(err, &buildErr); !ok {
983+
buildErr = &chart.BuildError{
984+
Reason: chart.ErrUnknown,
985+
Err: err,
986+
}
989987
}
990-
}
991988

992-
switch buildErr.Reason {
993-
case chart.ErrChartMetadataPatch, chart.ErrValuesFilesMerge, chart.ErrDependencyBuild, chart.ErrChartPackage:
994-
conditions.Delete(obj, sourcev1.FetchFailedCondition)
995-
conditions.MarkTrue(obj, sourcev1.BuildFailedCondition, buildErr.Reason.Reason, buildErr.Error())
996-
default:
997-
conditions.Delete(obj, sourcev1.BuildFailedCondition)
998-
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, buildErr.Reason.Reason, buildErr.Error())
989+
switch buildErr.Reason {
990+
case chart.ErrChartMetadataPatch, chart.ErrValuesFilesMerge, chart.ErrDependencyBuild, chart.ErrChartPackage:
991+
conditions.Delete(obj, sourcev1.FetchFailedCondition)
992+
conditions.MarkTrue(obj, sourcev1.BuildFailedCondition, buildErr.Reason.Reason, buildErr.Error())
993+
default:
994+
conditions.Delete(obj, sourcev1.BuildFailedCondition)
995+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, buildErr.Reason.Reason, buildErr.Error())
996+
}
997+
return
999998
}
1000999
}
10011000

10021001
func reasonForBuild(build *chart.Build) string {
1002+
if !build.Complete() {
1003+
return ""
1004+
}
10031005
if build.Packaged {
10041006
return sourcev1.ChartPackageSucceededReason
10051007
}
10061008
return sourcev1.ChartPullSucceededReason
10071009
}
1008-
1009-
// eventLog records event and logs at the same time. This log is different from
1010-
// the debug log in the event recorder in the sense that this is a simple log,
1011-
// the event recorder debug log contains complete details about the event.
1012-
func (r *HelmChartReconciler) eventLogf(ctx context.Context, obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) {
1013-
msg := fmt.Sprintf(messageFmt, args...)
1014-
// Log and emit event.
1015-
if eventType == corev1.EventTypeWarning {
1016-
ctrl.LoggerFrom(ctx).Error(errors.New(reason), msg)
1017-
} else {
1018-
ctrl.LoggerFrom(ctx).Info(msg)
1019-
}
1020-
r.Eventf(obj, eventType, reason, msg)
1021-
}

0 commit comments

Comments
 (0)