github.com/darkowlzz/helm@v2.5.1-0.20171213183701-6707fe0468d4+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/getter" 38 "k8s.io/helm/pkg/helm" 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/repo" 43 "k8s.io/helm/pkg/strvals" 44 "net/url" 45 ) 46 47 const installDesc = ` 48 This command installs a chart archive. 49 50 The install argument must be a chart reference, a path to a packaged chart, 51 a path to an unpacked chart directory or a URL. 52 53 To override values in a chart, use either the '--values' flag and pass in a file 54 or use the '--set' flag and pass configuration from the command line. 55 56 $ helm install -f myvalues.yaml ./redis 57 58 or 59 60 $ helm install --set name=prod ./redis 61 62 You can specify the '--values'/'-f' flag multiple times. The priority will be given to the 63 last (right-most) file specified. For example, if both myvalues.yaml and override.yaml 64 contained a key called 'Test', the value set in override.yaml would take precedence: 65 66 $ helm install -f myvalues.yaml -f override.yaml ./redis 67 68 You can specify the '--set' flag multiple times. The priority will be given to the 69 last (right-most) set specified. For example, if both 'bar' and 'newbar' values are 70 set for a key called 'foo', the 'newbar' value would take precedence: 71 72 $ helm install --set foo=bar --set foo=newbar ./redis 73 74 75 To check the generated manifests of a release without installing the chart, 76 the '--debug' and '--dry-run' flags can be combined. This will still require a 77 round-trip to the Tiller server. 78 79 If --verify is set, the chart MUST have a provenance file, and the provenance 80 file MUST pass all verification steps. 81 82 There are five different ways you can express the chart you want to install: 83 84 1. By chart reference: helm install stable/mariadb 85 2. By path to a packaged chart: helm install ./nginx-1.2.3.tgz 86 3. By path to an unpacked chart directory: helm install ./nginx 87 4. By absolute URL: helm install https://example.com/charts/nginx-1.2.3.tgz 88 5. By chart reference and repo url: helm install --repo https://example.com/charts/ nginx 89 90 CHART REFERENCES 91 92 A chart reference is a convenient way of reference a chart in a chart repository. 93 94 When you use a chart reference with a repo prefix ('stable/mariadb'), Helm will look in the local 95 configuration for a chart repository named 'stable', and will then look for a 96 chart in that repository whose name is 'mariadb'. It will install the latest 97 version of that chart unless you also supply a version number with the 98 '--version' flag. 99 100 To see the list of chart repositories, use 'helm repo list'. To search for 101 charts in a repository, use 'helm search'. 102 ` 103 104 type installCmd struct { 105 name string 106 namespace string 107 valueFiles valueFiles 108 chartPath string 109 dryRun bool 110 disableHooks bool 111 replace bool 112 verify bool 113 keyring string 114 out io.Writer 115 client helm.Interface 116 values []string 117 nameTemplate string 118 version string 119 timeout int64 120 wait bool 121 repoURL string 122 devel bool 123 depUp bool 124 125 certFile string 126 keyFile string 127 caFile string 128 } 129 130 type valueFiles []string 131 132 func (v *valueFiles) String() string { 133 return fmt.Sprint(*v) 134 } 135 136 func (v *valueFiles) Type() string { 137 return "valueFiles" 138 } 139 140 func (v *valueFiles) Set(value string) error { 141 for _, filePath := range strings.Split(value, ",") { 142 *v = append(*v, filePath) 143 } 144 return nil 145 } 146 147 func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { 148 inst := &installCmd{ 149 out: out, 150 client: c, 151 } 152 153 cmd := &cobra.Command{ 154 Use: "install [CHART]", 155 Short: "install a chart archive", 156 Long: installDesc, 157 PreRunE: setupConnection, 158 RunE: func(cmd *cobra.Command, args []string) error { 159 if err := checkArgsLength(len(args), "chart name"); err != nil { 160 return err 161 } 162 163 debug("Original chart version: %q", inst.version) 164 if inst.version == "" && inst.devel { 165 debug("setting version to >0.0.0-0") 166 inst.version = ">0.0.0-0" 167 } 168 169 cp, err := locateChartPath(inst.repoURL, args[0], inst.version, inst.verify, inst.keyring, 170 inst.certFile, inst.keyFile, inst.caFile) 171 if err != nil { 172 return err 173 } 174 inst.chartPath = cp 175 inst.client = ensureHelmClient(inst.client) 176 return inst.run() 177 }, 178 } 179 180 f := cmd.Flags() 181 f.VarP(&inst.valueFiles, "values", "f", "specify values in a YAML file or a URL(can specify multiple)") 182 f.StringVarP(&inst.name, "name", "n", "", "release name. If unspecified, it will autogenerate one for you") 183 f.StringVar(&inst.namespace, "namespace", "", "namespace to install the release into. Defaults to the current kube config namespace.") 184 f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install") 185 f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install") 186 f.BoolVar(&inst.replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production") 187 f.StringArrayVar(&inst.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") 188 f.StringVar(&inst.nameTemplate, "name-template", "", "specify template used to name the release") 189 f.BoolVar(&inst.verify, "verify", false, "verify the package before installing it") 190 f.StringVar(&inst.keyring, "keyring", defaultKeyring(), "location of public keys used for verification") 191 f.StringVar(&inst.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed") 192 f.Int64Var(&inst.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") 193 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") 194 f.StringVar(&inst.repoURL, "repo", "", "chart repository url where to locate the requested chart") 195 f.StringVar(&inst.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") 196 f.StringVar(&inst.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") 197 f.StringVar(&inst.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") 198 f.BoolVar(&inst.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") 199 f.BoolVar(&inst.depUp, "dep-up", false, "run helm dependency update before installing the chart") 200 201 return cmd 202 } 203 204 func (i *installCmd) run() error { 205 debug("CHART PATH: %s\n", i.chartPath) 206 207 if i.namespace == "" { 208 i.namespace = defaultNamespace() 209 } 210 211 rawVals, err := vals(i.valueFiles, i.values) 212 if err != nil { 213 return err 214 } 215 216 // If template is specified, try to run the template. 217 if i.nameTemplate != "" { 218 i.name, err = generateName(i.nameTemplate) 219 if err != nil { 220 return err 221 } 222 // Print the final name so the user knows what the final name of the release is. 223 fmt.Printf("FINAL NAME: %s\n", i.name) 224 } 225 226 // Check chart requirements to make sure all dependencies are present in /charts 227 chartRequested, err := chartutil.Load(i.chartPath) 228 if err != nil { 229 return prettyError(err) 230 } 231 232 if req, err := chartutil.LoadRequirements(chartRequested); err == nil { 233 // If checkDependencies returns an error, we have unfullfilled dependencies. 234 // As of Helm 2.4.0, this is treated as a stopping condition: 235 // https://github.com/kubernetes/helm/issues/2209 236 if err := checkDependencies(chartRequested, req); err != nil { 237 if i.depUp { 238 man := &downloader.Manager{ 239 Out: i.out, 240 ChartPath: i.chartPath, 241 HelmHome: settings.Home, 242 Keyring: defaultKeyring(), 243 SkipUpdate: false, 244 Getters: getter.All(settings), 245 } 246 if err := man.Update(); err != nil { 247 return prettyError(err) 248 } 249 } else { 250 return prettyError(err) 251 } 252 253 } 254 } else if err != chartutil.ErrRequirementsNotFound { 255 return fmt.Errorf("cannot load requirements: %v", err) 256 } 257 258 res, err := i.client.InstallReleaseFromChart( 259 chartRequested, 260 i.namespace, 261 helm.ValueOverrides(rawVals), 262 helm.ReleaseName(i.name), 263 helm.InstallDryRun(i.dryRun), 264 helm.InstallReuseName(i.replace), 265 helm.InstallDisableHooks(i.disableHooks), 266 helm.InstallTimeout(i.timeout), 267 helm.InstallWait(i.wait)) 268 if err != nil { 269 return prettyError(err) 270 } 271 272 rel := res.GetRelease() 273 if rel == nil { 274 return nil 275 } 276 i.printRelease(rel) 277 278 // If this is a dry run, we can't display status. 279 if i.dryRun { 280 return nil 281 } 282 283 // Print the status like status command does 284 status, err := i.client.ReleaseStatus(rel.Name) 285 if err != nil { 286 return prettyError(err) 287 } 288 PrintStatus(i.out, status) 289 return nil 290 } 291 292 // Merges source and destination map, preferring values from the source map 293 func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} { 294 for k, v := range src { 295 // If the key doesn't exist already, then just set the key to that value 296 if _, exists := dest[k]; !exists { 297 dest[k] = v 298 continue 299 } 300 nextMap, ok := v.(map[string]interface{}) 301 // If it isn't another map, overwrite the value 302 if !ok { 303 dest[k] = v 304 continue 305 } 306 // If the key doesn't exist already, then just set the key to that value 307 if _, exists := dest[k]; !exists { 308 dest[k] = nextMap 309 continue 310 } 311 // Edge case: If the key exists in the destination, but isn't a map 312 destMap, isMap := dest[k].(map[string]interface{}) 313 // If the source map has a map for this key, prefer it 314 if !isMap { 315 dest[k] = v 316 continue 317 } 318 // If we got to this point, it is a map in both, so merge them 319 dest[k] = mergeValues(destMap, nextMap) 320 } 321 return dest 322 } 323 324 // vals merges values from files specified via -f/--values and 325 // directly via --set, marshaling them to YAML 326 func vals(valueFiles valueFiles, values []string) ([]byte, error) { 327 base := map[string]interface{}{} 328 329 // User specified a values files via -f/--values 330 for _, filePath := range valueFiles { 331 currentMap := map[string]interface{}{} 332 333 var bytes []byte 334 var err error 335 if strings.TrimSpace(filePath) == "-" { 336 bytes, err = ioutil.ReadAll(os.Stdin) 337 } else { 338 bytes, err = readFile(filePath) 339 } 340 341 if err != nil { 342 return []byte{}, err 343 } 344 345 if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { 346 return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err) 347 } 348 // Merge with the previous map 349 base = mergeValues(base, currentMap) 350 } 351 352 // User specified a value via --set 353 for _, value := range values { 354 if err := strvals.ParseInto(value, base); err != nil { 355 return []byte{}, fmt.Errorf("failed parsing --set data: %s", err) 356 } 357 } 358 359 return yaml.Marshal(base) 360 } 361 362 // printRelease prints info about a release if the Debug is true. 363 func (i *installCmd) printRelease(rel *release.Release) { 364 if rel == nil { 365 return 366 } 367 // TODO: Switch to text/template like everything else. 368 fmt.Fprintf(i.out, "NAME: %s\n", rel.Name) 369 if settings.Debug { 370 printRelease(i.out, rel) 371 } 372 } 373 374 // locateChartPath looks for a chart directory in known places, and returns either the full path or an error. 375 // 376 // This does not ensure that the chart is well-formed; only that the requested filename exists. 377 // 378 // Order of resolution: 379 // - current working directory 380 // - if path is absolute or begins with '.', error out here 381 // - chart repos in $HELM_HOME 382 // - URL 383 // 384 // If 'verify' is true, this will attempt to also verify the chart. 385 func locateChartPath(repoURL, name, version string, verify bool, keyring, 386 certFile, keyFile, caFile string) (string, error) { 387 name = strings.TrimSpace(name) 388 version = strings.TrimSpace(version) 389 if fi, err := os.Stat(name); err == nil { 390 abs, err := filepath.Abs(name) 391 if err != nil { 392 return abs, err 393 } 394 if verify { 395 if fi.IsDir() { 396 return "", errors.New("cannot verify a directory") 397 } 398 if _, err := downloader.VerifyChart(abs, keyring); err != nil { 399 return "", err 400 } 401 } 402 return abs, nil 403 } 404 if filepath.IsAbs(name) || strings.HasPrefix(name, ".") { 405 return name, fmt.Errorf("path %q not found", name) 406 } 407 408 crepo := filepath.Join(settings.Home.Repository(), name) 409 if _, err := os.Stat(crepo); err == nil { 410 return filepath.Abs(crepo) 411 } 412 413 dl := downloader.ChartDownloader{ 414 HelmHome: settings.Home, 415 Out: os.Stdout, 416 Keyring: keyring, 417 Getters: getter.All(settings), 418 } 419 if verify { 420 dl.Verify = downloader.VerifyAlways 421 } 422 if repoURL != "" { 423 chartURL, err := repo.FindChartInRepoURL(repoURL, name, version, 424 certFile, keyFile, caFile, getter.All(settings)) 425 if err != nil { 426 return "", err 427 } 428 name = chartURL 429 } 430 431 if _, err := os.Stat(settings.Home.Archive()); os.IsNotExist(err) { 432 os.MkdirAll(settings.Home.Archive(), 0744) 433 } 434 435 filename, _, err := dl.DownloadTo(name, version, settings.Home.Archive()) 436 if err == nil { 437 lname, err := filepath.Abs(filename) 438 if err != nil { 439 return filename, err 440 } 441 debug("Fetched %s to %s\n", name, filename) 442 return lname, nil 443 } else if settings.Debug { 444 return filename, err 445 } 446 447 return filename, fmt.Errorf("failed to download %q", name) 448 } 449 450 func generateName(nameTemplate string) (string, error) { 451 t, err := template.New("name-template").Funcs(sprig.TxtFuncMap()).Parse(nameTemplate) 452 if err != nil { 453 return "", err 454 } 455 var b bytes.Buffer 456 err = t.Execute(&b, nil) 457 if err != nil { 458 return "", err 459 } 460 return b.String(), nil 461 } 462 463 func defaultNamespace() string { 464 if ns, _, err := kube.GetConfig(settings.KubeContext, settings.KubeConfig).Namespace(); err == nil { 465 return ns 466 } 467 return "default" 468 } 469 470 func checkDependencies(ch *chart.Chart, reqs *chartutil.Requirements) error { 471 missing := []string{} 472 473 deps := ch.GetDependencies() 474 for _, r := range reqs.Dependencies { 475 found := false 476 for _, d := range deps { 477 if d.Metadata.Name == r.Name { 478 found = true 479 break 480 } 481 } 482 if !found { 483 missing = append(missing, r.Name) 484 } 485 } 486 487 if len(missing) > 0 { 488 return fmt.Errorf("found in requirements.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", ")) 489 } 490 return nil 491 } 492 493 //readFile load a file from the local directory or a remote file with a url. 494 func readFile(filePath string) ([]byte, error) { 495 u, _ := url.Parse(filePath) 496 p := getter.All(settings) 497 498 // FIXME: maybe someone handle other protocols like ftp. 499 getterConstructor, err := p.ByScheme(u.Scheme) 500 501 if err != nil { 502 return ioutil.ReadFile(filePath) 503 } else { 504 getter, err := getterConstructor(filePath, "", "", "") 505 if err != nil { 506 return []byte{}, err 507 } 508 data, err := getter.Get(filePath) 509 return data.Bytes(), err 510 } 511 }