github.com/Azure/draft-classic@v0.16.0/cmd/draft/draft.go (about) 1 // Copyright (c) Microsoft Corporation. All rights reserved. 2 // 3 // Licensed under the MIT license. 4 5 package main 6 7 import ( 8 "fmt" 9 "io" 10 "os" 11 "path/filepath" 12 "runtime" 13 14 log "github.com/sirupsen/logrus" 15 "github.com/spf13/cobra" 16 "k8s.io/client-go/kubernetes" 17 _ "k8s.io/client-go/plugin/pkg/client/auth" 18 "k8s.io/client-go/rest" 19 "k8s.io/client-go/tools/clientcmd" 20 "k8s.io/helm/pkg/helm" 21 hpf "k8s.io/helm/pkg/helm/portforwarder" 22 "k8s.io/helm/pkg/kube" 23 "k8s.io/helm/pkg/tiller/environment" 24 25 "github.com/Azure/draft/pkg/draft/draftpath" 26 ) 27 28 const ( 29 homeEnvVar = "DRAFT_HOME" 30 hostEnvVar = "HELM_HOST" 31 namespaceEnvVar = "TILLER_NAMESPACE" 32 ) 33 34 var ( 35 // flagDebug is a signal that the user wants additional output. 36 flagDebug bool 37 kubeContext string 38 // draftHome depicts the home directory where all Draft config is stored. 39 draftHome string 40 // tillerHost depicts where Tiller is hosted. This is used when the port forwarding logic by Kubernetes is unavailable. 41 tillerHost string 42 // tillerNamespace depicts which namespace Tiller is running in. This is used when Tiller was installed in a different namespace than kube-system. 43 tillerNamespace string 44 //rootCmd is the root command handling `draft`. It's used in other parts of package cmd to add/search the command tree. 45 rootCmd *cobra.Command 46 // globalConfig is the configuration stored in $DRAFT_HOME/config.toml 47 globalConfig DraftConfig 48 ) 49 50 var globalUsage = `The application deployment tool for Kubernetes. 51 ` 52 53 func init() { 54 rootCmd = newRootCmd(os.Stdout, os.Stdin) 55 } 56 57 func newRootCmd(out io.Writer, in io.Reader) *cobra.Command { 58 cmd := &cobra.Command{ 59 Use: "draft", 60 Short: globalUsage, 61 Long: globalUsage, 62 SilenceUsage: true, 63 PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { 64 if flagDebug { 65 log.SetLevel(log.DebugLevel) 66 } 67 os.Setenv(homeEnvVar, draftHome) 68 globalConfig, err = ReadConfig() 69 return 70 }, 71 } 72 p := cmd.PersistentFlags() 73 p.StringVar(&draftHome, "home", defaultDraftHome(), "location of your Draft config. Overrides $DRAFT_HOME") 74 p.BoolVar(&flagDebug, "debug", false, "enable verbose output") 75 p.StringVar(&kubeContext, "kube-context", "", "name of the kubeconfig context to use when talking to Tiller") 76 p.StringVar(&tillerNamespace, "tiller-namespace", defaultTillerNamespace(), "namespace where Tiller is running. This is used when Tiller was installed in a different namespace than kube-system. Overrides $TILLER_NAMESPACE") 77 78 cmd.AddCommand( 79 newConfigCmd(out), 80 newCreateCmd(out), 81 newHomeCmd(out), 82 newInitCmd(out, in), 83 newUpCmd(out), 84 newVersionCmd(out), 85 newPluginCmd(out), 86 newConnectCmd(out), 87 newDeleteCmd(out), 88 newLogsCmd(out), 89 newHistoryCmd(out), 90 newPackCmd(out), 91 ) 92 93 // Find and add plugins 94 loadPlugins(cmd, draftpath.Home(homePath()), out, in) 95 96 return cmd 97 } 98 99 func defaultDraftHome() string { 100 if home := os.Getenv(homeEnvVar); home != "" { 101 return home 102 } 103 104 homeEnvPath := os.Getenv("HOME") 105 if homeEnvPath == "" && runtime.GOOS == "windows" { 106 homeEnvPath = os.Getenv("USERPROFILE") 107 } 108 109 return filepath.Join(homeEnvPath, ".draft") 110 } 111 112 func defaultTillerNamespace() string { 113 if namespace := os.Getenv(namespaceEnvVar); namespace != "" { 114 return namespace 115 } 116 return environment.DefaultTillerNamespace 117 } 118 119 func homePath() string { 120 return os.ExpandEnv(draftHome) 121 } 122 123 // configForContext creates a Kubernetes REST client configuration for a given kubeconfig context. 124 func configForContext(context string) (clientcmd.ClientConfig, *rest.Config, error) { 125 clientConfig := kube.GetConfig(context) 126 config, err := clientConfig.ClientConfig() 127 if err != nil { 128 return nil, nil, fmt.Errorf("could not get Kubernetes config for context %q: %s", context, err) 129 } 130 return clientConfig, config, nil 131 } 132 133 // getKubeClient creates a Kubernetes config and client for a given kubeconfig context. 134 func getKubeClient(context string) (kubernetes.Interface, *rest.Config, error) { 135 _, config, err := configForContext(context) 136 if err != nil { 137 return nil, nil, err 138 } 139 client, err := kubernetes.NewForConfig(config) 140 if err != nil { 141 return nil, nil, fmt.Errorf("could not get Kubernetes client: %s", err) 142 } 143 return client, config, nil 144 } 145 146 func debug(format string, args ...interface{}) { 147 if flagDebug { 148 format = fmt.Sprintf("[debug] %s\n", format) 149 fmt.Printf(format, args...) 150 } 151 } 152 153 func validateArgs(args, expectedArgs []string) error { 154 if len(args) != len(expectedArgs) { 155 return fmt.Errorf("This command needs %v argument(s): %v", len(expectedArgs), expectedArgs) 156 } 157 return nil 158 } 159 160 func setupHelm(kubeClient kubernetes.Interface, config *rest.Config, namespace string) (helm.Interface, error) { 161 tunnel, err := setupTillerConnection(kubeClient, config, namespace) 162 if err != nil { 163 return nil, err 164 } 165 166 return helm.NewClient(helm.Host(fmt.Sprintf("127.0.0.1:%d", tunnel.Local))), nil 167 } 168 169 func setupTillerConnection(client kubernetes.Interface, config *rest.Config, namespace string) (*kube.Tunnel, error) { 170 tunnel, err := hpf.New(namespace, client, config) 171 if err != nil { 172 return nil, fmt.Errorf("Could not get a connection to tiller: %s\nPlease ensure you have run `helm init`", err) 173 } 174 175 return tunnel, err 176 } 177 178 func main() { 179 if err := rootCmd.Execute(); err != nil { 180 os.Exit(1) 181 } 182 }