github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/tiltfile/helm.go (about) 1 package tiltfile 2 3 import ( 4 "fmt" 5 "os" 6 "os/exec" 7 "path/filepath" 8 "regexp" 9 "runtime" 10 "strings" 11 12 "sigs.k8s.io/yaml" 13 ) 14 15 // The helm template command outputs predictable yaml with a "Source:" comment, 16 // so take advantage of that. 17 const helmSeparator = "---\n" 18 19 const helmFileRepository = "file://" 20 21 var helmTestYAMLMatcher = regexp.MustCompile("^# Source: .*/tests/") 22 23 func filterHelmTestYAML(resourceBlob string) string { 24 result := []string{} 25 resources := strings.Split(resourceBlob, helmSeparator) 26 for _, resource := range resources { 27 if isHelmTestYAML(resource) { 28 continue 29 } 30 31 result = append(result, resource) 32 } 33 return strings.Join(result, helmSeparator) 34 } 35 36 func isHelmTestYAML(resource string) bool { 37 lines := strings.Split(resource, "\n") 38 for _, line := range lines { 39 if helmTestYAMLMatcher.MatchString(line) { 40 return true 41 } 42 } 43 return false 44 } 45 46 type helmVersion int 47 48 const ( 49 unknownHelmVersion helmVersion = iota 50 helmV2 51 helmV3_0 52 helmV3_1andAbove 53 ) 54 55 func parseVersion(versionOutput string) (helmVersion, error) { 56 // helm v3.3.3 throws warnings on stdout, which messes up version parsing; 57 // if we have multiple lines of version output, assume version info is in 58 // the last line (see https://github.com/tilt-dev/tilt/issues/3788) 59 version := versionOutput 60 lines := strings.Split(strings.TrimSpace(versionOutput), "\n") 61 if len(lines) > 1 { 62 version = lines[(len(lines) - 1)] 63 } 64 65 if strings.HasPrefix(version, "v3.0.") { 66 return helmV3_0, nil 67 } else if strings.HasPrefix(version, "v3.") { 68 return helmV3_1andAbove, nil 69 } else if strings.HasPrefix(version, "Client: v2") { 70 return helmV2, nil 71 } 72 73 return unknownHelmVersion, fmt.Errorf("could not parse Helm version from string: %q", versionOutput) 74 } 75 76 func isHelmInstalled() bool { 77 if runtime.GOOS == "windows" { 78 cmd := exec.Command("where", "helm.exe") 79 if err := cmd.Run(); err != nil { 80 return false 81 } 82 83 return true 84 } 85 86 cmd := exec.Command("/bin/sh", "-c", "command -v helm") 87 if err := cmd.Run(); err != nil { 88 return false 89 } 90 91 return true 92 } 93 94 func getHelmVersion() (helmVersion, error) { 95 if !isHelmInstalled() { 96 return unknownHelmVersion, unableToFindHelmErrorMessage() 97 } 98 99 // NOTE(dmiller): I pass `--client` here even though that doesn't do anything in Helm v3. 100 // In Helm v2 that causes `helm version` to not reach out to tiller. Doing so can cause the 101 // command to fail, even though Tilt doesn't use the server at all (it just calls 102 // `helm template`). 103 // In Helm v3, it has no effect, not even an unknown flag error. 104 cmd := exec.Command("helm", "version", "--client", "--short") 105 106 out, err := cmd.Output() 107 if err != nil { 108 return unknownHelmVersion, err 109 } 110 111 return parseVersion(string(out)) 112 } 113 114 func unableToFindHelmErrorMessage() error { 115 var binaryName string 116 if runtime.GOOS == "windows" { 117 binaryName = "helm.exe" 118 } else { 119 binaryName = "helm" 120 } 121 122 return fmt.Errorf("Unable to find Helm installation. Make sure `%s` is on your $PATH.", binaryName) 123 } 124 125 func localSubchartDependenciesFromPath(chartPath string) ([]string, error) { 126 var deps []string 127 requirementsPath := filepath.Join(chartPath, "requirements.yaml") 128 dat, err := os.ReadFile(requirementsPath) 129 if os.IsNotExist(err) { 130 return deps, nil 131 } else if err != nil { 132 return deps, err 133 } 134 135 return localSubchartDependencies(dat) 136 } 137 138 type chartDependency struct { 139 Repository string 140 } 141 142 type chartMetadata struct { 143 Dependencies []chartDependency 144 } 145 146 func localSubchartDependencies(dat []byte) ([]string, error) { 147 var deps []string 148 var metadata chartMetadata 149 150 err := yaml.Unmarshal(dat, &metadata) 151 if err != nil { 152 return deps, err 153 } 154 155 for _, d := range metadata.Dependencies { 156 if strings.HasPrefix(d.Repository, helmFileRepository) { 157 deps = append(deps, strings.TrimPrefix(d.Repository, helmFileRepository)) 158 } 159 } 160 161 return deps, nil 162 }