github.com/koderover/helm@v2.17.0+incompatible/cmd/helm/helm.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 // import "k8s.io/helm/cmd/helm"
    18  
    19  import (
    20  	"fmt"
    21  	"io/ioutil"
    22  	"log"
    23  	"os"
    24  	"strings"
    25  
    26  	"github.com/spf13/cobra"
    27  	"google.golang.org/grpc/grpclog"
    28  	"google.golang.org/grpc/status"
    29  	"k8s.io/client-go/kubernetes"
    30  	"k8s.io/client-go/rest"
    31  
    32  	// Import to initialize client auth plugins.
    33  	_ "k8s.io/client-go/plugin/pkg/client/auth"
    34  
    35  	"k8s.io/helm/pkg/helm"
    36  	helm_env "k8s.io/helm/pkg/helm/environment"
    37  	"k8s.io/helm/pkg/helm/portforwarder"
    38  	"k8s.io/helm/pkg/kube"
    39  	"k8s.io/helm/pkg/repo"
    40  	"k8s.io/helm/pkg/tlsutil"
    41  )
    42  
    43  const (
    44  	bashCompletionFunc = `
    45  __helm_override_flag_list=(--kubeconfig --kube-context --host --tiller-namespace --home)
    46  __helm_override_flags()
    47  {
    48      local ${__helm_override_flag_list[*]##*-} two_word_of of var
    49      for w in "${words[@]}"; do
    50          if [ -n "${two_word_of}" ]; then
    51              eval "${two_word_of##*-}=\"${two_word_of}=\${w}\""
    52              two_word_of=
    53              continue
    54          fi
    55          for of in "${__helm_override_flag_list[@]}"; do
    56              case "${w}" in
    57                  ${of}=*)
    58                      eval "${of##*-}=\"${w}\""
    59                      ;;
    60                  ${of})
    61                      two_word_of="${of}"
    62                      ;;
    63              esac
    64          done
    65      done
    66      for var in "${__helm_override_flag_list[@]##*-}"; do
    67          if eval "test -n \"\$${var}\""; then
    68              eval "echo \${${var}}"
    69          fi
    70      done
    71  }
    72  
    73  __helm_binary_name()
    74  {
    75      local helm_binary
    76      helm_binary="${words[0]}"
    77      __helm_debug "${FUNCNAME[0]}: helm_binary is ${helm_binary}"
    78      echo ${helm_binary}
    79  }
    80  
    81  __helm_list_releases()
    82  {
    83      __helm_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
    84      local out filter
    85      # Use ^ to map from the start of the release name
    86      filter="^${words[c]}"
    87      # Use eval in case helm_binary_name or __helm_override_flags contains a variable (e.g., $HOME/bin/h2)
    88      if out=$(eval $(__helm_binary_name) list $(__helm_override_flags) -a -q -m 1000 ${filter} 2>/dev/null); then
    89          COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
    90      fi
    91  }
    92  
    93  __helm_list_repos()
    94  {
    95      __helm_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
    96      local out oflags
    97      oflags=$(__helm_override_flags)
    98      __helm_debug "${FUNCNAME[0]}: __helm_override_flags are ${oflags}"
    99      # Use eval in case helm_binary_name contains a variable (e.g., $HOME/bin/h2)
   100      if out=$(eval $(__helm_binary_name) repo list ${oflags} 2>/dev/null | tail +2 | cut -f1); then
   101          COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
   102      fi
   103  }
   104  
   105  __helm_list_plugins()
   106  {
   107      __helm_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
   108      local out oflags
   109      oflags=$(__helm_override_flags)
   110      __helm_debug "${FUNCNAME[0]}: __helm_override_flags are ${oflags}"
   111      # Use eval in case helm_binary_name contains a variable (e.g., $HOME/bin/h2)
   112      if out=$(eval $(__helm_binary_name) plugin list ${oflags} 2>/dev/null | tail +2 | cut -f1); then
   113          COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
   114      fi
   115  }
   116  
   117  __helm_custom_func()
   118  {
   119      __helm_debug "${FUNCNAME[0]}: c is $c words[@] is ${words[@]}"
   120      case ${last_command} in
   121          helm_delete | helm_history | helm_status | helm_test |\
   122          helm_upgrade | helm_rollback | helm_get_*)
   123              __helm_list_releases
   124              return
   125              ;;
   126          helm_repo_remove | helm_repo_update)
   127              __helm_list_repos
   128              return
   129              ;;
   130          helm_plugin_remove | helm_plugin_update)
   131              __helm_list_plugins
   132              return
   133              ;;
   134          *)
   135              ;;
   136      esac
   137  }
   138  `
   139  )
   140  
   141  var (
   142  	tillerTunnel *kube.Tunnel
   143  	settings     helm_env.EnvSettings
   144  )
   145  
   146  var globalUsage = `The Kubernetes package manager
   147  
   148  To begin working with Helm, run the 'helm init' command:
   149  
   150  	$ helm init
   151  
   152  This will install Tiller to your running Kubernetes cluster.
   153  It will also set up any necessary local configuration.
   154  
   155  Common actions from this point include:
   156  
   157  - helm search:    Search for charts
   158  - helm fetch:     Download a chart to your local directory to view
   159  - helm install:   Upload the chart to Kubernetes
   160  - helm list:      List releases of charts
   161  
   162  Environment:
   163  
   164  - $HELM_HOME:           Set an alternative location for Helm files. By default, these are stored in ~/.helm
   165  - $HELM_HOST:           Set an alternative Tiller host. The format is host:port
   166  - $HELM_NO_PLUGINS:     Disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins.
   167  - $TILLER_NAMESPACE:    Set an alternative Tiller namespace (default "kube-system")
   168  - $KUBECONFIG:          Set an alternative Kubernetes configuration file (default "~/.kube/config")
   169  - $HELM_TLS_CA_CERT:    Path to TLS CA certificate used to verify the Helm client and Tiller server certificates (default "$HELM_HOME/ca.pem")
   170  - $HELM_TLS_CERT:       Path to TLS client certificate file for authenticating to Tiller (default "$HELM_HOME/cert.pem")
   171  - $HELM_TLS_KEY:        Path to TLS client key file for authenticating to Tiller (default "$HELM_HOME/key.pem")
   172  - $HELM_TLS_ENABLE:     Enable TLS connection between Helm and Tiller (default "false")
   173  - $HELM_TLS_VERIFY:     Enable TLS connection between Helm and Tiller and verify Tiller server certificate (default "false")
   174  - $HELM_TLS_HOSTNAME:   The hostname or IP address used to verify the Tiller server certificate (default "127.0.0.1")
   175  - $HELM_KEY_PASSPHRASE: Set HELM_KEY_PASSPHRASE to the passphrase of your PGP private key. If set, you will not be prompted for the passphrase while signing helm charts
   176  
   177  `
   178  
   179  func checkForExpiredRepos(repofile string) {
   180  
   181  	expiredRepos := []struct {
   182  		name string
   183  		old  string
   184  		new  string
   185  	}{
   186  		{
   187  			name: "stable",
   188  			old:  "kubernetes-charts.storage.googleapis.com",
   189  			new:  "https://charts.helm.sh/stable",
   190  		},
   191  		{
   192  			name: "incubator",
   193  			old:  "kubernetes-charts-incubator.storage.googleapis.com",
   194  			new:  "https://charts.helm.sh/incubator",
   195  		},
   196  	}
   197  
   198  	// parse repo file.
   199  	// Ignore the error because it is okay for a repo file to be unparseable at this
   200  	// stage. Later checks will trap the error and respond accordingly.
   201  	repoFile, err := repo.LoadRepositoriesFile(repofile)
   202  	if err != nil {
   203  		return
   204  	}
   205  
   206  	for _, exp := range expiredRepos {
   207  		r, ok := repoFile.Get(exp.name)
   208  		if !ok {
   209  			return
   210  		}
   211  
   212  		if url := r.URL; strings.Contains(url, exp.old) {
   213  			fmt.Fprintf(
   214  				os.Stderr,
   215  				"WARNING: %q is deprecated for %q and will be deleted Nov. 13, 2020.\nWARNING: You should switch to %q\n",
   216  				exp.old,
   217  				exp.name,
   218  				exp.new,
   219  			)
   220  		}
   221  	}
   222  
   223  }
   224  
   225  func newRootCmd(args []string) *cobra.Command {
   226  	cmd := &cobra.Command{
   227  		Use:          "helm",
   228  		Short:        "The Helm package manager for Kubernetes.",
   229  		Long:         globalUsage,
   230  		SilenceUsage: true,
   231  		PersistentPreRun: func(*cobra.Command, []string) {
   232  			checkForExpiredRepos(settings.Home.RepositoryFile())
   233  			if settings.TLSCaCertFile == helm_env.DefaultTLSCaCert || settings.TLSCaCertFile == "" {
   234  				settings.TLSCaCertFile = settings.Home.TLSCaCert()
   235  			} else {
   236  				settings.TLSCaCertFile = os.ExpandEnv(settings.TLSCaCertFile)
   237  			}
   238  			if settings.TLSCertFile == helm_env.DefaultTLSCert || settings.TLSCertFile == "" {
   239  				settings.TLSCertFile = settings.Home.TLSCert()
   240  			} else {
   241  				settings.TLSCertFile = os.ExpandEnv(settings.TLSCertFile)
   242  			}
   243  			if settings.TLSKeyFile == helm_env.DefaultTLSKeyFile || settings.TLSKeyFile == "" {
   244  				settings.TLSKeyFile = settings.Home.TLSKey()
   245  			} else {
   246  				settings.TLSKeyFile = os.ExpandEnv(settings.TLSKeyFile)
   247  			}
   248  		},
   249  		PersistentPostRun: func(*cobra.Command, []string) {
   250  			teardown()
   251  		},
   252  		BashCompletionFunction: bashCompletionFunc,
   253  	}
   254  	flags := cmd.PersistentFlags()
   255  
   256  	settings.AddFlags(flags)
   257  
   258  	out := cmd.OutOrStdout()
   259  
   260  	cmd.AddCommand(
   261  		// chart commands
   262  		newCreateCmd(out),
   263  		newDependencyCmd(out),
   264  		newFetchCmd(out),
   265  		newInspectCmd(out),
   266  		newLintCmd(out),
   267  		newPackageCmd(out),
   268  		newRepoCmd(out),
   269  		newSearchCmd(out),
   270  		newServeCmd(out),
   271  		newVerifyCmd(out),
   272  
   273  		// release commands
   274  		newDeleteCmd(nil, out),
   275  		newGetCmd(nil, out),
   276  		newHistoryCmd(nil, out),
   277  		newInstallCmd(nil, out),
   278  		newListCmd(nil, out),
   279  		newRollbackCmd(nil, out),
   280  		newStatusCmd(nil, out),
   281  		newUpgradeCmd(nil, out),
   282  
   283  		newReleaseTestCmd(nil, out),
   284  		newResetCmd(nil, out),
   285  		newVersionCmd(nil, out),
   286  
   287  		newCompletionCmd(out),
   288  		newHomeCmd(out),
   289  		newInitCmd(out),
   290  		newPluginCmd(out),
   291  		newTemplateCmd(out),
   292  
   293  		// Hidden documentation generator command: 'helm docs'
   294  		newDocsCmd(out),
   295  
   296  		// Deprecated
   297  		markDeprecated(newRepoUpdateCmd(out), "Use 'helm repo update'\n"),
   298  	)
   299  
   300  	flags.Parse(args)
   301  
   302  	// set defaults from environment
   303  	settings.Init(flags)
   304  
   305  	// Find and add plugins
   306  	loadPlugins(cmd, out)
   307  
   308  	return cmd
   309  }
   310  
   311  func init() {
   312  	// Tell gRPC not to log to console.
   313  	grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags))
   314  }
   315  
   316  func main() {
   317  	cmd := newRootCmd(os.Args[1:])
   318  	if err := cmd.Execute(); err != nil {
   319  		switch e := err.(type) {
   320  		case pluginError:
   321  			os.Exit(e.code)
   322  		default:
   323  			os.Exit(1)
   324  		}
   325  	}
   326  }
   327  
   328  func markDeprecated(cmd *cobra.Command, notice string) *cobra.Command {
   329  	cmd.Deprecated = notice
   330  	return cmd
   331  }
   332  
   333  func setupConnection() error {
   334  	if settings.TillerHost == "" {
   335  		config, client, err := getKubeClient(settings.KubeContext, settings.KubeConfig)
   336  		if err != nil {
   337  			return err
   338  		}
   339  
   340  		tillerTunnel, err = portforwarder.New(settings.TillerNamespace, client, config)
   341  		if err != nil {
   342  			return err
   343  		}
   344  
   345  		settings.TillerHost = fmt.Sprintf("127.0.0.1:%d", tillerTunnel.Local)
   346  		debug("Created tunnel using local port: '%d'\n", tillerTunnel.Local)
   347  	}
   348  
   349  	// Set up the gRPC config.
   350  	debug("SERVER: %q\n", settings.TillerHost)
   351  
   352  	// Plugin support.
   353  	return nil
   354  }
   355  
   356  func teardown() {
   357  	if tillerTunnel != nil {
   358  		tillerTunnel.Close()
   359  	}
   360  }
   361  
   362  func checkArgsLength(argsReceived int, requiredArgs ...string) error {
   363  	expectedNum := len(requiredArgs)
   364  	if argsReceived != expectedNum {
   365  		arg := "arguments"
   366  		if expectedNum == 1 {
   367  			arg = "argument"
   368  		}
   369  		return fmt.Errorf("This command needs %v %s: %s", expectedNum, arg, strings.Join(requiredArgs, ", "))
   370  	}
   371  	return nil
   372  }
   373  
   374  // prettyError unwraps or rewrites certain errors to make them more user-friendly.
   375  func prettyError(err error) error {
   376  	// Add this check can prevent the object creation if err is nil.
   377  	if err == nil {
   378  		return nil
   379  	}
   380  	// If it's grpc's error, make it more user-friendly.
   381  	if s, ok := status.FromError(err); ok {
   382  		return fmt.Errorf(s.Message())
   383  	}
   384  	// Else return the original error.
   385  	return err
   386  }
   387  
   388  // configForContext creates a Kubernetes REST client configuration for a given kubeconfig context.
   389  func configForContext(context string, kubeconfig string) (*rest.Config, error) {
   390  	config, err := kube.GetConfig(context, kubeconfig).ClientConfig()
   391  	if err != nil {
   392  		return nil, fmt.Errorf("could not get Kubernetes config for context %q: %s", context, err)
   393  	}
   394  	return config, nil
   395  }
   396  
   397  // getKubeClient creates a Kubernetes config and client for a given kubeconfig context.
   398  func getKubeClient(context string, kubeconfig string) (*rest.Config, kubernetes.Interface, error) {
   399  	config, err := configForContext(context, kubeconfig)
   400  	if err != nil {
   401  		return nil, nil, err
   402  	}
   403  	client, err := kubernetes.NewForConfig(config)
   404  	if err != nil {
   405  		return nil, nil, fmt.Errorf("could not get Kubernetes client: %s", err)
   406  	}
   407  	return config, client, nil
   408  }
   409  
   410  // ensureHelmClient returns a new helm client impl. if h is not nil.
   411  func ensureHelmClient(h helm.Interface) helm.Interface {
   412  	if h != nil {
   413  		return h
   414  	}
   415  	return newClient()
   416  }
   417  
   418  func newClient() helm.Interface {
   419  	options := []helm.Option{helm.Host(settings.TillerHost), helm.ConnectTimeout(settings.TillerConnectionTimeout)}
   420  
   421  	if settings.TLSVerify || settings.TLSEnable {
   422  		debug("Host=%q, Key=%q, Cert=%q, CA=%q\n", settings.TLSServerName, settings.TLSKeyFile, settings.TLSCertFile, settings.TLSCaCertFile)
   423  		tlsopts := tlsutil.Options{
   424  			ServerName:         settings.TLSServerName,
   425  			KeyFile:            settings.TLSKeyFile,
   426  			CertFile:           settings.TLSCertFile,
   427  			InsecureSkipVerify: true,
   428  		}
   429  		if settings.TLSVerify {
   430  			tlsopts.CaCertFile = settings.TLSCaCertFile
   431  			tlsopts.InsecureSkipVerify = false
   432  		}
   433  		tlscfg, err := tlsutil.ClientConfig(tlsopts)
   434  		if err != nil {
   435  			fmt.Fprintln(os.Stderr, err)
   436  			os.Exit(2)
   437  		}
   438  		options = append(options, helm.WithTLS(tlscfg))
   439  	}
   440  	return helm.NewClient(options...)
   441  }