github.com/argoproj/argo-cd/v2@v2.10.9/util/helm/helm.go (about)

     1  package helm
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	log "github.com/sirupsen/logrus"
    12  	"sigs.k8s.io/yaml"
    13  
    14  	"github.com/argoproj/argo-cd/v2/util/config"
    15  	executil "github.com/argoproj/argo-cd/v2/util/exec"
    16  	pathutil "github.com/argoproj/argo-cd/v2/util/io/path"
    17  )
    18  
    19  const (
    20  	ResourcePolicyAnnotation = "helm.sh/resource-policy"
    21  	ResourcePolicyKeep       = "keep"
    22  )
    23  
    24  type HelmRepository struct {
    25  	Creds
    26  	Name      string
    27  	Repo      string
    28  	EnableOci bool
    29  }
    30  
    31  // Helm provides wrapper functionality around the `helm` command.
    32  type Helm interface {
    33  	// Template returns a list of unstructured objects from a `helm template` command
    34  	Template(opts *TemplateOpts) (string, error)
    35  	// GetParameters returns a list of chart parameters taking into account values in provided YAML files.
    36  	GetParameters(valuesFiles []pathutil.ResolvedFilePath, appPath, repoRoot string) (map[string]string, error)
    37  	// DependencyBuild runs `helm dependency build` to download a chart's dependencies
    38  	DependencyBuild() error
    39  	// Init runs `helm init --client-only`
    40  	Init() error
    41  	// Dispose deletes temp resources
    42  	Dispose()
    43  }
    44  
    45  // NewHelmApp create a new wrapper to run commands on the `helm` command-line tool.
    46  func NewHelmApp(workDir string, repos []HelmRepository, isLocal bool, version string, proxy string, passCredentials bool) (Helm, error) {
    47  	cmd, err := NewCmd(workDir, version, proxy)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	cmd.IsLocal = isLocal
    52  
    53  	return &helm{repos: repos, cmd: *cmd, passCredentials: passCredentials}, nil
    54  }
    55  
    56  type helm struct {
    57  	cmd             Cmd
    58  	repos           []HelmRepository
    59  	passCredentials bool
    60  }
    61  
    62  var _ Helm = &helm{}
    63  
    64  // IsMissingDependencyErr tests if the error is related to a missing chart dependency
    65  func IsMissingDependencyErr(err error) bool {
    66  	return strings.Contains(err.Error(), "found in requirements.yaml, but missing in charts") ||
    67  		strings.Contains(err.Error(), "found in Chart.yaml, but missing in charts/ directory")
    68  }
    69  
    70  func (h *helm) Template(templateOpts *TemplateOpts) (string, error) {
    71  	out, err := h.cmd.template(".", templateOpts)
    72  	if err != nil {
    73  		return "", err
    74  	}
    75  	return out, nil
    76  }
    77  
    78  func (h *helm) DependencyBuild() error {
    79  	isHelmOci := h.cmd.IsHelmOci
    80  	defer func() {
    81  		h.cmd.IsHelmOci = isHelmOci
    82  	}()
    83  
    84  	for i := range h.repos {
    85  		repo := h.repos[i]
    86  		if repo.EnableOci {
    87  			h.cmd.IsHelmOci = true
    88  			if repo.Creds.Username != "" && repo.Creds.Password != "" {
    89  				_, err := h.cmd.RegistryLogin(repo.Repo, repo.Creds)
    90  
    91  				defer func() {
    92  					_, _ = h.cmd.RegistryLogout(repo.Repo, repo.Creds)
    93  				}()
    94  
    95  				if err != nil {
    96  					return err
    97  				}
    98  			}
    99  		} else {
   100  			_, err := h.cmd.RepoAdd(repo.Name, repo.Repo, repo.Creds, h.passCredentials)
   101  
   102  			if err != nil {
   103  				return err
   104  			}
   105  		}
   106  	}
   107  	h.repos = nil
   108  	_, err := h.cmd.dependencyBuild()
   109  	return err
   110  }
   111  
   112  func (h *helm) Init() error {
   113  	_, err := h.cmd.Init()
   114  	return err
   115  }
   116  
   117  func (h *helm) Dispose() {
   118  	h.cmd.Close()
   119  }
   120  
   121  func Version(shortForm bool) (string, error) {
   122  	executable := "helm"
   123  	cmdArgs := []string{"version", "--client"}
   124  	if shortForm {
   125  		cmdArgs = append(cmdArgs, "--short")
   126  	}
   127  	cmd := exec.Command(executable, cmdArgs...)
   128  	// example version output:
   129  	// long: "version.BuildInfo{Version:\"v3.3.1\", GitCommit:\"249e5215cde0c3fa72e27eb7a30e8d55c9696144\", GitTreeState:\"clean\", GoVersion:\"go1.14.7\"}"
   130  	// short: "v3.3.1+g249e521"
   131  	version, err := executil.RunWithRedactor(cmd, redactor)
   132  	if err != nil {
   133  		return "", fmt.Errorf("could not get helm version: %s", err)
   134  	}
   135  	return strings.TrimSpace(version), nil
   136  }
   137  
   138  func (h *helm) GetParameters(valuesFiles []pathutil.ResolvedFilePath, appPath, repoRoot string) (map[string]string, error) {
   139  	var values []string
   140  	// Don't load values.yaml if it's an out-of-bounds link.
   141  	if _, _, err := pathutil.ResolveValueFilePathOrUrl(appPath, repoRoot, "values.yaml", []string{}); err == nil {
   142  		out, err := h.cmd.inspectValues(".")
   143  		if err != nil {
   144  			return nil, err
   145  		}
   146  		values = append(values, out)
   147  	} else {
   148  		log.Warnf("Values file %s is not allowed: %v", filepath.Join(appPath, "values.yaml"), err)
   149  	}
   150  	for i := range valuesFiles {
   151  		file := string(valuesFiles[i])
   152  		var fileValues []byte
   153  		parsedURL, err := url.ParseRequestURI(file)
   154  		if err == nil && (parsedURL.Scheme == "http" || parsedURL.Scheme == "https") {
   155  			fileValues, err = config.ReadRemoteFile(file)
   156  		} else {
   157  			if _, err := os.Stat(file); os.IsNotExist(err) {
   158  				continue
   159  			}
   160  			fileValues, err = os.ReadFile(file)
   161  		}
   162  		if err != nil {
   163  			return nil, fmt.Errorf("failed to read value file %s: %s", file, err)
   164  		}
   165  		values = append(values, string(fileValues))
   166  	}
   167  
   168  	output := map[string]string{}
   169  	for _, file := range values {
   170  		values := map[string]interface{}{}
   171  		if err := yaml.Unmarshal([]byte(file), &values); err != nil {
   172  			return nil, fmt.Errorf("failed to parse values: %s", err)
   173  		}
   174  		flatVals(values, output)
   175  	}
   176  
   177  	return output, nil
   178  }
   179  
   180  func flatVals(input interface{}, output map[string]string, prefixes ...string) {
   181  	switch i := input.(type) {
   182  	case map[string]interface{}:
   183  		for k, v := range i {
   184  			flatVals(v, output, append(prefixes, k)...)
   185  		}
   186  	case []interface{}:
   187  		p := append([]string(nil), prefixes...)
   188  		for j, v := range i {
   189  			flatVals(v, output, append(p[0:len(p)-1], fmt.Sprintf("%s[%v]", prefixes[len(p)-1], j))...)
   190  		}
   191  	default:
   192  		output[strings.Join(prefixes, ".")] = fmt.Sprintf("%v", i)
   193  	}
   194  }