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