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