github.com/argoproj/argo-cd/v2@v2.10.9/util/helm/cmd.go (about) 1 package helm 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "os/exec" 8 "path" 9 "path/filepath" 10 "regexp" 11 "strings" 12 13 log "github.com/sirupsen/logrus" 14 15 "github.com/argoproj/argo-cd/v2/common" 16 executil "github.com/argoproj/argo-cd/v2/util/exec" 17 argoio "github.com/argoproj/argo-cd/v2/util/io" 18 pathutil "github.com/argoproj/argo-cd/v2/util/io/path" 19 "github.com/argoproj/argo-cd/v2/util/proxy" 20 ) 21 22 // A thin wrapper around the "helm" command, adding logging and error translation. 23 type Cmd struct { 24 HelmVer 25 helmHome string 26 WorkDir string 27 IsLocal bool 28 IsHelmOci bool 29 proxy string 30 } 31 32 func NewCmd(workDir string, version string, proxy string) (*Cmd, error) { 33 34 switch version { 35 // If v3 is specified (or by default, if no value is specified) then use v3 36 case "", "v3": 37 return NewCmdWithVersion(workDir, HelmV3, false, proxy) 38 } 39 return nil, fmt.Errorf("helm chart version '%s' is not supported", version) 40 } 41 42 func NewCmdWithVersion(workDir string, version HelmVer, isHelmOci bool, proxy string) (*Cmd, error) { 43 tmpDir, err := os.MkdirTemp("", "helm") 44 if err != nil { 45 return nil, err 46 } 47 return &Cmd{WorkDir: workDir, helmHome: tmpDir, HelmVer: version, IsHelmOci: isHelmOci, proxy: proxy}, err 48 } 49 50 var redactor = func(text string) string { 51 return regexp.MustCompile("(--username|--password) [^ ]*").ReplaceAllString(text, "$1 ******") 52 } 53 54 func (c Cmd) run(args ...string) (string, error) { 55 cmd := exec.Command(c.binaryName, args...) 56 cmd.Dir = c.WorkDir 57 cmd.Env = os.Environ() 58 if !c.IsLocal { 59 cmd.Env = append(cmd.Env, 60 fmt.Sprintf("XDG_CACHE_HOME=%s/cache", c.helmHome), 61 fmt.Sprintf("XDG_CONFIG_HOME=%s/config", c.helmHome), 62 fmt.Sprintf("XDG_DATA_HOME=%s/data", c.helmHome), 63 fmt.Sprintf("HELM_CONFIG_HOME=%s/config", c.helmHome)) 64 } 65 66 if c.IsHelmOci { 67 cmd.Env = append(cmd.Env, "HELM_EXPERIMENTAL_OCI=1") 68 } 69 70 cmd.Env = proxy.UpsertEnv(cmd, c.proxy) 71 72 return executil.RunWithRedactor(cmd, redactor) 73 } 74 75 func (c *Cmd) Init() (string, error) { 76 if c.initSupported { 77 return c.run("init", "--client-only", "--skip-refresh") 78 } 79 return "", nil 80 } 81 82 func (c *Cmd) RegistryLogin(repo string, creds Creds) (string, error) { 83 args := []string{"registry", "login"} 84 args = append(args, repo) 85 86 if creds.Username != "" { 87 args = append(args, "--username", creds.Username) 88 } 89 90 if creds.Password != "" { 91 args = append(args, "--password", creds.Password) 92 } 93 94 if creds.CAPath != "" { 95 args = append(args, "--ca-file", creds.CAPath) 96 } 97 98 if len(creds.CertData) > 0 { 99 filePath, closer, err := writeToTmp(creds.CertData) 100 if err != nil { 101 return "", err 102 } 103 defer argoio.Close(closer) 104 args = append(args, "--cert-file", filePath) 105 } 106 107 if len(creds.KeyData) > 0 { 108 filePath, closer, err := writeToTmp(creds.KeyData) 109 if err != nil { 110 return "", err 111 } 112 defer argoio.Close(closer) 113 args = append(args, "--key-file", filePath) 114 } 115 116 if creds.InsecureSkipVerify { 117 args = append(args, "--insecure") 118 } 119 return c.run(args...) 120 } 121 122 func (c *Cmd) RegistryLogout(repo string, creds Creds) (string, error) { 123 args := []string{"registry", "logout"} 124 args = append(args, repo) 125 126 return c.run(args...) 127 } 128 129 func (c *Cmd) RepoAdd(name string, url string, opts Creds, passCredentials bool) (string, error) { 130 tmp, err := os.MkdirTemp("", "helm") 131 if err != nil { 132 return "", err 133 } 134 defer func() { _ = os.RemoveAll(tmp) }() 135 136 args := []string{"repo", "add"} 137 138 if opts.Username != "" { 139 args = append(args, "--username", opts.Username) 140 } 141 142 if opts.Password != "" { 143 args = append(args, "--password", opts.Password) 144 } 145 146 if opts.CAPath != "" { 147 args = append(args, "--ca-file", opts.CAPath) 148 } 149 150 if opts.InsecureSkipVerify && c.insecureSkipVerifySupported { 151 args = append(args, "--insecure-skip-tls-verify") 152 } 153 154 if len(opts.CertData) > 0 { 155 certFile, err := os.CreateTemp("", "helm") 156 if err != nil { 157 return "", err 158 } 159 _, err = certFile.Write(opts.CertData) 160 if err != nil { 161 return "", err 162 } 163 defer certFile.Close() 164 args = append(args, "--cert-file", certFile.Name()) 165 } 166 167 if len(opts.KeyData) > 0 { 168 keyFile, err := os.CreateTemp("", "helm") 169 if err != nil { 170 return "", err 171 } 172 _, err = keyFile.Write(opts.KeyData) 173 if err != nil { 174 return "", err 175 } 176 defer keyFile.Close() 177 args = append(args, "--key-file", keyFile.Name()) 178 } 179 180 if c.helmPassCredentialsSupported && passCredentials { 181 args = append(args, "--pass-credentials") 182 } 183 184 args = append(args, name, url) 185 186 return c.run(args...) 187 } 188 189 func writeToTmp(data []byte) (string, argoio.Closer, error) { 190 file, err := os.CreateTemp("", "") 191 if err != nil { 192 return "", nil, err 193 } 194 err = os.WriteFile(file.Name(), data, 0644) 195 if err != nil { 196 _ = os.RemoveAll(file.Name()) 197 return "", nil, err 198 } 199 defer func() { 200 if err = file.Close(); err != nil { 201 log.WithFields(log.Fields{ 202 common.SecurityField: common.SecurityMedium, 203 common.SecurityCWEField: common.SecurityCWEMissingReleaseOfFileDescriptor, 204 }).Errorf("error closing file %q: %v", file.Name(), err) 205 } 206 }() 207 return file.Name(), argoio.NewCloser(func() error { 208 return os.RemoveAll(file.Name()) 209 }), nil 210 } 211 212 func (c *Cmd) Fetch(repo, chartName, version, destination string, creds Creds, passCredentials bool) (string, error) { 213 args := []string{c.pullCommand, "--destination", destination} 214 if version != "" { 215 args = append(args, "--version", version) 216 } 217 if creds.Username != "" { 218 args = append(args, "--username", creds.Username) 219 } 220 if creds.Password != "" { 221 args = append(args, "--password", creds.Password) 222 } 223 if creds.InsecureSkipVerify && c.insecureSkipVerifySupported { 224 args = append(args, "--insecure-skip-tls-verify") 225 } 226 227 args = append(args, "--repo", repo, chartName) 228 229 if creds.CAPath != "" { 230 args = append(args, "--ca-file", creds.CAPath) 231 } 232 if len(creds.CertData) > 0 { 233 filePath, closer, err := writeToTmp(creds.CertData) 234 if err != nil { 235 return "", err 236 } 237 defer argoio.Close(closer) 238 args = append(args, "--cert-file", filePath) 239 } 240 if len(creds.KeyData) > 0 { 241 filePath, closer, err := writeToTmp(creds.KeyData) 242 if err != nil { 243 return "", err 244 } 245 defer argoio.Close(closer) 246 args = append(args, "--key-file", filePath) 247 } 248 if passCredentials && c.helmPassCredentialsSupported { 249 args = append(args, "--pass-credentials") 250 } 251 252 return c.run(args...) 253 } 254 255 func (c *Cmd) PullOCI(repo string, chart string, version string, destination string, creds Creds) (string, error) { 256 args := []string{"pull", fmt.Sprintf("oci://%s/%s", repo, chart), "--version", 257 version, 258 "--destination", 259 destination} 260 if creds.CAPath != "" { 261 args = append(args, "--ca-file", creds.CAPath) 262 } 263 264 if len(creds.CertData) > 0 { 265 filePath, closer, err := writeToTmp(creds.CertData) 266 if err != nil { 267 return "", err 268 } 269 defer argoio.Close(closer) 270 args = append(args, "--cert-file", filePath) 271 } 272 273 if len(creds.KeyData) > 0 { 274 filePath, closer, err := writeToTmp(creds.KeyData) 275 if err != nil { 276 return "", err 277 } 278 defer argoio.Close(closer) 279 args = append(args, "--key-file", filePath) 280 } 281 282 if creds.InsecureSkipVerify && c.insecureSkipVerifySupported { 283 args = append(args, "--insecure-skip-tls-verify") 284 } 285 return c.run(args...) 286 } 287 288 func (c *Cmd) dependencyBuild() (string, error) { 289 return c.run("dependency", "build") 290 } 291 292 func (c *Cmd) inspectValues(values string) (string, error) { 293 return c.run(c.showCommand, "values", values) 294 } 295 296 func (c *Cmd) InspectChart() (string, error) { 297 return c.run(c.showCommand, "chart", ".") 298 } 299 300 type TemplateOpts struct { 301 Name string 302 Namespace string 303 KubeVersion string 304 APIVersions []string 305 Set map[string]string 306 SetString map[string]string 307 SetFile map[string]pathutil.ResolvedFilePath 308 Values []pathutil.ResolvedFilePath 309 SkipCrds bool 310 } 311 312 var ( 313 re = regexp.MustCompile(`([^\\]),`) 314 apiVersionsRemover = regexp.MustCompile(`(--api-versions [^ ]+ )+`) 315 ) 316 317 func cleanSetParameters(val string) string { 318 // `{}` equal helm list parameters format, so don't escape `,`. 319 if strings.HasPrefix(val, `{`) && strings.HasSuffix(val, `}`) { 320 return val 321 } 322 return re.ReplaceAllString(val, `$1\,`) 323 } 324 325 func (c *Cmd) template(chartPath string, opts *TemplateOpts) (string, error) { 326 if c.HelmVer.getPostTemplateCallback != nil { 327 if callback, err := c.HelmVer.getPostTemplateCallback(filepath.Clean(path.Join(c.WorkDir, chartPath))); err == nil { 328 defer callback() 329 } else { 330 return "", err 331 } 332 } 333 334 args := []string{"template", chartPath, c.templateNameArg, opts.Name} 335 336 if opts.Namespace != "" { 337 args = append(args, "--namespace", opts.Namespace) 338 } 339 if opts.KubeVersion != "" && c.kubeVersionSupported { 340 args = append(args, "--kube-version", opts.KubeVersion) 341 } 342 for key, val := range opts.Set { 343 args = append(args, "--set", key+"="+cleanSetParameters(val)) 344 } 345 for key, val := range opts.SetString { 346 args = append(args, "--set-string", key+"="+cleanSetParameters(val)) 347 } 348 for key, val := range opts.SetFile { 349 args = append(args, "--set-file", key+"="+cleanSetParameters(string(val))) 350 } 351 for _, val := range opts.Values { 352 args = append(args, "--values", string(val)) 353 } 354 for _, v := range opts.APIVersions { 355 args = append(args, "--api-versions", v) 356 } 357 if c.HelmVer.includeCrds && !opts.SkipCrds { 358 args = append(args, "--include-crds") 359 } 360 361 out, err := c.run(args...) 362 if err != nil { 363 msg := err.Error() 364 if strings.Contains(msg, "--api-versions") { 365 log.Debug(msg) 366 msg = apiVersionsRemover.ReplaceAllString(msg, "<api versions removed> ") 367 } 368 return "", errors.New(msg) 369 } 370 return out, nil 371 } 372 373 func (c *Cmd) Freestyle(args ...string) (string, error) { 374 return c.run(args...) 375 } 376 377 func (c *Cmd) Close() { 378 _ = os.RemoveAll(c.helmHome) 379 }