github.com/sdbaiguanghe/helm@v2.16.7+incompatible/cmd/helm/init.go (about)

     1  /*
     2  Copyright The Helm Authors.
     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  	"time"
    27  
    28  	"github.com/spf13/cobra"
    29  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    30  	"k8s.io/client-go/kubernetes"
    31  
    32  	"k8s.io/apimachinery/pkg/util/yaml"
    33  	"k8s.io/helm/cmd/helm/installer"
    34  	"k8s.io/helm/pkg/helm"
    35  	"k8s.io/helm/pkg/helm/helmpath"
    36  	"k8s.io/helm/pkg/helm/portforwarder"
    37  	"k8s.io/helm/pkg/version"
    38  )
    39  
    40  const initDesc = `
    41  This command installs Tiller (the Helm server-side component) onto your
    42  Kubernetes Cluster and sets up local configuration in $HELM_HOME (default ~/.helm/).
    43  
    44  As with the rest of the Helm commands, 'helm init' discovers Kubernetes clusters
    45  by reading $KUBECONFIG (default '~/.kube/config') and using the default context.
    46  
    47  To set up just a local environment, use '--client-only'. That will configure
    48  $HELM_HOME, but not attempt to connect to a Kubernetes cluster and install the Tiller
    49  deployment.
    50  
    51  When installing Tiller, 'helm init' will attempt to install the latest released
    52  version. You can specify an alternative image with '--tiller-image'. For those
    53  frequently working on the latest code, the flag '--canary-image' will install
    54  the latest pre-release version of Tiller (e.g. the HEAD commit in the GitHub
    55  repository on the master branch).
    56  
    57  To dump a manifest containing the Tiller deployment YAML, combine the
    58  '--dry-run' and '--debug' flags.
    59  `
    60  
    61  var (
    62  	stableRepositoryURL = "https://kubernetes-charts.storage.googleapis.com"
    63  	// This is the IPv4 loopback, not localhost, because we have to force IPv4
    64  	// for Dockerized Helm: https://github.com/kubernetes/helm/issues/1410
    65  	localRepositoryURL = "http://127.0.0.1:8879/charts"
    66  	tlsServerName      string // overrides the server name used to verify the hostname on the returned certificates from the server.
    67  	tlsCaCertFile      string // path to TLS CA certificate file
    68  	tlsCertFile        string // path to TLS certificate file
    69  	tlsKeyFile         string // path to TLS key file
    70  	tlsVerify          bool   // enable TLS and verify remote certificates
    71  	tlsEnable          bool   // enable TLS
    72  )
    73  
    74  type initCmd struct {
    75  	image          string
    76  	clientOnly     bool
    77  	canary         bool
    78  	upgrade        bool
    79  	namespace      string
    80  	dryRun         bool
    81  	forceUpgrade   bool
    82  	skipRefresh    bool
    83  	out            io.Writer
    84  	client         helm.Interface
    85  	home           helmpath.Home
    86  	opts           installer.Options
    87  	kubeClient     kubernetes.Interface
    88  	serviceAccount string
    89  	maxHistory     int
    90  	replicas       int
    91  	wait           bool
    92  }
    93  
    94  func newInitCmd(out io.Writer) *cobra.Command {
    95  	i := &initCmd{out: out}
    96  
    97  	cmd := &cobra.Command{
    98  		Use:   "init",
    99  		Short: "Initialize Helm on both client and server",
   100  		Long:  initDesc,
   101  		RunE: func(cmd *cobra.Command, args []string) error {
   102  			if len(args) != 0 {
   103  				return errors.New("This command does not accept arguments")
   104  			}
   105  			i.namespace = settings.TillerNamespace
   106  			i.home = settings.Home
   107  			i.client = ensureHelmClient(i.client)
   108  
   109  			return i.run()
   110  		},
   111  	}
   112  
   113  	f := cmd.Flags()
   114  	f.StringVarP(&i.image, "tiller-image", "i", "", "Override Tiller image")
   115  	f.BoolVar(&i.canary, "canary-image", false, "Use the canary Tiller image")
   116  	f.BoolVar(&i.upgrade, "upgrade", false, "Upgrade if Tiller is already installed")
   117  	f.BoolVar(&i.forceUpgrade, "force-upgrade", false, "Force upgrade of Tiller to the current helm version")
   118  	f.BoolVarP(&i.clientOnly, "client-only", "c", false, "If set does not install Tiller")
   119  	f.BoolVar(&i.dryRun, "dry-run", false, "Do not install local or remote")
   120  	f.BoolVar(&i.skipRefresh, "skip-refresh", false, "Do not refresh (download) the local repository cache")
   121  	f.BoolVar(&i.wait, "wait", false, "Block until Tiller is running and ready to receive requests")
   122  
   123  	// TODO: replace TLS flags with pkg/helm/environment.AddFlagsTLS() in Helm 3
   124  	//
   125  	// NOTE (bacongobbler): we can't do this in Helm 2 because the flag names differ, and `helm init --tls-ca-cert`
   126  	// doesn't conform with the rest of the TLS flag names (should be --tiller-tls-ca-cert in Helm 3)
   127  	f.BoolVar(&tlsEnable, "tiller-tls", false, "Install Tiller with TLS enabled")
   128  	f.BoolVar(&tlsVerify, "tiller-tls-verify", false, "Install Tiller with TLS enabled and to verify remote certificates")
   129  	f.StringVar(&tlsKeyFile, "tiller-tls-key", "", "Path to TLS key file to install with Tiller")
   130  	f.StringVar(&tlsCertFile, "tiller-tls-cert", "", "Path to TLS certificate file to install with Tiller")
   131  	f.StringVar(&tlsCaCertFile, "tls-ca-cert", "", "Path to CA root certificate")
   132  	f.StringVar(&tlsServerName, "tiller-tls-hostname", settings.TillerHost, "The server name used to verify the hostname on the returned certificates from Tiller")
   133  
   134  	f.StringVar(&stableRepositoryURL, "stable-repo-url", stableRepositoryURL, "URL for stable repository")
   135  	f.StringVar(&localRepositoryURL, "local-repo-url", localRepositoryURL, "URL for local repository")
   136  
   137  	f.BoolVar(&i.opts.EnableHostNetwork, "net-host", false, "Install Tiller with net=host")
   138  	f.StringVar(&i.serviceAccount, "service-account", "", "Name of service account")
   139  	f.IntVar(&i.maxHistory, "history-max", 0, "Limit the maximum number of revisions saved per release. Use 0 for no limit.")
   140  	f.IntVar(&i.replicas, "replicas", 1, "Amount of tiller instances to run on the cluster")
   141  
   142  	f.StringVar(&i.opts.NodeSelectors, "node-selectors", "", "Labels to specify the node on which Tiller is installed (app=tiller,helm=rocks)")
   143  	f.VarP(&i.opts.Output, "output", "o", "Skip installation and output Tiller's manifest in specified format (json or yaml)")
   144  	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)")
   145  	f.BoolVar(&i.opts.AutoMountServiceAccountToken, "automount-service-account-token", true, "Auto-mount the given service account to tiller")
   146  
   147  	return cmd
   148  }
   149  
   150  // tlsOptions sanitizes the tls flags as well as checks for the existence of required
   151  // tls files indicated by those flags, if any.
   152  func (i *initCmd) tlsOptions() error {
   153  	i.opts.EnableTLS = tlsEnable || tlsVerify
   154  	i.opts.VerifyTLS = tlsVerify
   155  
   156  	if i.opts.EnableTLS {
   157  		missing := func(file string) bool {
   158  			_, err := os.Stat(file)
   159  			return os.IsNotExist(err)
   160  		}
   161  		if i.opts.TLSKeyFile = tlsKeyFile; i.opts.TLSKeyFile == "" || missing(i.opts.TLSKeyFile) {
   162  			return errors.New("missing required TLS key file")
   163  		}
   164  		if i.opts.TLSCertFile = tlsCertFile; i.opts.TLSCertFile == "" || missing(i.opts.TLSCertFile) {
   165  			return errors.New("missing required TLS certificate file")
   166  		}
   167  		if i.opts.VerifyTLS {
   168  			if i.opts.TLSCaCertFile = tlsCaCertFile; i.opts.TLSCaCertFile == "" || missing(i.opts.TLSCaCertFile) {
   169  				return errors.New("missing required TLS CA file")
   170  			}
   171  		}
   172  
   173  		// FIXME: remove once we use pkg/helm/environment.AddFlagsTLS() in Helm 3
   174  		settings.TLSEnable = tlsEnable
   175  		settings.TLSVerify = tlsVerify
   176  		settings.TLSServerName = tlsServerName
   177  		settings.TLSCaCertFile = tlsCaCertFile
   178  		settings.TLSCertFile = tlsCertFile
   179  		settings.TLSKeyFile = tlsKeyFile
   180  	}
   181  	return nil
   182  }
   183  
   184  // run initializes local config and installs Tiller to Kubernetes cluster.
   185  func (i *initCmd) run() error {
   186  	if err := i.tlsOptions(); err != nil {
   187  		return err
   188  	}
   189  	i.opts.Namespace = i.namespace
   190  	i.opts.UseCanary = i.canary
   191  	i.opts.ImageSpec = i.image
   192  	i.opts.ForceUpgrade = i.forceUpgrade
   193  	i.opts.ServiceAccount = i.serviceAccount
   194  	i.opts.MaxHistory = i.maxHistory
   195  	i.opts.Replicas = i.replicas
   196  
   197  	writeYAMLManifests := func(manifests []string) error {
   198  		w := i.out
   199  		for _, manifest := range manifests {
   200  			if _, err := fmt.Fprintln(w, "---"); err != nil {
   201  				return err
   202  			}
   203  
   204  			if _, err := fmt.Fprintln(w, manifest); err != nil {
   205  				return err
   206  			}
   207  		}
   208  
   209  		// YAML ending document boundary marker
   210  		_, err := fmt.Fprintln(w, "...")
   211  		return err
   212  	}
   213  	if len(i.opts.Output) > 0 {
   214  		var manifests []string
   215  		var err error
   216  		if manifests, err = installer.TillerManifests(&i.opts); err != nil {
   217  			return err
   218  		}
   219  		switch i.opts.Output.String() {
   220  		case "json":
   221  			for _, manifest := range manifests {
   222  				var out bytes.Buffer
   223  				jsonb, err := yaml.ToJSON([]byte(manifest))
   224  				if err != nil {
   225  					return err
   226  				}
   227  				buf := bytes.NewBuffer(jsonb)
   228  				if err := json.Indent(&out, buf.Bytes(), "", "    "); err != nil {
   229  					return err
   230  				}
   231  				if _, err = i.out.Write(out.Bytes()); err != nil {
   232  					return err
   233  				}
   234  				fmt.Fprint(i.out, "\n")
   235  			}
   236  			return nil
   237  		case "yaml":
   238  			return writeYAMLManifests(manifests)
   239  		default:
   240  			return fmt.Errorf("unknown output format: %q", i.opts.Output)
   241  		}
   242  	}
   243  	if settings.Debug {
   244  		var manifests []string
   245  		var err error
   246  
   247  		// write Tiller manifests
   248  		if manifests, err = installer.TillerManifests(&i.opts); err != nil {
   249  			return err
   250  		}
   251  
   252  		if err = writeYAMLManifests(manifests); err != nil {
   253  			return err
   254  		}
   255  	}
   256  
   257  	if i.dryRun {
   258  		return nil
   259  	}
   260  
   261  	if err := installer.Initialize(i.home, i.out, i.skipRefresh, settings, stableRepositoryURL, localRepositoryURL); err != nil {
   262  		return fmt.Errorf("error initializing: %s", err)
   263  	}
   264  	fmt.Fprintf(i.out, "$HELM_HOME has been configured at %s.\n", settings.Home)
   265  
   266  	if !i.clientOnly {
   267  		if i.kubeClient == nil {
   268  			_, c, err := getKubeClient(settings.KubeContext, settings.KubeConfig)
   269  			if err != nil {
   270  				return fmt.Errorf("could not get kubernetes client: %s", err)
   271  			}
   272  			i.kubeClient = c
   273  		}
   274  		if err := installer.Install(i.kubeClient, &i.opts); err != nil {
   275  			if !apierrors.IsAlreadyExists(err) {
   276  				return fmt.Errorf("error installing: %s", err)
   277  			}
   278  			if i.upgrade {
   279  				if err := installer.Upgrade(i.kubeClient, &i.opts); err != nil {
   280  					return fmt.Errorf("error when upgrading: %s", err)
   281  				}
   282  				if err := i.ping(i.opts.SelectImage()); err != nil {
   283  					return err
   284  				}
   285  				fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been updated to", i.opts.SelectImage(), ".")
   286  			} else {
   287  				debug("The error received while trying to init: %s", err)
   288  				fmt.Fprintln(i.out, "Warning: Tiller is already installed in the cluster.\n"+
   289  					"(Use --client-only to suppress this message, or --upgrade to upgrade Tiller to the current version.)")
   290  			}
   291  		} else {
   292  			fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been installed into your Kubernetes Cluster.")
   293  			if !tlsVerify {
   294  				fmt.Fprintln(i.out, "\nPlease note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.\n"+
   295  					"To prevent this, run `helm init` with the --tiller-tls-verify flag.\n"+
   296  					"For more information on securing your installation see: https://v2.helm.sh/docs/securing_installation/")
   297  			}
   298  		}
   299  		if err := i.ping(i.opts.SelectImage()); err != nil {
   300  			return err
   301  		}
   302  	} else {
   303  		fmt.Fprintln(i.out, "Not installing Tiller due to 'client-only' flag having been set")
   304  	}
   305  
   306  	needsDefaultImage := !i.clientOnly && !i.opts.UseCanary && len(i.opts.ImageSpec) == 0 && version.BuildMetadata == "unreleased"
   307  	if needsDefaultImage {
   308  		fmt.Fprintf(i.out, "\nWarning: You appear to be using an unreleased version of Helm. Please either use the\n"+
   309  			"--canary-image flag, or specify your desired tiller version with --tiller-image.\n\n"+
   310  			"Ex:\n"+
   311  			"$ helm init --tiller-image gcr.io/kubernetes-helm/tiller:v2.8.2\n\n")
   312  	}
   313  
   314  	return nil
   315  }
   316  
   317  func (i *initCmd) ping(image string) error {
   318  	if i.wait {
   319  		_, kubeClient, err := getKubeClient(settings.KubeContext, settings.KubeConfig)
   320  		if err != nil {
   321  			return err
   322  		}
   323  		if !watchTillerUntilReady(settings.TillerNamespace, kubeClient, settings.TillerConnectionTimeout, image) {
   324  			return fmt.Errorf("tiller was not found. polling deadline exceeded")
   325  		}
   326  
   327  		// establish a connection to Tiller now that we've effectively guaranteed it's available
   328  		if err := setupConnection(); err != nil {
   329  			return err
   330  		}
   331  		i.client = newClient()
   332  		if err := i.client.PingTiller(); err != nil {
   333  			return fmt.Errorf("could not ping Tiller: %s", err)
   334  		}
   335  	}
   336  
   337  	return nil
   338  }
   339  
   340  // watchTillerUntilReady waits for the tiller pod to become available. This is useful in situations where we
   341  // want to wait before we call New().
   342  //
   343  // Returns true if it exists. If the timeout was reached and it could not find the pod, it returns false.
   344  func watchTillerUntilReady(namespace string, client kubernetes.Interface, timeout int64, newImage string) bool {
   345  	deadlinePollingChan := time.NewTimer(time.Duration(timeout) * time.Second).C
   346  	checkTillerPodTicker := time.NewTicker(500 * time.Millisecond)
   347  	doneChan := make(chan bool)
   348  
   349  	defer checkTillerPodTicker.Stop()
   350  
   351  	go func() {
   352  		for range checkTillerPodTicker.C {
   353  			image, err := portforwarder.GetTillerPodImage(client.CoreV1(), namespace)
   354  			if err == nil && image == newImage {
   355  				doneChan <- true
   356  				break
   357  			}
   358  		}
   359  	}()
   360  
   361  	for {
   362  		select {
   363  		case <-deadlinePollingChan:
   364  			return false
   365  		case <-doneChan:
   366  			return true
   367  		}
   368  	}
   369  }