github.com/tilt-dev/tilt@v0.36.0/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 helmV3_0 51 helmV3_1andAbove 52 ) 53 54 func parseVersion(versionOutput string) (helmVersion, error) { 55 // helm v3.3.3 throws warnings on stdout, which messes up version parsing; 56 // if we have multiple lines of version output, assume version info is in 57 // the last line (see https://github.com/tilt-dev/tilt/issues/3788) 58 version := versionOutput 59 lines := strings.Split(strings.TrimSpace(versionOutput), "\n") 60 if len(lines) > 1 { 61 version = lines[(len(lines) - 1)] 62 } 63 64 if strings.HasPrefix(version, "v3.0.") { 65 return helmV3_0, nil 66 } else if strings.HasPrefix(version, "v3.") || strings.HasPrefix(version, "3.") || strings.HasPrefix(version, "v4.") { 67 return helmV3_1andAbove, nil 68 } 69 70 return unknownHelmVersion, fmt.Errorf("could not parse Helm version from string: %q", versionOutput) 71 } 72 73 func isHelmInstalled() bool { 74 if runtime.GOOS == "windows" { 75 cmd := exec.Command("where", "helm.exe") 76 if err := cmd.Run(); err != nil { 77 return false 78 } 79 80 return true 81 } 82 83 cmd := exec.Command("/bin/sh", "-c", "command -v helm") 84 if err := cmd.Run(); err != nil { 85 return false 86 } 87 88 return true 89 } 90 91 func getHelmVersion() (helmVersion, error) { 92 if !isHelmInstalled() { 93 return unknownHelmVersion, unableToFindHelmErrorMessage() 94 } 95 96 cmd := exec.Command("helm", "version", "--short") 97 out, err := cmd.Output() 98 if err != nil { 99 return unknownHelmVersion, err 100 } 101 102 return parseVersion(string(out)) 103 } 104 105 func unableToFindHelmErrorMessage() error { 106 var binaryName string 107 if runtime.GOOS == "windows" { 108 binaryName = "helm.exe" 109 } else { 110 binaryName = "helm" 111 } 112 113 return fmt.Errorf("Unable to find Helm installation. Make sure `%s` is on your $PATH.", binaryName) 114 } 115 116 func localSubchartDependenciesFromPath(chartPath string) ([]string, error) { 117 var deps []string 118 requirementsPath := filepath.Join(chartPath, "requirements.yaml") 119 dat, err := os.ReadFile(requirementsPath) 120 if os.IsNotExist(err) { 121 return deps, nil 122 } else if err != nil { 123 return deps, err 124 } 125 126 return localSubchartDependencies(dat) 127 } 128 129 type chartDependency struct { 130 Repository string 131 } 132 133 type chartMetadata struct { 134 Dependencies []chartDependency 135 } 136 137 func localSubchartDependencies(dat []byte) ([]string, error) { 138 var deps []string 139 var metadata chartMetadata 140 141 err := yaml.Unmarshal(dat, &metadata) 142 if err != nil { 143 return deps, err 144 } 145 146 for _, d := range metadata.Dependencies { 147 if strings.HasPrefix(d.Repository, helmFileRepository) { 148 deps = append(deps, strings.TrimPrefix(d.Repository, helmFileRepository)) 149 } 150 } 151 152 return deps, nil 153 }