custom-targets/helm/helm-deployer/cmd.go (84 lines of code) (raw):
// Copyright 2023 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// https://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"regexp"
)
const (
helmBin = "helm"
gcloudBin = "gcloud"
)
// helmOptions configures the args provided to `helm`.
type helmOptions struct {
namespace string
}
// helmTemplateOptions configures the args provided to `helm template`.
type helmTemplateOptions struct {
helmOptions
lookup bool
validate bool
}
// helmTemplate runs `helm template` for the provided release name and chart path with the
// provided options. The output from this command is not written to stdout. Returns the
// manifest in YAML format.
func helmTemplate(releaseName, chartPath string, opts *helmTemplateOptions) ([]byte, error) {
args := []string{"template", releaseName, chartPath, "--include-crds"}
if opts.lookup {
args = append(args, "--dry-run=server")
}
if opts.validate {
args = append(args, "--validate")
}
if len(opts.helmOptions.namespace) > 0 {
args = append(args, fmt.Sprintf("--namespace=%s", opts.helmOptions.namespace))
}
return runCmd(helmBin, args, true)
}
// helmUpgradeOptions configures the args provided to `helm upgrade`.
type helmUpgradeOptions struct {
helmOptions
timeout string
}
// helmUpgrade runs `helm upgrade` for the provided release and chart path with the
// provided options.
func helmUpgrade(releaseName, chartPath string, opts *helmUpgradeOptions) ([]byte, error) {
args := []string{"upgrade", releaseName, chartPath, "--install", "--wait", "--wait-for-jobs"}
if len(opts.timeout) != 0 {
args = append(args, fmt.Sprintf("--timeout=%s", opts.timeout))
}
if len(opts.helmOptions.namespace) > 0 {
args = append(args, fmt.Sprintf("--namespace=%s", opts.helmOptions.namespace))
}
return runCmd(helmBin, args, false)
}
// helmGetManifest runs `helm get manifest` for the provided release name. The output
// from this command is not written to stdout.
func helmGetManifest(releaseName string, opts *helmOptions) ([]byte, error) {
args := []string{"get", "manifest", releaseName}
if len(opts.namespace) > 0 {
args = append(args, fmt.Sprintf("--namespace=%s", opts.namespace))
}
return runCmd(helmBin, args, true)
}
// gkeClusterRegex represents the regex that a GKE cluster resource name needs to match.
var gkeClusterRegex = regexp.MustCompile("^projects/([^/]+)/locations/([^/]+)/clusters/([^/]+)$")
// gcloudClusterCredentials runs `gcloud container clusters get-credentials` to set up
// the cluster credentials.
func gcloudClusterCredentials(gkeCluster string) ([]byte, error) {
m := gkeClusterRegex.FindStringSubmatch(gkeCluster)
if len(m) == 0 {
return nil, fmt.Errorf("invalid GKE cluster name: %s", gkeCluster)
}
args := []string{"container", "clusters", "get-credentials", m[3], fmt.Sprintf("--region=%s", m[2]), fmt.Sprintf("--project=%s", m[1])}
return runCmd(gcloudBin, args, false)
}
// runCmd starts and waits for the provided command with args to complete. If the command
// succeeds it returns the stdout of the command.
func runCmd(binPath string, args []string, closeOSStdout bool) ([]byte, error) {
fmt.Printf("Running the following command: %s %s\n", binPath, args)
cmd := exec.Command(binPath, args...)
var stderr bytes.Buffer
errWriter := io.MultiWriter(&stderr, os.Stderr)
cmd.Stderr = errWriter
var stdout bytes.Buffer
if closeOSStdout {
cmd.Stdout = &stdout
} else {
cmd.Stdout = io.MultiWriter(&stdout, os.Stdout)
}
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("failed to start command: %v", err)
}
if err := cmd.Wait(); err != nil {
return nil, fmt.Errorf("error running command: %v\n%s", err, stderr.Bytes())
}
return stdout.Bytes(), nil
}