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  }