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  }