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