github.com/splunk/qbec@v0.15.2/vm/internal/natives/helm.go (about)

     1  /*
     2     Copyright 2021 Splunk Inc.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package natives
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"os"
    23  	"os/exec"
    24  	"path/filepath"
    25  	"strings"
    26  
    27  	"github.com/ghodss/yaml"
    28  	"github.com/pkg/errors"
    29  )
    30  
    31  // helmOptions are options that can be passed to the helm template command as well
    32  // as a `thisFile` option that the caller needs to set from `std.thisFile` to make
    33  // relative references to charts work correctly.
    34  type helmOptions struct {
    35  	Execute      []string `json:"execute"`      // --execute option
    36  	KubeVersion  string   `json:"kubeVersion"`  // --kube-version option
    37  	Name         string   `json:"name"`         // --name option
    38  	NameTemplate string   `json:"nameTemplate"` // --name-template option
    39  	Namespace    string   `json:"namespace"`    // --namespace option
    40  	ThisFile     string   `json:"thisFile"`     // use supplied file as current file to resolve relative refs, should be set to std.thisFile
    41  	Verbose      bool     `json:"verbose"`      // print helm template command before executing it
    42  	//IsUpgrade    bool     `json:"isUpgrade"` // --is-upgrade option, defer adding this until implications are known,
    43  }
    44  
    45  // toArgs converts options to a slice of command-line args.
    46  func (h helmOptions) toArgs() []string {
    47  	var ret []string
    48  	if len(h.Execute) > 0 {
    49  		for _, e := range h.Execute {
    50  			ret = append(ret, "--execute", e)
    51  		}
    52  	}
    53  	if h.KubeVersion != "" {
    54  		ret = append(ret, "--kube-version", h.KubeVersion)
    55  	}
    56  	if h.Name != "" {
    57  		ret = append(ret, "--name", h.Name)
    58  	}
    59  	if h.NameTemplate != "" {
    60  		ret = append(ret, "--name-template", h.NameTemplate)
    61  	}
    62  	if h.Namespace != "" {
    63  		ret = append(ret, "--namespace", h.Namespace)
    64  	}
    65  	//if h.IsUpgrade {
    66  	//	ret = append(ret, "--is-upgrade")
    67  	//}
    68  	return ret
    69  }
    70  
    71  // expandHelmTemplate produces an array of objects parsed from the output of running `helm template` with
    72  // the supplied values and helm options.
    73  func expandHelmTemplate(chart string, values map[string]interface{}, options helmOptions) (out []interface{}, finalErr error) {
    74  	// run command from the directory containing current file or the OS temp dir if `thisFile` not specified. That is,
    75  	// explicitly fail to resolve relative refs unless the calling file is specified; don't let them work by happenstance.
    76  	workDir := os.TempDir()
    77  	if options.ThisFile != "" {
    78  		dir := filepath.Dir(options.ThisFile)
    79  		if !filepath.IsAbs(dir) {
    80  			wd, err := os.Getwd()
    81  			if err != nil {
    82  				return nil, errors.Wrap(err, "get working directory")
    83  			}
    84  			dir = filepath.Join(wd, dir)
    85  		}
    86  		workDir = dir
    87  	}
    88  
    89  	valueBytes, err := yaml.Marshal(values)
    90  	if err != nil {
    91  		return nil, errors.Wrap(err, "marshal values to YAML")
    92  	}
    93  
    94  	args := append([]string{"template", chart}, options.toArgs()...)
    95  	args = append(args, "--values", "-")
    96  
    97  	var stdout bytes.Buffer
    98  	cmd := exec.Command("helm", args...)
    99  	cmd.Stdin = bytes.NewBuffer(valueBytes)
   100  	cmd.Stdout = &stdout
   101  	cmd.Stderr = os.Stderr
   102  	cmd.Dir = workDir
   103  
   104  	if options.Verbose {
   105  		fmt.Fprintf(os.Stderr, "[helm template] cd %s && helm %s\n", workDir, strings.Join(args, " "))
   106  	}
   107  
   108  	if err := cmd.Run(); err != nil {
   109  		if options.ThisFile == "" {
   110  			fmt.Fprintln(os.Stderr, "[WARN] helm template command failed, you may need to set the 'thisFile' option to make relative chart paths work")
   111  		}
   112  		return nil, errors.Wrap(err, "run helm template command")
   113  	}
   114  
   115  	return ParseYAMLDocuments(bytes.NewReader(stdout.Bytes()))
   116  }