github.com/Microsoft/fabrikate@v0.0.0-20190420002442-bff75be28d02/generators/helm.go (about)

     1  package generators
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os/exec"
     7  	"path"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/Microsoft/fabrikate/core"
    12  	"github.com/kyokomi/emoji"
    13  	log "github.com/sirupsen/logrus"
    14  	"gopkg.in/yaml.v2"
    15  )
    16  
    17  type HelmGenerator struct{}
    18  
    19  func AddNamespaceToManifests(manifests string, namespace string) (namespacedManifests string, err error) {
    20  	splitManifest := strings.Split(manifests, "\n---")
    21  
    22  	for _, manifest := range splitManifest {
    23  		parsedManifest := make(map[interface{}]interface{})
    24  		err := yaml.Unmarshal([]byte(manifest), &parsedManifest)
    25  		if err != nil {
    26  			return "", err
    27  		}
    28  
    29  		// strip any empty entries
    30  		if len(parsedManifest) == 0 {
    31  			continue
    32  		}
    33  
    34  		if parsedManifest["metadata"] != nil {
    35  			metadataMap := parsedManifest["metadata"].(map[interface{}]interface{})
    36  			if metadataMap["namespace"] == nil {
    37  				metadataMap["namespace"] = namespace
    38  			}
    39  		}
    40  
    41  		updatedManifest, err := yaml.Marshal(&parsedManifest)
    42  		if err != nil {
    43  			return "", err
    44  		}
    45  
    46  		namespacedManifests += fmt.Sprintf("---\n%s\n", updatedManifest)
    47  	}
    48  
    49  	return namespacedManifests, nil
    50  }
    51  
    52  func (hg *HelmGenerator) MakeHelmRepoPath(component *core.Component) string {
    53  	if component.Method != "git" {
    54  		return component.PhysicalPath
    55  	} else {
    56  		return path.Join(component.PhysicalPath, "helm_repos", component.Name)
    57  	}
    58  }
    59  
    60  func (hg *HelmGenerator) Generate(component *core.Component) (manifest string, err error) {
    61  	log.Println(emoji.Sprintf(":truck: generating component '%s' with helm with repo %s", component.Name, component.Source))
    62  
    63  	configYaml, err := yaml.Marshal(&component.Config.Config)
    64  	if err != nil {
    65  		log.Errorf("marshalling config yaml for helm generated component '%s' failed with: %s\n", component.Name, err.Error())
    66  		return "", err
    67  	}
    68  
    69  	helmRepoPath := hg.MakeHelmRepoPath(component)
    70  	absHelmRepoPath, err := filepath.Abs(helmRepoPath)
    71  	if err != nil {
    72  		return "", err
    73  	}
    74  
    75  	chartPath := path.Join(absHelmRepoPath, component.Path)
    76  	absOverriddenPath := path.Join(chartPath, "overriddenValues.yaml")
    77  
    78  	log.Debugf("writing config %s to %s\n", configYaml, absOverriddenPath)
    79  	err = ioutil.WriteFile(absOverriddenPath, configYaml, 0644)
    80  	if err != nil {
    81  		return "", err
    82  	}
    83  
    84  	name := component.Name
    85  
    86  	namespace := "default"
    87  	if component.Config.Namespace != "" {
    88  		namespace = component.Config.Namespace
    89  	}
    90  
    91  	output, err := exec.Command("helm", "template", chartPath, "--values", absOverriddenPath, "--name", name, "--namespace", namespace).Output()
    92  
    93  	if err != nil {
    94  		if ee, ok := err.(*exec.ExitError); ok {
    95  			log.Errorf("helm template failed with: %s\n", ee.Stderr)
    96  			_ = exec.Command("rm", absOverriddenPath).Run()
    97  			return "", err
    98  		}
    99  	}
   100  
   101  	stringManifests := string(output)
   102  
   103  	// helm template does not inject namespace unless chart directly provides support for it: https://github.com/helm/helm/issues/3553
   104  	// some helm templates expect Tiller to inject namespace, so enable Fabrikate component designer to
   105  	// opt into injecting these namespaces manually.  We should reassess if this is necessary after Helm 3 is released and client side
   106  	// templating really becomes a first class function in Helm.
   107  	if component.Config.InjectNamespace && component.Config.Namespace != "" {
   108  		stringManifests, err = AddNamespaceToManifests(stringManifests, component.Config.Namespace)
   109  	}
   110  
   111  	_ = exec.Command("rm", absOverriddenPath).Run()
   112  
   113  	return stringManifests, err
   114  }
   115  
   116  func (hg *HelmGenerator) Install(component *core.Component) (err error) {
   117  	if len(component.Source) == 0 || component.Method != "git" {
   118  		return nil
   119  	}
   120  
   121  	helmRepoPath := hg.MakeHelmRepoPath(component)
   122  	if err := exec.Command("rm", "-rf", helmRepoPath).Run(); err != nil {
   123  		return err
   124  	}
   125  
   126  	if err := exec.Command("mkdir", "-p", helmRepoPath).Run(); err != nil {
   127  		return err
   128  	}
   129  
   130  	log.Println(emoji.Sprintf(":helicopter: installing helm repo %s for %s into %s", component.Source, component.Name, helmRepoPath))
   131  	if err = core.CloneRepo(component.Source, component.Version, helmRepoPath, component.Branch); err != nil {
   132  		return err
   133  	}
   134  
   135  	absHelmRepoPath, err := filepath.Abs(helmRepoPath)
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	chartPath := path.Join(absHelmRepoPath, component.Path)
   141  
   142  	for name, url := range component.Repositories {
   143  		log.Println(emoji.Sprintf(":helicopter: adding helm repo '%s' at %s for component '%s'", name, url, component.Name))
   144  		if err = exec.Command("helm", "repo", "add", name, url).Run(); err != nil {
   145  			return err
   146  		}
   147  	}
   148  
   149  	log.Println(emoji.Sprintf(":helicopter: updating helm chart's dependencies for component '%s'", component.Name))
   150  	err = exec.Command("helm", "dependency", "update", chartPath).Run()
   151  
   152  	if err != nil {
   153  		log.Errorf("updating chart dependencies failed\n")
   154  		log.Errorf("run 'helm dependency update %s' for more error details.\n", chartPath)
   155  	}
   156  
   157  	return err
   158  }