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 }