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