github.com/jaylevin/jenkins-library@v1.230.4/pkg/kubernetes/helm.go (about)

     1  package kubernetes
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/http"
     7  	"strings"
     8  
     9  	piperhttp "github.com/SAP/jenkins-library/pkg/http"
    10  	"github.com/SAP/jenkins-library/pkg/log"
    11  )
    12  
    13  // HelmExecutor is used for mock
    14  type HelmExecutor interface {
    15  	RunHelmUpgrade() error
    16  	RunHelmLint() error
    17  	RunHelmInstall() error
    18  	RunHelmUninstall() error
    19  	RunHelmTest() error
    20  	RunHelmPublish() error
    21  	RunHelmDependency() error
    22  }
    23  
    24  // HelmExecute struct
    25  type HelmExecute struct {
    26  	utils   DeployUtils
    27  	config  HelmExecuteOptions
    28  	verbose bool
    29  	stdout  io.Writer
    30  }
    31  
    32  // HelmExecuteOptions struct holds common parameters for functions RunHelm...
    33  type HelmExecuteOptions struct {
    34  	AdditionalParameters      []string `json:"additionalParameters,omitempty"`
    35  	ChartPath                 string   `json:"chartPath,omitempty"`
    36  	DeploymentName            string   `json:"deploymentName,omitempty"`
    37  	ForceUpdates              bool     `json:"forceUpdates,omitempty"`
    38  	HelmDeployWaitSeconds     int      `json:"helmDeployWaitSeconds,omitempty"`
    39  	HelmValues                []string `json:"helmValues,omitempty"`
    40  	Image                     string   `json:"image,omitempty"`
    41  	KeepFailedDeployments     bool     `json:"keepFailedDeployments,omitempty"`
    42  	KubeConfig                string   `json:"kubeConfig,omitempty"`
    43  	KubeContext               string   `json:"kubeContext,omitempty"`
    44  	Namespace                 string   `json:"namespace,omitempty"`
    45  	DockerConfigJSON          string   `json:"dockerConfigJSON,omitempty"`
    46  	Version                   string   `json:"version,omitempty"`
    47  	AppVersion                string   `json:"appVersion,omitempty"`
    48  	PublishVersion            string   `json:"publishVersion,omitempty"`
    49  	Dependency                string   `json:"dependency,omitempty" validate:"possible-values=build list update"`
    50  	PackageDependencyUpdate   bool     `json:"packageDependencyUpdate,omitempty"`
    51  	DumpLogs                  bool     `json:"dumpLogs,omitempty"`
    52  	FilterTest                string   `json:"filterTest,omitempty"`
    53  	TargetRepositoryURL       string   `json:"targetRepositoryURL,omitempty"`
    54  	TargetRepositoryName      string   `json:"targetRepositoryName,omitempty"`
    55  	TargetRepositoryUser      string   `json:"targetRepositoryUser,omitempty"`
    56  	TargetRepositoryPassword  string   `json:"targetRepositoryPassword,omitempty"`
    57  	HelmCommand               string   `json:"helmCommand,omitempty"`
    58  	CustomTLSCertificateLinks []string `json:"customTlsCertificateLinks,omitempty"`
    59  }
    60  
    61  // NewHelmExecutor creates HelmExecute instance
    62  func NewHelmExecutor(config HelmExecuteOptions, utils DeployUtils, verbose bool, stdout io.Writer) HelmExecutor {
    63  	return &HelmExecute{
    64  		config:  config,
    65  		utils:   utils,
    66  		verbose: verbose,
    67  		stdout:  stdout,
    68  	}
    69  }
    70  
    71  // runHelmInit is used to set up env for executing helm command
    72  func (h *HelmExecute) runHelmInit() error {
    73  	helmLogFields := map[string]interface{}{}
    74  	helmLogFields["Chart Path"] = h.config.ChartPath
    75  	helmLogFields["Namespace"] = h.config.Namespace
    76  	helmLogFields["Deployment Name"] = h.config.DeploymentName
    77  	helmLogFields["Context"] = h.config.KubeContext
    78  	helmLogFields["Kubeconfig"] = h.config.KubeConfig
    79  	log.Entry().WithFields(helmLogFields).Debug("Calling Helm")
    80  
    81  	helmEnv := []string{fmt.Sprintf("KUBECONFIG=%v", h.config.KubeConfig)}
    82  
    83  	log.Entry().Debugf("Helm SetEnv: %v", helmEnv)
    84  	h.utils.SetEnv(helmEnv)
    85  	h.utils.Stdout(h.stdout)
    86  
    87  	return nil
    88  }
    89  
    90  // runHelmAdd is used to add a chart repository
    91  func (h *HelmExecute) runHelmAdd() error {
    92  	helmParams := []string{
    93  		"repo",
    94  		"add",
    95  	}
    96  	if len(h.config.TargetRepositoryName) == 0 {
    97  		return fmt.Errorf("there is no TargetRepositoryName value. 'helm repo add' command requires 2 arguments")
    98  	}
    99  	if len(h.config.TargetRepositoryUser) != 0 {
   100  		helmParams = append(helmParams, "--username", h.config.TargetRepositoryUser)
   101  	}
   102  	if len(h.config.TargetRepositoryPassword) != 0 {
   103  		helmParams = append(helmParams, "--password", h.config.TargetRepositoryPassword)
   104  	}
   105  	helmParams = append(helmParams, h.config.TargetRepositoryName)
   106  	helmParams = append(helmParams, h.config.TargetRepositoryURL)
   107  	if h.verbose {
   108  		helmParams = append(helmParams, "--debug")
   109  	}
   110  
   111  	if err := h.runHelmCommand(helmParams); err != nil {
   112  		log.Entry().WithError(err).Fatal("Helm add call failed")
   113  	}
   114  
   115  	return nil
   116  }
   117  
   118  // RunHelmUpgrade is used to upgrade a release
   119  func (h *HelmExecute) RunHelmUpgrade() error {
   120  	if len(h.config.ChartPath) == 0 {
   121  		return fmt.Errorf("there is no ChartPath value. The chartPath value is mandatory")
   122  	}
   123  
   124  	err := h.runHelmInit()
   125  	if err != nil {
   126  		return fmt.Errorf("failed to execute deployments: %v", err)
   127  	}
   128  
   129  	if err := h.runHelmAdd(); err != nil {
   130  		return fmt.Errorf("failed to execute deployments: %v", err)
   131  	}
   132  
   133  	helmParams := []string{
   134  		"upgrade",
   135  		h.config.DeploymentName,
   136  		h.config.ChartPath,
   137  	}
   138  
   139  	if h.verbose {
   140  		helmParams = append(helmParams, "--debug")
   141  	}
   142  
   143  	for _, v := range h.config.HelmValues {
   144  		helmParams = append(helmParams, "--values", v)
   145  	}
   146  
   147  	helmParams = append(
   148  		helmParams,
   149  		"--install",
   150  		"--namespace", h.config.Namespace,
   151  	)
   152  
   153  	if h.config.ForceUpdates {
   154  		helmParams = append(helmParams, "--force")
   155  	}
   156  
   157  	helmParams = append(helmParams, "--wait", "--timeout", fmt.Sprintf("%vs", h.config.HelmDeployWaitSeconds))
   158  
   159  	if !h.config.KeepFailedDeployments {
   160  		helmParams = append(helmParams, "--atomic")
   161  	}
   162  
   163  	if len(h.config.AdditionalParameters) > 0 {
   164  		helmParams = append(helmParams, h.config.AdditionalParameters...)
   165  	}
   166  
   167  	if err := h.runHelmCommand(helmParams); err != nil {
   168  		log.Entry().WithError(err).Fatal("Helm upgrade call failed")
   169  	}
   170  
   171  	return nil
   172  }
   173  
   174  // RunHelmLint is used to examine a chart for possible issues
   175  func (h *HelmExecute) RunHelmLint() error {
   176  	err := h.runHelmInit()
   177  	if err != nil {
   178  		return fmt.Errorf("failed to execute deployments: %v", err)
   179  	}
   180  
   181  	helmParams := []string{
   182  		"lint",
   183  		h.config.ChartPath,
   184  	}
   185  
   186  	if h.verbose {
   187  		helmParams = append(helmParams, "--debug")
   188  	}
   189  
   190  	h.utils.Stdout(h.stdout)
   191  	log.Entry().Info("Calling helm lint ...")
   192  	log.Entry().Debugf("Helm parameters: %v", helmParams)
   193  	if err := h.utils.RunExecutable("helm", helmParams...); err != nil {
   194  		log.Entry().WithError(err).Fatal("Helm lint call failed")
   195  	}
   196  
   197  	return nil
   198  }
   199  
   200  // RunHelmInstall is used to install a chart
   201  func (h *HelmExecute) RunHelmInstall() error {
   202  	if len(h.config.ChartPath) == 0 {
   203  		return fmt.Errorf("there is no ChartPath value. The chartPath value is mandatory")
   204  	}
   205  
   206  	if err := h.runHelmInit(); err != nil {
   207  		return fmt.Errorf("failed to execute deployments: %v", err)
   208  	}
   209  
   210  	if err := h.runHelmAdd(); err != nil {
   211  		return fmt.Errorf("failed to execute deployments: %v", err)
   212  	}
   213  
   214  	helmParams := []string{
   215  		"install",
   216  		h.config.DeploymentName,
   217  		h.config.ChartPath,
   218  	}
   219  	helmParams = append(helmParams, "--namespace", h.config.Namespace)
   220  	helmParams = append(helmParams, "--create-namespace")
   221  	if !h.config.KeepFailedDeployments {
   222  		helmParams = append(helmParams, "--atomic")
   223  	}
   224  	helmParams = append(helmParams, "--wait", "--timeout", fmt.Sprintf("%vs", h.config.HelmDeployWaitSeconds))
   225  	for _, v := range h.config.HelmValues {
   226  		helmParams = append(helmParams, "--values", v)
   227  	}
   228  	if len(h.config.AdditionalParameters) > 0 {
   229  		helmParams = append(helmParams, h.config.AdditionalParameters...)
   230  	}
   231  	if h.verbose {
   232  		helmParams = append(helmParams, "--debug")
   233  	}
   234  
   235  	if h.verbose {
   236  		helmParamsDryRun := helmParams
   237  		helmParamsDryRun = append(helmParamsDryRun, "--dry-run")
   238  		if err := h.runHelmCommand(helmParamsDryRun); err != nil {
   239  			log.Entry().WithError(err).Error("Helm install --dry-run call failed")
   240  		}
   241  	}
   242  
   243  	if err := h.runHelmCommand(helmParams); err != nil {
   244  		log.Entry().WithError(err).Fatal("Helm install call failed")
   245  	}
   246  
   247  	return nil
   248  }
   249  
   250  // RunHelmUninstall is used to uninstall a chart
   251  func (h *HelmExecute) RunHelmUninstall() error {
   252  	err := h.runHelmInit()
   253  	if err != nil {
   254  		return fmt.Errorf("failed to execute deployments: %v", err)
   255  	}
   256  
   257  	if err := h.runHelmAdd(); err != nil {
   258  		return fmt.Errorf("failed to execute deployments: %v", err)
   259  	}
   260  
   261  	helmParams := []string{
   262  		"uninstall",
   263  		h.config.DeploymentName,
   264  	}
   265  	if len(h.config.Namespace) <= 0 {
   266  		return fmt.Errorf("namespace has not been set, please configure namespace parameter")
   267  	}
   268  	helmParams = append(helmParams, "--namespace", h.config.Namespace)
   269  	if h.config.HelmDeployWaitSeconds > 0 {
   270  		helmParams = append(helmParams, "--wait", "--timeout", fmt.Sprintf("%vs", h.config.HelmDeployWaitSeconds))
   271  	}
   272  	if h.verbose {
   273  		helmParams = append(helmParams, "--debug")
   274  	}
   275  
   276  	if h.verbose {
   277  		helmParamsDryRun := helmParams
   278  		helmParamsDryRun = append(helmParamsDryRun, "--dry-run")
   279  		if err := h.runHelmCommand(helmParamsDryRun); err != nil {
   280  			log.Entry().WithError(err).Error("Helm uninstall --dry-run call failed")
   281  		}
   282  	}
   283  
   284  	if err := h.runHelmCommand(helmParams); err != nil {
   285  		log.Entry().WithError(err).Fatal("Helm uninstall call failed")
   286  	}
   287  
   288  	return nil
   289  }
   290  
   291  // RunHelmPackage is used to package a chart directory into a chart archive
   292  func (h *HelmExecute) runHelmPackage() error {
   293  	if len(h.config.ChartPath) == 0 {
   294  		return fmt.Errorf("there is no ChartPath value. The chartPath value is mandatory")
   295  	}
   296  
   297  	err := h.runHelmInit()
   298  	if err != nil {
   299  		return fmt.Errorf("failed to execute deployments: %v", err)
   300  	}
   301  
   302  	helmParams := []string{
   303  		"package",
   304  		h.config.ChartPath,
   305  	}
   306  	if len(h.config.Version) > 0 {
   307  		helmParams = append(helmParams, "--version", h.config.Version)
   308  	}
   309  	if h.config.PackageDependencyUpdate {
   310  		helmParams = append(helmParams, "--dependency-update")
   311  	}
   312  	if len(h.config.AppVersion) > 0 {
   313  		helmParams = append(helmParams, "--app-version", h.config.AppVersion)
   314  	}
   315  	if h.verbose {
   316  		helmParams = append(helmParams, "--debug")
   317  	}
   318  
   319  	if err := h.runHelmCommand(helmParams); err != nil {
   320  		log.Entry().WithError(err).Fatal("Helm package call failed")
   321  	}
   322  
   323  	return nil
   324  }
   325  
   326  // RunHelmTest is used to run tests for a release
   327  func (h *HelmExecute) RunHelmTest() error {
   328  	err := h.runHelmInit()
   329  	if err != nil {
   330  		return fmt.Errorf("failed to execute deployments: %v", err)
   331  	}
   332  
   333  	helmParams := []string{
   334  		"test",
   335  		h.config.ChartPath,
   336  	}
   337  	if len(h.config.FilterTest) > 0 {
   338  		helmParams = append(helmParams, "--filter", h.config.FilterTest)
   339  	}
   340  	if h.config.DumpLogs {
   341  		helmParams = append(helmParams, "--logs")
   342  	}
   343  	if h.verbose {
   344  		helmParams = append(helmParams, "--debug")
   345  	}
   346  
   347  	if err := h.runHelmCommand(helmParams); err != nil {
   348  		log.Entry().WithError(err).Fatal("Helm test call failed")
   349  	}
   350  
   351  	return nil
   352  }
   353  
   354  // RunHelmDependency is used to manage a chart's dependencies
   355  func (h *HelmExecute) RunHelmDependency() error {
   356  	if len(h.config.Dependency) == 0 {
   357  		return fmt.Errorf("there is no dependency value. Possible values are build, list, update")
   358  	}
   359  
   360  	helmParams := []string{
   361  		"dependency",
   362  	}
   363  
   364  	helmParams = append(helmParams, h.config.Dependency)
   365  
   366  	helmParams = append(helmParams, h.config.ChartPath)
   367  
   368  	if len(h.config.AdditionalParameters) > 0 {
   369  		helmParams = append(helmParams, h.config.AdditionalParameters...)
   370  	}
   371  
   372  	if err := h.runHelmCommand(helmParams); err != nil {
   373  		log.Entry().WithError(err).Fatal("Helm dependency call failed")
   374  	}
   375  
   376  	return nil
   377  }
   378  
   379  //RunHelmPublish is used to upload a chart to a registry
   380  func (h *HelmExecute) RunHelmPublish() error {
   381  	err := h.runHelmInit()
   382  	if err != nil {
   383  		return fmt.Errorf("failed to execute deployments: %v", err)
   384  	}
   385  
   386  	err = h.runHelmPackage()
   387  	if err != nil {
   388  		return fmt.Errorf("failed to execute deployments: %v", err)
   389  	}
   390  
   391  	if len(h.config.TargetRepositoryURL) == 0 {
   392  		return fmt.Errorf("there's no target repository for helm chart publishing configured")
   393  	}
   394  
   395  	repoClientOptions := piperhttp.ClientOptions{
   396  		Username:     h.config.TargetRepositoryUser,
   397  		Password:     h.config.TargetRepositoryPassword,
   398  		TrustedCerts: h.config.CustomTLSCertificateLinks,
   399  	}
   400  
   401  	h.utils.SetOptions(repoClientOptions)
   402  
   403  	binary := fmt.Sprintf("%v", h.config.DeploymentName+"-"+h.config.PublishVersion+".tgz")
   404  
   405  	targetPath := fmt.Sprintf("%v/%s", h.config.DeploymentName, binary)
   406  
   407  	separator := "/"
   408  
   409  	if strings.HasSuffix(h.config.TargetRepositoryURL, "/") {
   410  		separator = ""
   411  	}
   412  
   413  	targetURL := fmt.Sprintf("%s%s%s", h.config.TargetRepositoryURL, separator, targetPath)
   414  
   415  	log.Entry().Infof("publishing artifact: %s", targetURL)
   416  
   417  	response, err := h.utils.UploadRequest(http.MethodPut, targetURL, binary, "", nil, nil, "binary")
   418  	if err != nil {
   419  		return fmt.Errorf("couldn't upload artifact: %w", err)
   420  	}
   421  
   422  	if !(response.StatusCode == 200 || response.StatusCode == 201) {
   423  		return fmt.Errorf("couldn't upload artifact, received status code %d", response.StatusCode)
   424  	}
   425  
   426  	return nil
   427  }
   428  
   429  func (h *HelmExecute) runHelmCommand(helmParams []string) error {
   430  
   431  	h.utils.Stdout(h.stdout)
   432  	log.Entry().Infof("Calling helm %v ...", h.config.HelmCommand)
   433  	log.Entry().Debugf("Helm parameters: %v", helmParams)
   434  	if err := h.utils.RunExecutable("helm", helmParams...); err != nil {
   435  		log.Entry().WithError(err).Fatalf("Helm %v call failed", h.config.HelmCommand)
   436  		return err
   437  	}
   438  
   439  	return nil
   440  }