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  }