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