github.com/helmwave/helmwave@v0.36.4-0.20240509190856-b35563eba4c6/pkg/release/upgrade.go (about)

     1  package release
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"strings"
     8  
     9  	"github.com/helmwave/helmwave/pkg/helper"
    10  	"helm.sh/helm/v3/pkg/chart"
    11  	"helm.sh/helm/v3/pkg/cli/values"
    12  	"helm.sh/helm/v3/pkg/getter"
    13  	"helm.sh/helm/v3/pkg/release"
    14  )
    15  
    16  // Helm wraps a lot of meta.NoKindMatchError into fmt.Errorf which makes errors.Is unusable.
    17  // So we have to find this substring in error string.
    18  const errMissingCRD = "unable to build kubernetes objects from release manifest:"
    19  
    20  func (rel *config) upgrade(ctx context.Context) (*release.Release, error) {
    21  	ch, err := rel.GetChart()
    22  	if err != nil {
    23  		return nil, err
    24  	}
    25  
    26  	// Values
    27  	valuesFiles := helper.SlicesMap(rel.Values(), func(v ValuesReference) string {
    28  		return v.Dst
    29  	})
    30  
    31  	valOpts := &values.Options{ValueFiles: valuesFiles}
    32  	vals, err := valOpts.MergeValues(getter.All(rel.Helm()))
    33  	if err != nil {
    34  		return nil, fmt.Errorf("failed to merge values %v: %w", valuesFiles, err)
    35  	}
    36  
    37  	// Install or Template
    38  	if rel.dryRun {
    39  		rel.Logger().Debug("I'll dry-run.")
    40  		r, err := rel.installWithRetry(ctx, ch, vals)
    41  		if err != nil {
    42  			return nil, fmt.Errorf("failed with dry-run %q: %w", rel.Uniq(), err)
    43  		}
    44  
    45  		return r, nil
    46  	} else if !rel.dryRun && !rel.isInstalled() {
    47  		rel.Logger().Debug("🧐 Release does not exist. Installing it now.")
    48  		r, err := rel.installWithRetry(ctx, ch, vals)
    49  		if err != nil {
    50  			return nil, fmt.Errorf("failed to install %q: %w", rel.Uniq(), err)
    51  		}
    52  
    53  		return r, nil
    54  	}
    55  
    56  	pending, err := rel.isPending()
    57  	if err != nil {
    58  		return nil, fmt.Errorf("failed to check %q for pending status: %w", rel.Uniq(), err)
    59  	}
    60  	if pending {
    61  		err := rel.fixPending(ctx)
    62  		if err != nil {
    63  			return nil, fmt.Errorf("failed to fix %q pending status: %w", rel.Uniq(), err)
    64  		}
    65  	}
    66  
    67  	// Upgrade
    68  	r, err := rel.upgradeWithRetry(ctx, ch, vals)
    69  	if err != nil {
    70  		return nil, fmt.Errorf("failed to upgrade %s: %w", rel.Uniq(), err)
    71  	}
    72  
    73  	return r, nil
    74  }
    75  
    76  //nolint:wrapcheck // we wrap it later
    77  func (rel *config) installWithRetry(
    78  	ctx context.Context,
    79  	ch *chart.Chart,
    80  	vals map[string]interface{},
    81  ) (*release.Release, error) {
    82  	r, err := rel.newInstall().RunWithContext(ctx, ch, vals)
    83  
    84  	if err != nil && strings.Contains(err.Error(), errMissingCRD) && rel.dryRun {
    85  		er := rel.forceOfflineKubeVersion()
    86  		// return original error if we can't get kubernetes version
    87  		if er != nil {
    88  			return r, err
    89  		}
    90  
    91  		return rel.newInstall().RunWithContext(ctx, ch, vals)
    92  	}
    93  
    94  	return r, err
    95  }
    96  
    97  //nolint:wrapcheck // we wrap it later
    98  func (rel *config) upgradeWithRetry(
    99  	ctx context.Context,
   100  	ch *chart.Chart,
   101  	vals map[string]interface{},
   102  ) (*release.Release, error) {
   103  	r, err := rel.newUpgrade().RunWithContext(ctx, rel.Name(), ch, vals)
   104  
   105  	if err != nil && strings.Contains(err.Error(), errMissingCRD) && rel.dryRun {
   106  		er := rel.forceOfflineKubeVersion()
   107  		// return original error if we can't get kubernetes version
   108  		if er != nil {
   109  			return r, err
   110  		}
   111  
   112  		return rel.newUpgrade().RunWithContext(ctx, rel.Name(), ch, vals)
   113  	}
   114  
   115  	return r, err
   116  }
   117  
   118  func (rel *config) forceOfflineKubeVersion() error {
   119  	rel.Logger().Warn("🤔hmm, it looks like some required CRDs are not installed, setting offline_kube_version and trying again")
   120  
   121  	v, err := helper.GetKubernetesVersion(rel.Cfg())
   122  	if err != nil {
   123  		rel.Logger().WithError(err).Error("cannot get current kubernetes version, you need to set it manually")
   124  
   125  		return err
   126  	}
   127  
   128  	rel.OfflineKubeVersionF = v.GitVersion
   129  	rel.Logger().WithField("version", rel.OfflineKubeVersionF).Info("discovered kubernetes version")
   130  
   131  	return nil
   132  }
   133  
   134  func (rel *config) test() error {
   135  	rel.Logger().Info("running helm tests")
   136  
   137  	client := rel.newTest()
   138  	r, err := client.Run(rel.Name())
   139  
   140  	if (err != nil) || rel.Tests.ForceShowLogs {
   141  		var buf bytes.Buffer
   142  		_ = client.GetPodLogs(&buf, r)
   143  
   144  		if err != nil {
   145  			rel.Logger().WithError(err).WithField("output", buf.String()).Error("helm tests failed")
   146  
   147  			return NewHelmTestsError(err)
   148  		}
   149  
   150  		rel.Logger().WithField("output", buf.String()).Info("helm tests output")
   151  	}
   152  
   153  	return nil
   154  }