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