github.com/sgoings/helm@v2.0.0-alpha.2.0.20170406211108-734e92851ac3+incompatible/cmd/helm/helm.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 // import "k8s.io/helm/cmd/helm"
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"io/ioutil"
    24  	"log"
    25  	"os"
    26  	"path/filepath"
    27  	"strings"
    28  
    29  	"github.com/spf13/cobra"
    30  	"google.golang.org/grpc"
    31  	"google.golang.org/grpc/grpclog"
    32  	"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
    33  	"k8s.io/kubernetes/pkg/client/restclient"
    34  
    35  	"k8s.io/helm/pkg/helm"
    36  	"k8s.io/helm/pkg/helm/helmpath"
    37  	"k8s.io/helm/pkg/helm/portforwarder"
    38  	"k8s.io/helm/pkg/kube"
    39  	"k8s.io/helm/pkg/tiller/environment"
    40  	"k8s.io/helm/pkg/tlsutil"
    41  )
    42  
    43  const (
    44  	localRepoIndexFilePath = "index.yaml"
    45  	homeEnvVar             = "HELM_HOME"
    46  	hostEnvVar             = "HELM_HOST"
    47  	tillerNamespaceEnvVar  = "TILLER_NAMESPACE"
    48  )
    49  
    50  var (
    51  	tlsCaCertFile string // path to TLS CA certificate file
    52  	tlsCertFile   string // path to TLS certificate file
    53  	tlsKeyFile    string // path to TLS key file
    54  	tlsVerify     bool   // enable TLS and verify remote certificates
    55  	tlsEnable     bool   // enable TLS
    56  )
    57  
    58  var (
    59  	helmHome        string
    60  	tillerHost      string
    61  	tillerNamespace string
    62  	kubeContext     string
    63  	// TODO refactor out this global var
    64  	tillerTunnel *kube.Tunnel
    65  )
    66  
    67  // flagDebug is a signal that the user wants additional output.
    68  var flagDebug bool
    69  
    70  var globalUsage = `The Kubernetes package manager
    71  
    72  To begin working with Helm, run the 'helm init' command:
    73  
    74  	$ helm init
    75  
    76  This will install Tiller to your running Kubernetes cluster.
    77  It will also set up any necessary local configuration.
    78  
    79  Common actions from this point include:
    80  
    81  - helm search:    search for charts
    82  - helm fetch:     download a chart to your local directory to view
    83  - helm install:   upload the chart to Kubernetes
    84  - helm list:      list releases of charts
    85  
    86  Environment:
    87    $HELM_HOME          set an alternative location for Helm files. By default, these are stored in ~/.helm
    88    $HELM_HOST          set an alternative Tiller host. The format is host:port
    89    $HELM_NO_PLUGINS    disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins.
    90    $TILLER_NAMESPACE   set an alternative Tiller namespace (default "kube-namespace")
    91    $KUBECONFIG         set an alternative Kubernetes configuration file (default "~/.kube/config")
    92  `
    93  
    94  func newRootCmd(out io.Writer) *cobra.Command {
    95  	cmd := &cobra.Command{
    96  		Use:          "helm",
    97  		Short:        "The Helm package manager for Kubernetes.",
    98  		Long:         globalUsage,
    99  		SilenceUsage: true,
   100  		PersistentPreRun: func(cmd *cobra.Command, args []string) {
   101  			tlsCaCertFile = os.ExpandEnv(tlsCaCertFile)
   102  			tlsCertFile = os.ExpandEnv(tlsCertFile)
   103  			tlsKeyFile = os.ExpandEnv(tlsKeyFile)
   104  		},
   105  		PersistentPostRun: func(cmd *cobra.Command, args []string) {
   106  			teardown()
   107  		},
   108  	}
   109  	p := cmd.PersistentFlags()
   110  	p.StringVar(&helmHome, "home", defaultHelmHome(), "location of your Helm config. Overrides $HELM_HOME")
   111  	p.StringVar(&tillerHost, "host", defaultHelmHost(), "address of tiller. Overrides $HELM_HOST")
   112  	p.StringVar(&kubeContext, "kube-context", "", "name of the kubeconfig context to use")
   113  	p.BoolVar(&flagDebug, "debug", false, "enable verbose output")
   114  	p.StringVar(&tillerNamespace, "tiller-namespace", defaultTillerNamespace(), "namespace of tiller")
   115  
   116  	cmd.AddCommand(
   117  		// chart commands
   118  		newCreateCmd(out),
   119  		newDependencyCmd(out),
   120  		newFetchCmd(out),
   121  		newInspectCmd(out),
   122  		newLintCmd(out),
   123  		newPackageCmd(out),
   124  		newRepoCmd(out),
   125  		newSearchCmd(out),
   126  		newServeCmd(out),
   127  		newVerifyCmd(out),
   128  
   129  		// release commands
   130  		addFlagsTLS(newDeleteCmd(nil, out)),
   131  		addFlagsTLS(newGetCmd(nil, out)),
   132  		addFlagsTLS(newHistoryCmd(nil, out)),
   133  		addFlagsTLS(newInstallCmd(nil, out)),
   134  		addFlagsTLS(newListCmd(nil, out)),
   135  		addFlagsTLS(newRollbackCmd(nil, out)),
   136  		addFlagsTLS(newStatusCmd(nil, out)),
   137  		addFlagsTLS(newUpgradeCmd(nil, out)),
   138  
   139  		addFlagsTLS(newReleaseTestCmd(nil, out)),
   140  		addFlagsTLS(newResetCmd(nil, out)),
   141  		addFlagsTLS(newVersionCmd(nil, out)),
   142  		newCompletionCmd(out),
   143  		newHomeCmd(out),
   144  		newInitCmd(out),
   145  		newResetCmd(nil, out),
   146  		newVersionCmd(nil, out),
   147  		newReleaseTestCmd(nil, out),
   148  		newPluginCmd(out),
   149  
   150  		// Hidden documentation generator command: 'helm docs'
   151  		newDocsCmd(out),
   152  
   153  		// Deprecated
   154  		markDeprecated(newRepoUpdateCmd(out), "use 'helm repo update'\n"),
   155  	)
   156  
   157  	// Find and add plugins
   158  	loadPlugins(cmd, helmpath.Home(homePath()), out)
   159  
   160  	return cmd
   161  }
   162  
   163  func init() {
   164  	// Tell gRPC not to log to console.
   165  	grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags))
   166  }
   167  
   168  func main() {
   169  	cmd := newRootCmd(os.Stdout)
   170  	if err := cmd.Execute(); err != nil {
   171  		os.Exit(1)
   172  	}
   173  }
   174  
   175  func markDeprecated(cmd *cobra.Command, notice string) *cobra.Command {
   176  	cmd.Deprecated = notice
   177  	return cmd
   178  }
   179  
   180  func setupConnection(c *cobra.Command, args []string) error {
   181  	if tillerHost == "" {
   182  		config, client, err := getKubeClient(kubeContext)
   183  		if err != nil {
   184  			return err
   185  		}
   186  
   187  		tunnel, err := portforwarder.New(tillerNamespace, client, config)
   188  		if err != nil {
   189  			return err
   190  		}
   191  
   192  		tillerHost = fmt.Sprintf("localhost:%d", tunnel.Local)
   193  		if flagDebug {
   194  			fmt.Printf("Created tunnel using local port: '%d'\n", tunnel.Local)
   195  		}
   196  	}
   197  
   198  	// Set up the gRPC config.
   199  	if flagDebug {
   200  		fmt.Printf("SERVER: %q\n", tillerHost)
   201  	}
   202  	// Plugin support.
   203  	return nil
   204  }
   205  
   206  func teardown() {
   207  	if tillerTunnel != nil {
   208  		tillerTunnel.Close()
   209  	}
   210  }
   211  
   212  func checkArgsLength(argsReceived int, requiredArgs ...string) error {
   213  	expectedNum := len(requiredArgs)
   214  	if argsReceived != expectedNum {
   215  		arg := "arguments"
   216  		if expectedNum == 1 {
   217  			arg = "argument"
   218  		}
   219  		return fmt.Errorf("This command needs %v %s: %s", expectedNum, arg, strings.Join(requiredArgs, ", "))
   220  	}
   221  	return nil
   222  }
   223  
   224  // prettyError unwraps or rewrites certain errors to make them more user-friendly.
   225  func prettyError(err error) error {
   226  	if err == nil {
   227  		return nil
   228  	}
   229  	// This is ridiculous. Why is 'grpc.rpcError' not exported? The least they
   230  	// could do is throw an interface on the lib that would let us get back
   231  	// the desc. Instead, we have to pass ALL errors through this.
   232  	return errors.New(grpc.ErrorDesc(err))
   233  }
   234  
   235  func defaultHelmHome() string {
   236  	if home := os.Getenv(homeEnvVar); home != "" {
   237  		return home
   238  	}
   239  	return filepath.Join(os.Getenv("HOME"), ".helm")
   240  }
   241  
   242  func homePath() string {
   243  	s := os.ExpandEnv(helmHome)
   244  	os.Setenv(homeEnvVar, s)
   245  	return s
   246  }
   247  
   248  func defaultHelmHost() string {
   249  	return os.Getenv(hostEnvVar)
   250  }
   251  
   252  func defaultTillerNamespace() string {
   253  	if ns := os.Getenv(tillerNamespaceEnvVar); ns != "" {
   254  		return ns
   255  	}
   256  	return environment.DefaultTillerNamespace
   257  }
   258  
   259  // getKubeClient is a convenience method for creating kubernetes config and client
   260  // for a given kubeconfig context
   261  func getKubeClient(context string) (*restclient.Config, *internalclientset.Clientset, error) {
   262  	config, err := kube.GetConfig(context).ClientConfig()
   263  	if err != nil {
   264  		return nil, nil, fmt.Errorf("could not get kubernetes config for context '%s': %s", context, err)
   265  	}
   266  	client, err := internalclientset.NewForConfig(config)
   267  	if err != nil {
   268  		return nil, nil, fmt.Errorf("could not get kubernetes client: %s", err)
   269  	}
   270  	return config, client, nil
   271  }
   272  
   273  // getKubeCmd is a convenience method for creating kubernetes cmd client
   274  // for a given kubeconfig context
   275  func getKubeCmd(context string) *kube.Client {
   276  	return kube.New(kube.GetConfig(context))
   277  }
   278  
   279  // ensureHelmClient returns a new helm client impl. if h is not nil.
   280  func ensureHelmClient(h helm.Interface) helm.Interface {
   281  	if h != nil {
   282  		return h
   283  	}
   284  	return newClient()
   285  }
   286  
   287  func newClient() helm.Interface {
   288  	options := []helm.Option{helm.Host(tillerHost)}
   289  
   290  	if tlsVerify || tlsEnable {
   291  		tlsopts := tlsutil.Options{KeyFile: tlsKeyFile, CertFile: tlsCertFile, InsecureSkipVerify: true}
   292  		if tlsVerify {
   293  			tlsopts.CaCertFile = tlsCaCertFile
   294  			tlsopts.InsecureSkipVerify = false
   295  		}
   296  		tlscfg, err := tlsutil.ClientConfig(tlsopts)
   297  		if err != nil {
   298  			fmt.Fprintln(os.Stderr, err)
   299  			os.Exit(2)
   300  		}
   301  		options = append(options, helm.WithTLS(tlscfg))
   302  	}
   303  	return helm.NewClient(options...)
   304  }
   305  
   306  // addFlagsTLS adds the flags for supporting client side TLS to the
   307  // helm command (only those that invoke communicate to Tiller.)
   308  func addFlagsTLS(cmd *cobra.Command) *cobra.Command {
   309  	// defaults
   310  	var (
   311  		tlsCaCertDefault = "$HELM_HOME/ca.pem"
   312  		tlsCertDefault   = "$HELM_HOME/cert.pem"
   313  		tlsKeyDefault    = "$HELM_HOME/key.pem"
   314  	)
   315  
   316  	// add flags
   317  	cmd.Flags().StringVar(&tlsCaCertFile, "tls-ca-cert", tlsCaCertDefault, "path to TLS CA certificate file")
   318  	cmd.Flags().StringVar(&tlsCertFile, "tls-cert", tlsCertDefault, "path to TLS certificate file")
   319  	cmd.Flags().StringVar(&tlsKeyFile, "tls-key", tlsKeyDefault, "path to TLS key file")
   320  	cmd.Flags().BoolVar(&tlsVerify, "tls-verify", false, "enable TLS for request and verify remote")
   321  	cmd.Flags().BoolVar(&tlsEnable, "tls", false, "enable TLS for request")
   322  	return cmd
   323  }