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