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 }