github.com/rakanixu/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  }