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 }