github.com/canthefason/helm@v2.2.1-0.20170221172616-16b043b8d505+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/helmpath"
    30  	"k8s.io/helm/cmd/helm/installer"
    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  	out        io.Writer
    72  	home       helmpath.Home
    73  	kubeClient internalclientset.Interface
    74  }
    75  
    76  func newInitCmd(out io.Writer) *cobra.Command {
    77  	i := &initCmd{
    78  		out: out,
    79  	}
    80  
    81  	cmd := &cobra.Command{
    82  		Use:   "init",
    83  		Short: "initialize Helm on both client and server",
    84  		Long:  initDesc,
    85  		RunE: func(cmd *cobra.Command, args []string) error {
    86  			if len(args) != 0 {
    87  				return errors.New("This command does not accept arguments")
    88  			}
    89  			i.namespace = tillerNamespace
    90  			i.home = helmpath.Home(homePath())
    91  			return i.run()
    92  		},
    93  	}
    94  
    95  	f := cmd.Flags()
    96  	f.StringVarP(&i.image, "tiller-image", "i", "", "override tiller image")
    97  	f.BoolVar(&i.canary, "canary-image", false, "use the canary tiller image")
    98  	f.BoolVar(&i.upgrade, "upgrade", false, "upgrade if tiller is already installed")
    99  	f.BoolVarP(&i.clientOnly, "client-only", "c", false, "if set does not install tiller")
   100  	f.BoolVar(&i.dryRun, "dry-run", false, "do not install local or remote")
   101  
   102  	return cmd
   103  }
   104  
   105  // runInit initializes local config and installs tiller to Kubernetes Cluster
   106  func (i *initCmd) run() error {
   107  	if flagDebug {
   108  		dm, err := installer.DeploymentManifest(i.namespace, i.image, i.canary)
   109  		if err != nil {
   110  			return err
   111  		}
   112  		fm := fmt.Sprintf("apiVersion: extensions/v1beta1\nkind: Deployment\n%s", dm)
   113  		fmt.Fprintln(i.out, fm)
   114  
   115  		sm, err := installer.ServiceManifest(i.namespace)
   116  		if err != nil {
   117  			return err
   118  		}
   119  		fm = fmt.Sprintf("apiVersion: v1\nkind: Service\n%s", sm)
   120  		fmt.Fprintln(i.out, fm)
   121  	}
   122  
   123  	if i.dryRun {
   124  		return nil
   125  	}
   126  
   127  	if err := ensureDirectories(i.home, i.out); err != nil {
   128  		return err
   129  	}
   130  	if err := ensureDefaultRepos(i.home, i.out); err != nil {
   131  		return err
   132  	}
   133  	if err := ensureRepoFileFormat(i.home.RepositoryFile(), i.out); err != nil {
   134  		return err
   135  	}
   136  	fmt.Fprintf(i.out, "$HELM_HOME has been configured at %s.\n", helmHome)
   137  
   138  	if !i.clientOnly {
   139  		if i.kubeClient == nil {
   140  			_, c, err := getKubeClient(kubeContext)
   141  			if err != nil {
   142  				return fmt.Errorf("could not get kubernetes client: %s", err)
   143  			}
   144  			i.kubeClient = c
   145  		}
   146  		if err := installer.Install(i.kubeClient, i.namespace, i.image, i.canary, flagDebug); err != nil {
   147  			if !kerrors.IsAlreadyExists(err) {
   148  				return fmt.Errorf("error installing: %s", err)
   149  			}
   150  			if i.upgrade {
   151  				if err := installer.Upgrade(i.kubeClient, i.namespace, i.image, i.canary); err != nil {
   152  					return fmt.Errorf("error when upgrading: %s", err)
   153  				}
   154  				fmt.Fprintln(i.out, "\nTiller (the helm server side component) has been upgraded to the current version.")
   155  			} else {
   156  				fmt.Fprintln(i.out, "Warning: Tiller is already installed in the cluster.\n"+
   157  					"(Use --client-only to suppress this message, or --upgrade to upgrade Tiller to the current version.)")
   158  			}
   159  		} else {
   160  			fmt.Fprintln(i.out, "\nTiller (the helm server side component) has been installed into your Kubernetes Cluster.")
   161  		}
   162  	} else {
   163  		fmt.Fprintln(i.out, "Not installing tiller due to 'client-only' flag having been set")
   164  	}
   165  
   166  	fmt.Fprintln(i.out, "Happy Helming!")
   167  	return nil
   168  }
   169  
   170  // ensureDirectories checks to see if $HELM_HOME exists
   171  //
   172  // If $HELM_HOME does not exist, this function will create it.
   173  func ensureDirectories(home helmpath.Home, out io.Writer) error {
   174  	configDirectories := []string{
   175  		home.String(),
   176  		home.Repository(),
   177  		home.Cache(),
   178  		home.LocalRepository(),
   179  		home.Plugins(),
   180  		home.Starters(),
   181  	}
   182  	for _, p := range configDirectories {
   183  		if fi, err := os.Stat(p); err != nil {
   184  			fmt.Fprintf(out, "Creating %s \n", p)
   185  			if err := os.MkdirAll(p, 0755); err != nil {
   186  				return fmt.Errorf("Could not create %s: %s", p, err)
   187  			}
   188  		} else if !fi.IsDir() {
   189  			return fmt.Errorf("%s must be a directory", p)
   190  		}
   191  	}
   192  
   193  	return nil
   194  }
   195  
   196  func ensureDefaultRepos(home helmpath.Home, out io.Writer) error {
   197  	repoFile := home.RepositoryFile()
   198  	if fi, err := os.Stat(repoFile); err != nil {
   199  		fmt.Fprintf(out, "Creating %s \n", repoFile)
   200  		f := repo.NewRepoFile()
   201  		sr, err := initStableRepo(home.CacheIndex(stableRepository))
   202  		if err != nil {
   203  			return err
   204  		}
   205  		lr, err := initLocalRepo(home.LocalRepository(localRepoIndexFilePath), home.CacheIndex("local"))
   206  		if err != nil {
   207  			return err
   208  		}
   209  		f.Add(sr)
   210  		f.Add(lr)
   211  		if err := f.WriteFile(repoFile, 0644); err != nil {
   212  			return err
   213  		}
   214  	} else if fi.IsDir() {
   215  		return fmt.Errorf("%s must be a file, not a directory", repoFile)
   216  	}
   217  	return nil
   218  }
   219  
   220  func initStableRepo(cacheFile string) (*repo.Entry, error) {
   221  	c := repo.Entry{
   222  		Name:  stableRepository,
   223  		URL:   stableRepositoryURL,
   224  		Cache: cacheFile,
   225  	}
   226  	r, err := repo.NewChartRepository(&c)
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	// In this case, the cacheFile is always absolute. So passing empty string
   232  	// is safe.
   233  	if err := r.DownloadIndexFile(""); err != nil {
   234  		return nil, fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", stableRepositoryURL, err.Error())
   235  	}
   236  
   237  	return &c, nil
   238  }
   239  
   240  func initLocalRepo(indexFile, cacheFile string) (*repo.Entry, error) {
   241  	if fi, err := os.Stat(indexFile); err != nil {
   242  		i := repo.NewIndexFile()
   243  		if err := i.WriteFile(indexFile, 0644); err != nil {
   244  			return nil, err
   245  		}
   246  
   247  		//TODO: take this out and replace with helm update functionality
   248  		os.Symlink(indexFile, cacheFile)
   249  	} else if fi.IsDir() {
   250  		return nil, fmt.Errorf("%s must be a file, not a directory", indexFile)
   251  	}
   252  
   253  	return &repo.Entry{
   254  		Name:  localRepository,
   255  		URL:   localRepositoryURL,
   256  		Cache: cacheFile,
   257  	}, nil
   258  }
   259  
   260  func ensureRepoFileFormat(file string, out io.Writer) error {
   261  	r, err := repo.LoadRepositoriesFile(file)
   262  	if err == repo.ErrRepoOutOfDate {
   263  		fmt.Fprintln(out, "Updating repository file format...")
   264  		if err := r.WriteFile(file, 0644); err != nil {
   265  			return err
   266  		}
   267  	}
   268  
   269  	return nil
   270  }