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 }