github.com/splunk/dan1-qbec@v0.7.3/internal/vm/helm.go (about) 1 package vm 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "strings" 10 11 "github.com/ghodss/yaml" 12 "github.com/pkg/errors" 13 ) 14 15 // helmOptions are options that can be passed to the helm template command as well 16 // as a `thisFile` option that the caller needs to set from `std.thisFile` to make 17 // relative references to charts work correctly. 18 type helmOptions struct { 19 Execute []string `json:"execute"` // --execute option 20 KubeVersion string `json:"kubeVersion"` // --kube-version option 21 Name string `json:"name"` // --name option 22 NameTemplate string `json:"nameTemplate"` // --name-template option 23 Namespace string `json:"namespace"` // --namespace option 24 ThisFile string `json:"thisFile"` // use supplied file as current file to resolve relative refs, should be set to std.thisFile 25 Verbose bool `json:"verbose"` // print helm template command before executing it 26 //IsUpgrade bool `json:"isUpgrade"` // --is-upgrade option, defer adding this until implications are known, 27 } 28 29 // toArgs converts options to a slice of command-line args. 30 func (h helmOptions) toArgs() []string { 31 var ret []string 32 if len(h.Execute) > 0 { 33 for _, e := range h.Execute { 34 ret = append(ret, "--execute", e) 35 } 36 } 37 if h.KubeVersion != "" { 38 ret = append(ret, "--kube-version", h.KubeVersion) 39 } 40 if h.Name != "" { 41 ret = append(ret, "--name", h.Name) 42 } 43 if h.NameTemplate != "" { 44 ret = append(ret, "--name-template", h.NameTemplate) 45 } 46 if h.Namespace != "" { 47 ret = append(ret, "--namespace", h.Namespace) 48 } 49 //if h.IsUpgrade { 50 // ret = append(ret, "--is-upgrade") 51 //} 52 return ret 53 } 54 55 // expandHelmTemplate produces an array of objects parsed from the output of running `helm template` with 56 // the supplied values and helm options. 57 func expandHelmTemplate(chart string, values map[string]interface{}, options helmOptions) (out []interface{}, finalErr error) { 58 // run command from the directory containing current file or the OS temp dir if `thisFile` not specified. That is, 59 // explicitly fail to resolve relative refs unless the calling file is specified; don't let them work by happenstance. 60 workDir := os.TempDir() 61 if options.ThisFile != "" { 62 dir := filepath.Dir(options.ThisFile) 63 if !filepath.IsAbs(dir) { 64 wd, err := os.Getwd() 65 if err != nil { 66 return nil, errors.Wrap(err, "get working directory") 67 } 68 dir = filepath.Join(wd, dir) 69 } 70 workDir = dir 71 } 72 73 valueBytes, err := yaml.Marshal(values) 74 if err != nil { 75 return nil, errors.Wrap(err, "marshal values to YAML") 76 } 77 78 args := append([]string{"template", chart}, options.toArgs()...) 79 args = append(args, "--values", "-") 80 81 var stdout bytes.Buffer 82 cmd := exec.Command("helm", args...) 83 cmd.Stdin = bytes.NewBuffer(valueBytes) 84 cmd.Stdout = &stdout 85 cmd.Stderr = os.Stderr 86 cmd.Dir = workDir 87 88 if options.Verbose { 89 fmt.Fprintf(os.Stderr, "[helm template] cd %s && helm %s\n", workDir, strings.Join(args, " ")) 90 } 91 92 if err := cmd.Run(); err != nil { 93 if options.ThisFile == "" { 94 fmt.Fprintln(os.Stderr, "[WARN] helm template command failed, you may need to set the 'thisFile' option to make relative chart paths work") 95 } 96 return nil, errors.Wrap(err, "run helm template command") 97 } 98 99 return parseYAMLDocuments(bytes.NewReader(stdout.Bytes())) 100 }