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 }