github.com/valdemarpavesi/helm@v2.9.1+incompatible/cmd/helm/init.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 "encoding/json" 22 "errors" 23 "fmt" 24 "io" 25 "os" 26 "time" 27 28 "github.com/spf13/cobra" 29 apierrors "k8s.io/apimachinery/pkg/api/errors" 30 "k8s.io/client-go/kubernetes" 31 32 "k8s.io/apimachinery/pkg/util/yaml" 33 "k8s.io/helm/cmd/helm/installer" 34 "k8s.io/helm/pkg/getter" 35 "k8s.io/helm/pkg/helm" 36 "k8s.io/helm/pkg/helm/helmpath" 37 "k8s.io/helm/pkg/helm/portforwarder" 38 "k8s.io/helm/pkg/repo" 39 ) 40 41 const initDesc = ` 42 This command installs Tiller (the Helm server-side component) onto your 43 Kubernetes Cluster and sets up local configuration in $HELM_HOME (default ~/.helm/). 44 45 As with the rest of the Helm commands, 'helm init' discovers Kubernetes clusters 46 by reading $KUBECONFIG (default '~/.kube/config') and using the default context. 47 48 To set up just a local environment, use '--client-only'. That will configure 49 $HELM_HOME, but not attempt to connect to a Kubernetes cluster and install the Tiller 50 deployment. 51 52 When installing Tiller, 'helm init' will attempt to install the latest released 53 version. You can specify an alternative image with '--tiller-image'. For those 54 frequently working on the latest code, the flag '--canary-image' will install 55 the latest pre-release version of Tiller (e.g. the HEAD commit in the GitHub 56 repository on the master branch). 57 58 To dump a manifest containing the Tiller deployment YAML, combine the 59 '--dry-run' and '--debug' flags. 60 ` 61 62 const ( 63 stableRepository = "stable" 64 localRepository = "local" 65 localRepositoryIndexFile = "index.yaml" 66 ) 67 68 var ( 69 stableRepositoryURL = "https://kubernetes-charts.storage.googleapis.com" 70 // This is the IPv4 loopback, not localhost, because we have to force IPv4 71 // for Dockerized Helm: https://github.com/kubernetes/helm/issues/1410 72 localRepositoryURL = "http://127.0.0.1:8879/charts" 73 ) 74 75 type initCmd struct { 76 image string 77 clientOnly bool 78 canary bool 79 upgrade bool 80 namespace string 81 dryRun bool 82 forceUpgrade bool 83 skipRefresh bool 84 out io.Writer 85 client helm.Interface 86 home helmpath.Home 87 opts installer.Options 88 kubeClient kubernetes.Interface 89 serviceAccount string 90 maxHistory int 91 replicas int 92 wait bool 93 } 94 95 func newInitCmd(out io.Writer) *cobra.Command { 96 i := &initCmd{out: out} 97 98 cmd := &cobra.Command{ 99 Use: "init", 100 Short: "initialize Helm on both client and server", 101 Long: initDesc, 102 RunE: func(cmd *cobra.Command, args []string) error { 103 if len(args) != 0 { 104 return errors.New("This command does not accept arguments") 105 } 106 i.namespace = settings.TillerNamespace 107 i.home = settings.Home 108 i.client = ensureHelmClient(i.client) 109 110 return i.run() 111 }, 112 } 113 114 f := cmd.Flags() 115 f.StringVarP(&i.image, "tiller-image", "i", "", "override Tiller image") 116 f.BoolVar(&i.canary, "canary-image", false, "use the canary Tiller image") 117 f.BoolVar(&i.upgrade, "upgrade", false, "upgrade if Tiller is already installed") 118 f.BoolVar(&i.forceUpgrade, "force-upgrade", false, "force upgrade of Tiller to the current helm version") 119 f.BoolVarP(&i.clientOnly, "client-only", "c", false, "if set does not install Tiller") 120 f.BoolVar(&i.dryRun, "dry-run", false, "do not install local or remote") 121 f.BoolVar(&i.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache") 122 f.BoolVar(&i.wait, "wait", false, "block until Tiller is running and ready to receive requests") 123 124 f.BoolVar(&tlsEnable, "tiller-tls", false, "install Tiller with TLS enabled") 125 f.BoolVar(&tlsVerify, "tiller-tls-verify", false, "install Tiller with TLS enabled and to verify remote certificates") 126 f.StringVar(&tlsKeyFile, "tiller-tls-key", "", "path to TLS key file to install with Tiller") 127 f.StringVar(&tlsCertFile, "tiller-tls-cert", "", "path to TLS certificate file to install with Tiller") 128 f.StringVar(&tlsCaCertFile, "tls-ca-cert", "", "path to CA root certificate") 129 130 f.StringVar(&stableRepositoryURL, "stable-repo-url", stableRepositoryURL, "URL for stable repository") 131 f.StringVar(&localRepositoryURL, "local-repo-url", localRepositoryURL, "URL for local repository") 132 133 f.BoolVar(&i.opts.EnableHostNetwork, "net-host", false, "install Tiller with net=host") 134 f.StringVar(&i.serviceAccount, "service-account", "", "name of service account") 135 f.IntVar(&i.maxHistory, "history-max", 0, "limit the maximum number of revisions saved per release. Use 0 for no limit.") 136 f.IntVar(&i.replicas, "replicas", 1, "amount of tiller instances to run on the cluster") 137 138 f.StringVar(&i.opts.NodeSelectors, "node-selectors", "", "labels to specify the node on which Tiller is installed (app=tiller,helm=rocks)") 139 f.VarP(&i.opts.Output, "output", "o", "skip installation and output Tiller's manifest in specified format (json or yaml)") 140 f.StringArrayVar(&i.opts.Values, "override", []string{}, "override values for the Tiller Deployment manifest (can specify multiple or separate values with commas: key1=val1,key2=val2)") 141 142 return cmd 143 } 144 145 // tlsOptions sanitizes the tls flags as well as checks for the existence of required 146 // tls files indicated by those flags, if any. 147 func (i *initCmd) tlsOptions() error { 148 i.opts.EnableTLS = tlsEnable || tlsVerify 149 i.opts.VerifyTLS = tlsVerify 150 151 if i.opts.EnableTLS { 152 missing := func(file string) bool { 153 _, err := os.Stat(file) 154 return os.IsNotExist(err) 155 } 156 if i.opts.TLSKeyFile = tlsKeyFile; i.opts.TLSKeyFile == "" || missing(i.opts.TLSKeyFile) { 157 return errors.New("missing required TLS key file") 158 } 159 if i.opts.TLSCertFile = tlsCertFile; i.opts.TLSCertFile == "" || missing(i.opts.TLSCertFile) { 160 return errors.New("missing required TLS certificate file") 161 } 162 if i.opts.VerifyTLS { 163 if i.opts.TLSCaCertFile = tlsCaCertFile; i.opts.TLSCaCertFile == "" || missing(i.opts.TLSCaCertFile) { 164 return errors.New("missing required TLS CA file") 165 } 166 } 167 } 168 return nil 169 } 170 171 // run initializes local config and installs Tiller to Kubernetes cluster. 172 func (i *initCmd) run() error { 173 if err := i.tlsOptions(); err != nil { 174 return err 175 } 176 i.opts.Namespace = i.namespace 177 i.opts.UseCanary = i.canary 178 i.opts.ImageSpec = i.image 179 i.opts.ForceUpgrade = i.forceUpgrade 180 i.opts.ServiceAccount = i.serviceAccount 181 i.opts.MaxHistory = i.maxHistory 182 i.opts.Replicas = i.replicas 183 184 writeYAMLManifest := func(apiVersion, kind, body string, first, last bool) error { 185 w := i.out 186 if !first { 187 // YAML starting document boundary marker 188 if _, err := fmt.Fprintln(w, "---"); err != nil { 189 return err 190 } 191 } 192 if _, err := fmt.Fprintln(w, "apiVersion:", apiVersion); err != nil { 193 return err 194 } 195 if _, err := fmt.Fprintln(w, "kind:", kind); err != nil { 196 return err 197 } 198 if _, err := fmt.Fprint(w, body); err != nil { 199 return err 200 } 201 if !last { 202 return nil 203 } 204 // YAML ending document boundary marker 205 _, err := fmt.Fprintln(w, "...") 206 return err 207 } 208 if len(i.opts.Output) > 0 { 209 var body string 210 var err error 211 const tm = `{"apiVersion":"extensions/v1beta1","kind":"Deployment",` 212 if body, err = installer.DeploymentManifest(&i.opts); err != nil { 213 return err 214 } 215 switch i.opts.Output.String() { 216 case "json": 217 var out bytes.Buffer 218 jsonb, err := yaml.ToJSON([]byte(body)) 219 if err != nil { 220 return err 221 } 222 buf := bytes.NewBuffer(make([]byte, 0, len(tm)+len(jsonb)-1)) 223 buf.WriteString(tm) 224 // Drop the opening object delimiter ('{'). 225 buf.Write(jsonb[1:]) 226 if err := json.Indent(&out, buf.Bytes(), "", " "); err != nil { 227 return err 228 } 229 if _, err = i.out.Write(out.Bytes()); err != nil { 230 return err 231 } 232 233 return nil 234 case "yaml": 235 if err := writeYAMLManifest("extensions/v1beta1", "Deployment", body, true, false); err != nil { 236 return err 237 } 238 return nil 239 default: 240 return fmt.Errorf("unknown output format: %q", i.opts.Output) 241 } 242 } 243 if settings.Debug { 244 245 var body string 246 var err error 247 248 // write Deployment manifest 249 if body, err = installer.DeploymentManifest(&i.opts); err != nil { 250 return err 251 } 252 if err := writeYAMLManifest("extensions/v1beta1", "Deployment", body, true, false); err != nil { 253 return err 254 } 255 256 // write Service manifest 257 if body, err = installer.ServiceManifest(i.namespace); err != nil { 258 return err 259 } 260 if err := writeYAMLManifest("v1", "Service", body, false, !i.opts.EnableTLS); err != nil { 261 return err 262 } 263 264 // write Secret manifest 265 if i.opts.EnableTLS { 266 if body, err = installer.SecretManifest(&i.opts); err != nil { 267 return err 268 } 269 if err := writeYAMLManifest("v1", "Secret", body, false, true); err != nil { 270 return err 271 } 272 } 273 } 274 275 if i.dryRun { 276 return nil 277 } 278 279 if err := ensureDirectories(i.home, i.out); err != nil { 280 return err 281 } 282 if err := ensureDefaultRepos(i.home, i.out, i.skipRefresh); err != nil { 283 return err 284 } 285 if err := ensureRepoFileFormat(i.home.RepositoryFile(), i.out); err != nil { 286 return err 287 } 288 fmt.Fprintf(i.out, "$HELM_HOME has been configured at %s.\n", settings.Home) 289 290 if !i.clientOnly { 291 if i.kubeClient == nil { 292 _, c, err := getKubeClient(settings.KubeContext) 293 if err != nil { 294 return fmt.Errorf("could not get kubernetes client: %s", err) 295 } 296 i.kubeClient = c 297 } 298 if err := installer.Install(i.kubeClient, &i.opts); err != nil { 299 if !apierrors.IsAlreadyExists(err) { 300 return fmt.Errorf("error installing: %s", err) 301 } 302 if i.upgrade { 303 if err := installer.Upgrade(i.kubeClient, &i.opts); err != nil { 304 return fmt.Errorf("error when upgrading: %s", err) 305 } 306 if err := i.ping(); err != nil { 307 return err 308 } 309 fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been upgraded to the current version.") 310 } else { 311 fmt.Fprintln(i.out, "Warning: Tiller is already installed in the cluster.\n"+ 312 "(Use --client-only to suppress this message, or --upgrade to upgrade Tiller to the current version.)") 313 } 314 } else { 315 fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been installed into your Kubernetes Cluster.\n\n"+ 316 "Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.\n"+ 317 "For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation") 318 } 319 if err := i.ping(); err != nil { 320 return err 321 } 322 } else { 323 fmt.Fprintln(i.out, "Not installing Tiller due to 'client-only' flag having been set") 324 } 325 326 fmt.Fprintln(i.out, "Happy Helming!") 327 return nil 328 } 329 330 func (i *initCmd) ping() error { 331 if i.wait { 332 _, kubeClient, err := getKubeClient(settings.KubeContext) 333 if err != nil { 334 return err 335 } 336 if !watchTillerUntilReady(settings.TillerNamespace, kubeClient, settings.TillerConnectionTimeout) { 337 return fmt.Errorf("tiller was not found. polling deadline exceeded") 338 } 339 340 // establish a connection to Tiller now that we've effectively guaranteed it's available 341 if err := setupConnection(); err != nil { 342 return err 343 } 344 i.client = newClient() 345 if err := i.client.PingTiller(); err != nil { 346 return fmt.Errorf("could not ping Tiller: %s", err) 347 } 348 } 349 350 return nil 351 } 352 353 // ensureDirectories checks to see if $HELM_HOME exists. 354 // 355 // If $HELM_HOME does not exist, this function will create it. 356 func ensureDirectories(home helmpath.Home, out io.Writer) error { 357 configDirectories := []string{ 358 home.String(), 359 home.Repository(), 360 home.Cache(), 361 home.LocalRepository(), 362 home.Plugins(), 363 home.Starters(), 364 home.Archive(), 365 } 366 for _, p := range configDirectories { 367 if fi, err := os.Stat(p); err != nil { 368 fmt.Fprintf(out, "Creating %s \n", p) 369 if err := os.MkdirAll(p, 0755); err != nil { 370 return fmt.Errorf("Could not create %s: %s", p, err) 371 } 372 } else if !fi.IsDir() { 373 return fmt.Errorf("%s must be a directory", p) 374 } 375 } 376 377 return nil 378 } 379 380 func ensureDefaultRepos(home helmpath.Home, out io.Writer, skipRefresh bool) error { 381 repoFile := home.RepositoryFile() 382 if fi, err := os.Stat(repoFile); err != nil { 383 fmt.Fprintf(out, "Creating %s \n", repoFile) 384 f := repo.NewRepoFile() 385 sr, err := initStableRepo(home.CacheIndex(stableRepository), out, skipRefresh, home) 386 if err != nil { 387 return err 388 } 389 lr, err := initLocalRepo(home.LocalRepository(localRepositoryIndexFile), home.CacheIndex("local"), out, home) 390 if err != nil { 391 return err 392 } 393 f.Add(sr) 394 f.Add(lr) 395 if err := f.WriteFile(repoFile, 0644); err != nil { 396 return err 397 } 398 } else if fi.IsDir() { 399 return fmt.Errorf("%s must be a file, not a directory", repoFile) 400 } 401 return nil 402 } 403 404 func initStableRepo(cacheFile string, out io.Writer, skipRefresh bool, home helmpath.Home) (*repo.Entry, error) { 405 fmt.Fprintf(out, "Adding %s repo with URL: %s \n", stableRepository, stableRepositoryURL) 406 c := repo.Entry{ 407 Name: stableRepository, 408 URL: stableRepositoryURL, 409 Cache: cacheFile, 410 } 411 r, err := repo.NewChartRepository(&c, getter.All(settings)) 412 if err != nil { 413 return nil, err 414 } 415 416 if skipRefresh { 417 return &c, nil 418 } 419 420 // In this case, the cacheFile is always absolute. So passing empty string 421 // is safe. 422 if err := r.DownloadIndexFile(""); err != nil { 423 return nil, fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", stableRepositoryURL, err.Error()) 424 } 425 426 return &c, nil 427 } 428 429 func initLocalRepo(indexFile, cacheFile string, out io.Writer, home helmpath.Home) (*repo.Entry, error) { 430 if fi, err := os.Stat(indexFile); err != nil { 431 fmt.Fprintf(out, "Adding %s repo with URL: %s \n", localRepository, localRepositoryURL) 432 i := repo.NewIndexFile() 433 if err := i.WriteFile(indexFile, 0644); err != nil { 434 return nil, err 435 } 436 437 //TODO: take this out and replace with helm update functionality 438 if err := createLink(indexFile, cacheFile, home); err != nil { 439 return nil, err 440 } 441 } else if fi.IsDir() { 442 return nil, fmt.Errorf("%s must be a file, not a directory", indexFile) 443 } 444 445 return &repo.Entry{ 446 Name: localRepository, 447 URL: localRepositoryURL, 448 Cache: cacheFile, 449 }, nil 450 } 451 452 func ensureRepoFileFormat(file string, out io.Writer) error { 453 r, err := repo.LoadRepositoriesFile(file) 454 if err == repo.ErrRepoOutOfDate { 455 fmt.Fprintln(out, "Updating repository file format...") 456 if err := r.WriteFile(file, 0644); err != nil { 457 return err 458 } 459 } 460 461 return nil 462 } 463 464 // watchTillerUntilReady waits for the tiller pod to become available. This is useful in situations where we 465 // want to wait before we call New(). 466 // 467 // Returns true if it exists. If the timeout was reached and it could not find the pod, it returns false. 468 func watchTillerUntilReady(namespace string, client kubernetes.Interface, timeout int64) bool { 469 deadlinePollingChan := time.NewTimer(time.Duration(timeout) * time.Second).C 470 checkTillerPodTicker := time.NewTicker(500 * time.Millisecond) 471 doneChan := make(chan bool) 472 473 defer checkTillerPodTicker.Stop() 474 475 go func() { 476 for range checkTillerPodTicker.C { 477 _, err := portforwarder.GetTillerPodName(client.CoreV1(), namespace) 478 if err == nil { 479 doneChan <- true 480 break 481 } 482 } 483 }() 484 485 for { 486 select { 487 case <-deadlinePollingChan: 488 return false 489 case <-doneChan: 490 return true 491 } 492 } 493 }