github.com/sgoings/helm@v2.0.0-alpha.2.0.20170406211108-734e92851ac3+incompatible/cmd/helm/install.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors All rights reserved. 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 main 18 19 import ( 20 "bytes" 21 "errors" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "strings" 28 29 "text/template" 30 31 "github.com/Masterminds/sprig" 32 "github.com/ghodss/yaml" 33 "github.com/spf13/cobra" 34 35 "k8s.io/helm/pkg/chartutil" 36 "k8s.io/helm/pkg/downloader" 37 "k8s.io/helm/pkg/helm" 38 "k8s.io/helm/pkg/helm/helmpath" 39 "k8s.io/helm/pkg/kube" 40 "k8s.io/helm/pkg/proto/hapi/chart" 41 "k8s.io/helm/pkg/proto/hapi/release" 42 "k8s.io/helm/pkg/strvals" 43 ) 44 45 const installDesc = ` 46 This command installs a chart archive. 47 48 The install argument must be either a relative path to a chart directory or the 49 name of a chart in the current working directory. 50 51 To override values in a chart, use either the '--values' flag and pass in a file 52 or use the '--set' flag and pass configuration from the command line. 53 54 $ helm install -f myvalues.yaml ./redis 55 56 or 57 58 $ helm install --set name=prod ./redis 59 60 You can specify the '--values'/'-f' flag multiple times. The priority will be given to the 61 last (right-most) file specified. For example, if both myvalues.yaml and override.yaml 62 contained a key called 'Test', the value set in override.yaml would take precedence: 63 64 $ helm install -f myvalues.yaml -f override.yaml ./redis 65 66 You can specify the '--set' flag multiple times. The priority will be given to the 67 last (right-most) set specified. For example, if both 'bar' and 'newbar' values are 68 set for a key called 'foo', the 'newbar' value would take precedence: 69 70 $ helm install --set foo=bar --set foo=newbar ./redis 71 72 73 To check the generated manifests of a release without installing the chart, 74 the '--debug' and '--dry-run' flags can be combined. This will still require a 75 round-trip to the Tiller server. 76 77 If --verify is set, the chart MUST have a provenance file, and the provenenace 78 fall MUST pass all verification steps. 79 80 There are four different ways you can express the chart you want to install: 81 82 1. By chart reference: helm install stable/mariadb 83 2. By path to a packaged chart: helm install ./nginx-1.2.3.tgz 84 3. By path to an unpacked chart directory: helm install ./nginx 85 4. By absolute URL: helm install https://example.com/charts/nginx-1.2.3.tgz 86 87 CHART REFERENCES 88 89 A chart reference is a convenient way of reference a chart in a chart repository. 90 91 When you use a chart reference ('stable/mariadb'), Helm will look in the local 92 configuration for a chart repository named 'stable', and will then look for a 93 chart in that repository whose name is 'mariadb'. It will install the latest 94 version of that chart unless you also supply a version number with the 95 '--version' flag. 96 97 To see the list of chart repositories, use 'helm repo list'. To search for 98 charts in a repository, use 'helm search'. 99 ` 100 101 type installCmd struct { 102 name string 103 namespace string 104 valueFiles valueFiles 105 chartPath string 106 dryRun bool 107 disableHooks bool 108 replace bool 109 verify bool 110 keyring string 111 out io.Writer 112 client helm.Interface 113 values []string 114 nameTemplate string 115 version string 116 timeout int64 117 wait bool 118 } 119 120 type valueFiles []string 121 122 func (v *valueFiles) String() string { 123 return fmt.Sprint(*v) 124 } 125 126 func (v *valueFiles) Type() string { 127 return "valueFiles" 128 } 129 130 func (v *valueFiles) Set(value string) error { 131 for _, filePath := range strings.Split(value, ",") { 132 *v = append(*v, filePath) 133 } 134 return nil 135 } 136 137 func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { 138 inst := &installCmd{ 139 out: out, 140 client: c, 141 } 142 143 cmd := &cobra.Command{ 144 Use: "install [CHART]", 145 Short: "install a chart archive", 146 Long: installDesc, 147 PersistentPreRunE: setupConnection, 148 RunE: func(cmd *cobra.Command, args []string) error { 149 if err := checkArgsLength(len(args), "chart name"); err != nil { 150 return err 151 } 152 cp, err := locateChartPath(args[0], inst.version, inst.verify, inst.keyring) 153 if err != nil { 154 return err 155 } 156 inst.chartPath = cp 157 inst.client = ensureHelmClient(inst.client) 158 return inst.run() 159 }, 160 } 161 162 f := cmd.Flags() 163 f.VarP(&inst.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") 164 f.StringVarP(&inst.name, "name", "n", "", "release name. If unspecified, it will autogenerate one for you") 165 f.StringVar(&inst.namespace, "namespace", "", "namespace to install the release into") 166 f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install") 167 f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install") 168 f.BoolVar(&inst.replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production") 169 f.StringArrayVar(&inst.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") 170 f.StringVar(&inst.nameTemplate, "name-template", "", "specify template used to name the release") 171 f.BoolVar(&inst.verify, "verify", false, "verify the package before installing it") 172 f.StringVar(&inst.keyring, "keyring", defaultKeyring(), "location of public keys used for verification") 173 f.StringVar(&inst.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed") 174 f.Int64Var(&inst.timeout, "timeout", 300, "time in seconds to wait for any individual kubernetes operation (like Jobs for hooks)") 175 f.BoolVar(&inst.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") 176 177 return cmd 178 } 179 180 func (i *installCmd) run() error { 181 if flagDebug { 182 fmt.Fprintf(i.out, "CHART PATH: %s\n", i.chartPath) 183 } 184 185 if i.namespace == "" { 186 i.namespace = defaultNamespace() 187 } 188 189 rawVals, err := i.vals() 190 if err != nil { 191 return err 192 } 193 194 // If template is specified, try to run the template. 195 if i.nameTemplate != "" { 196 i.name, err = generateName(i.nameTemplate) 197 if err != nil { 198 return err 199 } 200 // Print the final name so the user knows what the final name of the release is. 201 fmt.Printf("FINAL NAME: %s\n", i.name) 202 } 203 204 // Check chart requirements to make sure all dependencies are present in /charts 205 if c, err := chartutil.Load(i.chartPath); err == nil { 206 if req, err := chartutil.LoadRequirements(c); err == nil { 207 checkDependencies(c, req, i.out) 208 } 209 } 210 211 res, err := i.client.InstallRelease( 212 i.chartPath, 213 i.namespace, 214 helm.ValueOverrides(rawVals), 215 helm.ReleaseName(i.name), 216 helm.InstallDryRun(i.dryRun), 217 helm.InstallReuseName(i.replace), 218 helm.InstallDisableHooks(i.disableHooks), 219 helm.InstallTimeout(i.timeout), 220 helm.InstallWait(i.wait)) 221 if err != nil { 222 return prettyError(err) 223 } 224 225 rel := res.GetRelease() 226 if rel == nil { 227 return nil 228 } 229 i.printRelease(rel) 230 231 // If this is a dry run, we can't display status. 232 if i.dryRun { 233 return nil 234 } 235 236 // Print the status like status command does 237 status, err := i.client.ReleaseStatus(rel.Name) 238 if err != nil { 239 return prettyError(err) 240 } 241 PrintStatus(i.out, status) 242 return nil 243 } 244 245 // Merges source and destination map, preferring values from the source map 246 func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} { 247 for k, v := range src { 248 // If the key doesn't exist already, then just set the key to that value 249 if _, exists := dest[k]; !exists { 250 dest[k] = v 251 continue 252 } 253 nextMap, ok := v.(map[string]interface{}) 254 // If it isn't another map, overwrite the value 255 if !ok { 256 dest[k] = v 257 continue 258 } 259 // If the key doesn't exist already, then just set the key to that value 260 if _, exists := dest[k]; !exists { 261 dest[k] = nextMap 262 continue 263 } 264 // Edge case: If the key exists in the destination, but isn't a map 265 destMap, isMap := dest[k].(map[string]interface{}) 266 // If the source map has a map for this key, prefer it 267 if !isMap { 268 dest[k] = v 269 continue 270 } 271 // If we got to this point, it is a map in both, so merge them 272 dest[k] = mergeValues(destMap, nextMap) 273 } 274 return dest 275 } 276 277 func (i *installCmd) vals() ([]byte, error) { 278 base := map[string]interface{}{} 279 280 // User specified a values files via -f/--values 281 for _, filePath := range i.valueFiles { 282 currentMap := map[string]interface{}{} 283 bytes, err := ioutil.ReadFile(filePath) 284 if err != nil { 285 return []byte{}, err 286 } 287 288 if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { 289 return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err) 290 } 291 // Merge with the previous map 292 base = mergeValues(base, currentMap) 293 } 294 295 // User specified a value via --set 296 for _, value := range i.values { 297 if err := strvals.ParseInto(value, base); err != nil { 298 return []byte{}, fmt.Errorf("failed parsing --set data: %s", err) 299 } 300 } 301 302 return yaml.Marshal(base) 303 } 304 305 // printRelease prints info about a release if the flagDebug is true. 306 func (i *installCmd) printRelease(rel *release.Release) { 307 if rel == nil { 308 return 309 } 310 // TODO: Switch to text/template like everything else. 311 fmt.Fprintf(i.out, "NAME: %s\n", rel.Name) 312 if flagDebug { 313 printRelease(i.out, rel) 314 } 315 } 316 317 // locateChartPath looks for a chart directory in known places, and returns either the full path or an error. 318 // 319 // This does not ensure that the chart is well-formed; only that the requested filename exists. 320 // 321 // Order of resolution: 322 // - current working directory 323 // - if path is absolute or begins with '.', error out here 324 // - chart repos in $HELM_HOME 325 // - URL 326 // 327 // If 'verify' is true, this will attempt to also verify the chart. 328 func locateChartPath(name, version string, verify bool, keyring string) (string, error) { 329 name = strings.TrimSpace(name) 330 version = strings.TrimSpace(version) 331 if fi, err := os.Stat(name); err == nil { 332 abs, err := filepath.Abs(name) 333 if err != nil { 334 return abs, err 335 } 336 if verify { 337 if fi.IsDir() { 338 return "", errors.New("cannot verify a directory") 339 } 340 if _, err := downloader.VerifyChart(abs, keyring); err != nil { 341 return "", err 342 } 343 } 344 return abs, nil 345 } 346 if filepath.IsAbs(name) || strings.HasPrefix(name, ".") { 347 return name, fmt.Errorf("path %q not found", name) 348 } 349 350 crepo := filepath.Join(helmpath.Home(homePath()).Repository(), name) 351 if _, err := os.Stat(crepo); err == nil { 352 return filepath.Abs(crepo) 353 } 354 355 dl := downloader.ChartDownloader{ 356 HelmHome: helmpath.Home(homePath()), 357 Out: os.Stdout, 358 Keyring: keyring, 359 } 360 if verify { 361 dl.Verify = downloader.VerifyAlways 362 } 363 364 filename, _, err := dl.DownloadTo(name, version, ".") 365 if err == nil { 366 lname, err := filepath.Abs(filename) 367 if err != nil { 368 return filename, err 369 } 370 if flagDebug { 371 fmt.Printf("Fetched %s to %s\n", name, filename) 372 } 373 return lname, nil 374 } else if flagDebug { 375 return filename, err 376 } 377 378 return filename, fmt.Errorf("file %q not found", name) 379 } 380 381 func generateName(nameTemplate string) (string, error) { 382 t, err := template.New("name-template").Funcs(sprig.TxtFuncMap()).Parse(nameTemplate) 383 if err != nil { 384 return "", err 385 } 386 var b bytes.Buffer 387 err = t.Execute(&b, nil) 388 if err != nil { 389 return "", err 390 } 391 return b.String(), nil 392 } 393 394 func defaultNamespace() string { 395 if ns, _, err := kube.GetConfig(kubeContext).Namespace(); err == nil { 396 return ns 397 } 398 return "default" 399 } 400 401 func checkDependencies(ch *chart.Chart, reqs *chartutil.Requirements, out io.Writer) { 402 deps := ch.GetDependencies() 403 for _, r := range reqs.Dependencies { 404 found := false 405 for _, d := range deps { 406 if d.Metadata.Name == r.Name { 407 found = true 408 break 409 } 410 } 411 if !found { 412 fmt.Fprintf(out, "Warning: %s is in requirements.yaml but not in the charts/ directory!\n", r.Name) 413 } 414 } 415 }