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