github.com/argoproj/argo-cd@v1.8.7/util/helm/helm.go (about)

     1  package helm
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net/url"
     7  	"os/exec"
     8  	"path"
     9  	"strings"
    10  
    11  	"github.com/ghodss/yaml"
    12  
    13  	"github.com/argoproj/argo-cd/util/config"
    14  	executil "github.com/argoproj/argo-cd/util/exec"
    15  )
    16  
    17  type HelmRepository struct {
    18  	Creds
    19  	Name string
    20  	Repo string
    21  }
    22  
    23  // Helm provides wrapper functionality around the `helm` command.
    24  type Helm interface {
    25  	// Template returns a list of unstructured objects from a `helm template` command
    26  	Template(opts *TemplateOpts) (string, error)
    27  	// GetParameters returns a list of chart parameters taking into account values in provided YAML files.
    28  	GetParameters(valuesFiles []string) (map[string]string, error)
    29  	// DependencyBuild runs `helm dependency build` to download a chart's dependencies
    30  	DependencyBuild() error
    31  	// Init runs `helm init --client-only`
    32  	Init() error
    33  	// Dispose deletes temp resources
    34  	Dispose()
    35  }
    36  
    37  // NewHelmApp create a new wrapper to run commands on the `helm` command-line tool.
    38  func NewHelmApp(workDir string, repos []HelmRepository, isLocal bool, version string) (Helm, error) {
    39  	cmd, err := NewCmd(workDir, version)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	cmd.IsLocal = isLocal
    44  
    45  	return &helm{repos: repos, cmd: *cmd}, nil
    46  }
    47  
    48  type helm struct {
    49  	cmd   Cmd
    50  	repos []HelmRepository
    51  }
    52  
    53  // IsMissingDependencyErr tests if the error is related to a missing chart dependency
    54  func IsMissingDependencyErr(err error) bool {
    55  	return strings.Contains(err.Error(), "found in requirements.yaml, but missing in charts") ||
    56  		strings.Contains(err.Error(), "found in Chart.yaml, but missing in charts/ directory")
    57  }
    58  
    59  func (h *helm) Template(templateOpts *TemplateOpts) (string, error) {
    60  	out, err := h.cmd.template(".", templateOpts)
    61  	if err != nil {
    62  		return "", err
    63  	}
    64  	return out, nil
    65  }
    66  
    67  func (h *helm) DependencyBuild() error {
    68  	for _, repo := range h.repos {
    69  		_, err := h.cmd.RepoAdd(repo.Name, repo.Repo, repo.Creds)
    70  
    71  		if err != nil {
    72  			return err
    73  		}
    74  	}
    75  	h.repos = nil
    76  	_, err := h.cmd.dependencyBuild()
    77  	return err
    78  }
    79  
    80  func (h *helm) Init() error {
    81  	_, err := h.cmd.Init()
    82  	return err
    83  }
    84  
    85  func (h *helm) Dispose() {
    86  	h.cmd.Close()
    87  }
    88  
    89  func Version(shortForm bool) (string, error) {
    90  	executable := "helm"
    91  	cmdArgs := []string{"version", "--client"}
    92  	if shortForm {
    93  		cmdArgs = append(cmdArgs, "--short")
    94  	}
    95  	cmd := exec.Command(executable, cmdArgs...)
    96  	// example version output:
    97  	// long: "version.BuildInfo{Version:\"v3.3.1\", GitCommit:\"249e5215cde0c3fa72e27eb7a30e8d55c9696144\", GitTreeState:\"clean\", GoVersion:\"go1.14.7\"}"
    98  	// short: "v3.3.1+g249e521"
    99  	version, err := executil.RunWithRedactor(cmd, redactor)
   100  	if err != nil {
   101  		return "", fmt.Errorf("could not get helm version: %s", err)
   102  	}
   103  	return strings.TrimSpace(version), nil
   104  }
   105  
   106  func (h *helm) GetParameters(valuesFiles []string) (map[string]string, error) {
   107  	out, err := h.cmd.inspectValues(".")
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	values := []string{out}
   112  	for _, file := range valuesFiles {
   113  		var fileValues []byte
   114  		parsedURL, err := url.ParseRequestURI(file)
   115  		if err == nil && (parsedURL.Scheme == "http" || parsedURL.Scheme == "https") {
   116  			fileValues, err = config.ReadRemoteFile(file)
   117  		} else {
   118  			fileValues, err = ioutil.ReadFile(path.Join(h.cmd.WorkDir, file))
   119  		}
   120  		if err != nil {
   121  			return nil, fmt.Errorf("failed to read value file %s: %s", file, err)
   122  		}
   123  		values = append(values, string(fileValues))
   124  	}
   125  
   126  	output := map[string]string{}
   127  	for _, file := range values {
   128  		values := map[string]interface{}{}
   129  		if err = yaml.Unmarshal([]byte(file), &values); err != nil {
   130  			return nil, fmt.Errorf("failed to parse values: %s", err)
   131  		}
   132  		flatVals(values, output)
   133  	}
   134  
   135  	return output, nil
   136  }
   137  
   138  func flatVals(input interface{}, output map[string]string, prefixes ...string) {
   139  	switch i := input.(type) {
   140  	case map[string]interface{}:
   141  		for k, v := range i {
   142  			flatVals(v, output, append(prefixes, k)...)
   143  		}
   144  	case []interface{}:
   145  		for j, v := range i {
   146  			flatVals(v, output, append(prefixes[0:len(prefixes)-1], fmt.Sprintf("%s[%v]", prefixes[len(prefixes)-1], j))...)
   147  		}
   148  	default:
   149  		output[strings.Join(prefixes, ".")] = fmt.Sprintf("%v", i)
   150  	}
   151  }