github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/deploy/helm/util.go (about)

     1  /*
     2  Copyright 2020 The Skaffold Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package helm
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"io"
    24  	"io/ioutil"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  	"sort"
    29  	"strings"
    30  
    31  	"github.com/blang/semver"
    32  
    33  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
    34  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
    35  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
    36  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    37  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    38  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
    39  )
    40  
    41  func IsHelmChart(path string) bool {
    42  	return filepath.Base(path) == "Chart.yaml"
    43  }
    44  
    45  // copy of cmd/skaffold/app/flags.BuildOutputs
    46  type buildOutputs struct {
    47  	Builds []graph.Artifact `json:"builds"`
    48  }
    49  
    50  func writeBuildArtifacts(builds []graph.Artifact) (string, func(), error) {
    51  	buildOutput, err := json.Marshal(buildOutputs{builds})
    52  	if err != nil {
    53  		return "", nil, fmt.Errorf("cannot marshal build artifacts: %w", err)
    54  	}
    55  
    56  	f, err := ioutil.TempFile("", "builds*.yaml")
    57  	if err != nil {
    58  		return "", nil, fmt.Errorf("cannot create temp file: %w", err)
    59  	}
    60  	if _, err := f.Write(buildOutput); err != nil {
    61  		return "", nil, fmt.Errorf("cannot write to temp file: %w", err)
    62  	}
    63  	if err := f.Close(); err != nil {
    64  		return "", nil, fmt.Errorf("cannot close temp file: %w", err)
    65  	}
    66  	return f.Name(), func() { os.Remove(f.Name()) }, nil
    67  }
    68  
    69  // sortKeys returns the map keys in sorted order
    70  func sortKeys(m map[string]string) []string {
    71  	s := make([]string, 0, len(m))
    72  	for k := range m {
    73  		s = append(s, k)
    74  	}
    75  	sort.Strings(s)
    76  	return s
    77  }
    78  
    79  // binVer returns the version of the helm binary found in PATH.
    80  func binVer(ctx context.Context) (semver.Version, error) {
    81  	cmd := exec.Command("helm", "version", "--client")
    82  	b, err := util.RunCmdOut(ctx, cmd)
    83  	if err != nil {
    84  		return semver.Version{}, fmt.Errorf("helm version command failed %q: %w", string(b), err)
    85  	}
    86  	raw := string(b)
    87  	matches := versionRegex.FindStringSubmatch(raw)
    88  	if len(matches) == 0 {
    89  		return semver.Version{}, fmt.Errorf("unable to parse output: %q", raw)
    90  	}
    91  	return semver.ParseTolerant(matches[1])
    92  }
    93  
    94  // imageSetFromConfig calculates the --set-string value from the helm config
    95  func imageSetFromConfig(cfg *latest.HelmConventionConfig, valueName string, tag string) (string, error) {
    96  	if cfg == nil {
    97  		return fmt.Sprintf("%s=%s", valueName, tag), nil
    98  	}
    99  
   100  	ref, err := docker.ParseReference(tag)
   101  	if err != nil {
   102  		return "", fmt.Errorf("cannot parse the image reference %q: %w", tag, err)
   103  	}
   104  
   105  	var imageTag string
   106  	if ref.Digest != "" {
   107  		imageTag = fmt.Sprintf("%s@%s", ref.Tag, ref.Digest)
   108  	} else {
   109  		imageTag = ref.Tag
   110  	}
   111  
   112  	if cfg.ExplicitRegistry {
   113  		if ref.Domain == "" {
   114  			return "", fmt.Errorf("image reference %s has no domain", tag)
   115  		}
   116  		return fmt.Sprintf("%[1]s.registry=%[2]s,%[1]s.repository=%[3]s,%[1]s.tag=%[4]s", valueName, ref.Domain, ref.Path, imageTag), nil
   117  	}
   118  
   119  	return fmt.Sprintf("%[1]s.repository=%[2]s,%[1]s.tag=%[3]s", valueName, ref.BaseName, imageTag), nil
   120  }
   121  
   122  // pairParamsToArtifacts associates parameters to the build artifact it creates
   123  func pairParamsToArtifacts(builds []graph.Artifact, params map[string]string) (map[string]graph.Artifact, error) {
   124  	imageToBuildResult := map[string]graph.Artifact{}
   125  	for _, b := range builds {
   126  		imageToBuildResult[b.ImageName] = b
   127  	}
   128  
   129  	paramToBuildResult := map[string]graph.Artifact{}
   130  
   131  	for param, imageName := range params {
   132  		b, ok := imageToBuildResult[imageName]
   133  		if !ok {
   134  			return nil, noMatchingBuild(imageName)
   135  		}
   136  
   137  		paramToBuildResult[param] = b
   138  	}
   139  
   140  	return paramToBuildResult, nil
   141  }
   142  
   143  func (h *Deployer) generateSkaffoldDebugFilter(buildsFile string) []string {
   144  	args := []string{"filter", "--debugging", "--kube-context", h.kubeContext}
   145  	if len(buildsFile) > 0 {
   146  		args = append(args, "--build-artifacts", buildsFile)
   147  	}
   148  	args = append(args, h.Flags.Global...)
   149  
   150  	if h.kubeConfig != "" {
   151  		args = append(args, "--kubeconfig", h.kubeConfig)
   152  	}
   153  	return args
   154  }
   155  
   156  func (h *Deployer) releaseNamespace(r latest.HelmRelease) (string, error) {
   157  	if h.namespace != "" {
   158  		return h.namespace, nil
   159  	} else if r.Namespace != "" {
   160  		namespace, err := util.ExpandEnvTemplateOrFail(r.Namespace, nil)
   161  		if err != nil {
   162  			return "", fmt.Errorf("cannot parse the release namespace template: %w", err)
   163  		}
   164  		return namespace, nil
   165  	}
   166  	return "", nil
   167  }
   168  
   169  // envVarForImage creates an environment map for an image and digest tag (fqn)
   170  func envVarForImage(imageName string, digest string) map[string]string {
   171  	customMap := map[string]string{
   172  		"IMAGE_NAME": imageName,
   173  		"DIGEST":     digest, // The `DIGEST` name is kept for compatibility reasons
   174  	}
   175  
   176  	// Standardize access to Image reference fields in templates
   177  	ref, err := docker.ParseReference(digest)
   178  	if err == nil {
   179  		customMap[constants.ImageRef.Repo] = ref.BaseName
   180  		customMap[constants.ImageRef.Tag] = ref.Tag
   181  		customMap[constants.ImageRef.Digest] = ref.Digest
   182  	} else {
   183  		log.Entry(context.TODO()).Warnf("unable to extract values for %v, %v and %v from image %v due to error:\n%v", constants.ImageRef.Repo, constants.ImageRef.Tag, constants.ImageRef.Digest, digest, err)
   184  	}
   185  
   186  	if digest == "" {
   187  		return customMap
   188  	}
   189  
   190  	// DIGEST_ALGO and DIGEST_HEX are deprecated and will contain nonsense values
   191  	names := strings.SplitN(digest, ":", 2)
   192  	if len(names) >= 2 {
   193  		customMap["DIGEST_ALGO"] = names[0]
   194  		customMap["DIGEST_HEX"] = names[1]
   195  	} else {
   196  		customMap["DIGEST_HEX"] = digest
   197  	}
   198  	return customMap
   199  }
   200  
   201  // generates the helm command to run according to the given configuration
   202  func (h *Deployer) generateHelmCommand(ctx context.Context, useSecrets bool, env []string, args ...string) *exec.Cmd {
   203  	args = append([]string{"--kube-context", h.kubeContext}, args...)
   204  	args = append(args, h.Flags.Global...)
   205  
   206  	if h.kubeConfig != "" {
   207  		args = append(args, "--kubeconfig", h.kubeConfig)
   208  	}
   209  
   210  	if useSecrets {
   211  		args = append([]string{"secrets"}, args...)
   212  	}
   213  
   214  	cmd := exec.CommandContext(ctx, "helm", args...)
   215  	if len(env) > 0 {
   216  		cmd.Env = env
   217  	}
   218  
   219  	return cmd
   220  }
   221  
   222  // executes the helm command, writing combined stdout/stderr to the provided writer
   223  func (h *Deployer) exec(ctx context.Context, out io.Writer, useSecrets bool, env []string, args ...string) error {
   224  	cmd := h.generateHelmCommand(ctx, useSecrets, env, args...)
   225  	cmd.Stdout = out
   226  	cmd.Stderr = out
   227  
   228  	return util.RunCmd(ctx, cmd)
   229  }
   230  
   231  // executes the helm command, writing stdout and stderr to the provided writers
   232  func (h *Deployer) execWithStdoutAndStderr(ctx context.Context, stdout io.Writer, stderr io.Writer, useSecrets bool, env []string, args ...string) error {
   233  	cmd := h.generateHelmCommand(ctx, useSecrets, env, args...)
   234  	cmd.Stdout = stdout
   235  	cmd.Stderr = stderr
   236  
   237  	return util.RunCmd(ctx, cmd)
   238  }