2019-01-12 04:58:27 +00:00
/ *
Copyright 2014 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package cmd
import (
"flag"
"fmt"
"io"
2021-03-18 22:40:29 +00:00
"net/http"
2019-01-12 04:58:27 +00:00
"os"
"os/exec"
2019-09-27 21:51:53 +00:00
"runtime"
2019-01-12 04:58:27 +00:00
"strings"
"syscall"
"github.com/spf13/cobra"
2020-08-10 17:43:49 +00:00
"k8s.io/client-go/rest"
2019-01-12 04:58:27 +00:00
"k8s.io/client-go/tools/clientcmd"
2019-04-07 17:07:55 +00:00
cliflag "k8s.io/component-base/cli/flag"
2021-07-02 08:43:15 +00:00
"k8s.io/klog/v2"
2019-09-27 21:51:53 +00:00
"k8s.io/kubectl/pkg/cmd/annotate"
"k8s.io/kubectl/pkg/cmd/apiresources"
"k8s.io/kubectl/pkg/cmd/apply"
"k8s.io/kubectl/pkg/cmd/attach"
2020-12-01 01:06:26 +00:00
"k8s.io/kubectl/pkg/cmd/auth"
2019-09-27 21:51:53 +00:00
"k8s.io/kubectl/pkg/cmd/autoscale"
"k8s.io/kubectl/pkg/cmd/certificates"
"k8s.io/kubectl/pkg/cmd/clusterinfo"
"k8s.io/kubectl/pkg/cmd/completion"
cmdconfig "k8s.io/kubectl/pkg/cmd/config"
2020-08-10 17:43:49 +00:00
"k8s.io/kubectl/pkg/cmd/cp"
2019-09-27 21:51:53 +00:00
"k8s.io/kubectl/pkg/cmd/create"
2020-12-01 01:06:26 +00:00
"k8s.io/kubectl/pkg/cmd/debug"
2019-09-27 21:51:53 +00:00
"k8s.io/kubectl/pkg/cmd/delete"
"k8s.io/kubectl/pkg/cmd/describe"
"k8s.io/kubectl/pkg/cmd/diff"
"k8s.io/kubectl/pkg/cmd/drain"
"k8s.io/kubectl/pkg/cmd/edit"
cmdexec "k8s.io/kubectl/pkg/cmd/exec"
"k8s.io/kubectl/pkg/cmd/explain"
"k8s.io/kubectl/pkg/cmd/expose"
2019-12-12 01:27:03 +00:00
"k8s.io/kubectl/pkg/cmd/get"
2019-09-27 21:51:53 +00:00
"k8s.io/kubectl/pkg/cmd/label"
"k8s.io/kubectl/pkg/cmd/logs"
"k8s.io/kubectl/pkg/cmd/options"
"k8s.io/kubectl/pkg/cmd/patch"
"k8s.io/kubectl/pkg/cmd/plugin"
"k8s.io/kubectl/pkg/cmd/portforward"
"k8s.io/kubectl/pkg/cmd/proxy"
"k8s.io/kubectl/pkg/cmd/replace"
"k8s.io/kubectl/pkg/cmd/rollout"
"k8s.io/kubectl/pkg/cmd/run"
"k8s.io/kubectl/pkg/cmd/scale"
"k8s.io/kubectl/pkg/cmd/set"
"k8s.io/kubectl/pkg/cmd/taint"
"k8s.io/kubectl/pkg/cmd/top"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/cmd/version"
"k8s.io/kubectl/pkg/cmd/wait"
2021-07-02 08:43:15 +00:00
"k8s.io/kubectl/pkg/util"
2019-09-27 21:51:53 +00:00
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
2020-08-10 17:43:49 +00:00
"k8s.io/kubectl/pkg/util/term"
2019-01-12 04:58:27 +00:00
"k8s.io/cli-runtime/pkg/genericclioptions"
2019-09-27 21:51:53 +00:00
"k8s.io/kubectl/pkg/cmd/kustomize"
2019-01-12 04:58:27 +00:00
)
2021-03-18 22:40:29 +00:00
const kubectlCmdHeaders = "KUBECTL_COMMAND_HEADERS"
2019-01-12 04:58:27 +00:00
// NewDefaultKubectlCommand creates the `kubectl` command with default arguments
func NewDefaultKubectlCommand ( ) * cobra . Command {
2019-04-07 17:07:55 +00:00
return NewDefaultKubectlCommandWithArgs ( NewDefaultPluginHandler ( plugin . ValidPluginFilenamePrefixes ) , os . Args , os . Stdin , os . Stdout , os . Stderr )
2019-01-12 04:58:27 +00:00
}
// NewDefaultKubectlCommandWithArgs creates the `kubectl` command with arguments
func NewDefaultKubectlCommandWithArgs ( pluginHandler PluginHandler , args [ ] string , in io . Reader , out , errout io . Writer ) * cobra . Command {
cmd := NewKubectlCommand ( in , out , errout )
if pluginHandler == nil {
return cmd
}
if len ( args ) > 1 {
cmdPathPieces := args [ 1 : ]
// only look for suitable extension executables if
// the specified command does not already exist
if _ , _ , err := cmd . Find ( cmdPathPieces ) ; err != nil {
2019-04-07 17:07:55 +00:00
if err := HandlePluginCommand ( pluginHandler , cmdPathPieces ) ; err != nil {
2020-12-01 01:06:26 +00:00
fmt . Fprintf ( errout , "Error: %v\n" , err )
2019-01-12 04:58:27 +00:00
os . Exit ( 1 )
}
}
}
return cmd
}
// PluginHandler is capable of parsing command line arguments
// and performing executable filename lookups to search
// for valid plugin files, and execute found plugins.
type PluginHandler interface {
2019-04-07 17:07:55 +00:00
// exists at the given filename, or a boolean false.
// Lookup will iterate over a list of given prefixes
// in order to recognize valid plugin filenames.
// The first filepath to match a prefix is returned.
Lookup ( filename string ) ( string , bool )
2019-01-12 04:58:27 +00:00
// Execute receives an executable's filepath, a slice
// of arguments, and a slice of environment variables
// to relay to the executable.
Execute ( executablePath string , cmdArgs , environment [ ] string ) error
}
2019-04-07 17:07:55 +00:00
// DefaultPluginHandler implements PluginHandler
type DefaultPluginHandler struct {
ValidPrefixes [ ] string
}
// NewDefaultPluginHandler instantiates the DefaultPluginHandler with a list of
// given filename prefixes used to identify valid plugin filenames.
func NewDefaultPluginHandler ( validPrefixes [ ] string ) * DefaultPluginHandler {
return & DefaultPluginHandler {
ValidPrefixes : validPrefixes ,
}
}
2019-01-12 04:58:27 +00:00
// Lookup implements PluginHandler
2019-04-07 17:07:55 +00:00
func ( h * DefaultPluginHandler ) Lookup ( filename string ) ( string , bool ) {
for _ , prefix := range h . ValidPrefixes {
path , err := exec . LookPath ( fmt . Sprintf ( "%s-%s" , prefix , filename ) )
if err != nil || len ( path ) == 0 {
continue
}
return path , true
2019-01-12 04:58:27 +00:00
}
2019-04-07 17:07:55 +00:00
return "" , false
2019-01-12 04:58:27 +00:00
}
// Execute implements PluginHandler
2019-04-07 17:07:55 +00:00
func ( h * DefaultPluginHandler ) Execute ( executablePath string , cmdArgs , environment [ ] string ) error {
2019-09-27 21:51:53 +00:00
// Windows does not support exec syscall.
if runtime . GOOS == "windows" {
cmd := exec . Command ( executablePath , cmdArgs ... )
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
cmd . Stdin = os . Stdin
cmd . Env = environment
err := cmd . Run ( )
if err == nil {
os . Exit ( 0 )
}
return err
}
// invoke cmd binary relaying the environment and args given
// append executablePath to cmdArgs, as execve will make first argument the "binary name".
return syscall . Exec ( executablePath , append ( [ ] string { executablePath } , cmdArgs ... ) , environment )
2019-01-12 04:58:27 +00:00
}
2019-04-07 17:07:55 +00:00
// HandlePluginCommand receives a pluginHandler and command-line arguments and attempts to find
// a plugin executable on the PATH that satisfies the given arguments.
func HandlePluginCommand ( pluginHandler PluginHandler , cmdArgs [ ] string ) error {
2020-12-01 01:06:26 +00:00
var remainingArgs [ ] string // all "non-flag" arguments
for _ , arg := range cmdArgs {
if strings . HasPrefix ( arg , "-" ) {
2019-01-12 04:58:27 +00:00
break
}
2020-12-01 01:06:26 +00:00
remainingArgs = append ( remainingArgs , strings . Replace ( arg , "-" , "_" , - 1 ) )
}
if len ( remainingArgs ) == 0 {
// the length of cmdArgs is at least 1
return fmt . Errorf ( "flags cannot be placed before plugin name: %s" , cmdArgs [ 0 ] )
2019-01-12 04:58:27 +00:00
}
foundBinaryPath := ""
// attempt to find binary, starting at longest possible name with given cmdArgs
for len ( remainingArgs ) > 0 {
2019-04-07 17:07:55 +00:00
path , found := pluginHandler . Lookup ( strings . Join ( remainingArgs , "-" ) )
if ! found {
2019-01-12 04:58:27 +00:00
remainingArgs = remainingArgs [ : len ( remainingArgs ) - 1 ]
continue
}
foundBinaryPath = path
break
}
if len ( foundBinaryPath ) == 0 {
return nil
}
// invoke cmd binary relaying the current environment and args given
2019-09-27 21:51:53 +00:00
if err := pluginHandler . Execute ( foundBinaryPath , cmdArgs [ len ( remainingArgs ) : ] , os . Environ ( ) ) ; err != nil {
2019-01-12 04:58:27 +00:00
return err
}
return nil
}
// NewKubectlCommand creates the `kubectl` command and its nested children.
func NewKubectlCommand ( in io . Reader , out , err io . Writer ) * cobra . Command {
2020-08-10 17:43:49 +00:00
warningHandler := rest . NewWarningWriter ( err , rest . WarningWriterOptions { Deduplicate : true , Color : term . AllowsColorOutput ( err ) } )
warningsAsErrors := false
2019-01-12 04:58:27 +00:00
// Parent command to which all subcommands are added.
cmds := & cobra . Command {
Use : "kubectl" ,
Short : i18n . T ( "kubectl controls the Kubernetes cluster manager" ) ,
Long : templates . LongDesc ( `
kubectl controls the Kubernetes cluster manager .
Find more information at :
https : //kubernetes.io/docs/reference/kubectl/overview/`),
Run : runHelp ,
// Hook before and after Run initialize and write profiles to disk,
// respectively.
PersistentPreRunE : func ( * cobra . Command , [ ] string ) error {
2020-08-10 17:43:49 +00:00
rest . SetDefaultWarningHandler ( warningHandler )
2019-01-12 04:58:27 +00:00
return initProfiling ( )
} ,
PersistentPostRunE : func ( * cobra . Command , [ ] string ) error {
2020-08-10 17:43:49 +00:00
if err := flushProfiling ( ) ; err != nil {
return err
}
if warningsAsErrors {
count := warningHandler . WarningCount ( )
switch count {
case 0 :
// no warnings
case 1 :
return fmt . Errorf ( "%d warning received" , count )
default :
return fmt . Errorf ( "%d warnings received" , count )
}
}
return nil
2019-01-12 04:58:27 +00:00
} ,
}
flags := cmds . PersistentFlags ( )
2019-04-07 17:07:55 +00:00
flags . SetNormalizeFunc ( cliflag . WarnWordSepNormalizeFunc ) // Warn for "_" flags
2019-01-12 04:58:27 +00:00
// Normalize all flags that are coming from other packages or pre-configurations
// a.k.a. change all "_" to "-". e.g. glog package
2019-04-07 17:07:55 +00:00
flags . SetNormalizeFunc ( cliflag . WordSepNormalizeFunc )
2019-01-12 04:58:27 +00:00
addProfilingFlags ( flags )
2020-08-10 17:43:49 +00:00
flags . BoolVar ( & warningsAsErrors , "warnings-as-errors" , warningsAsErrors , "Treat warnings received from the server as errors and exit with a non-zero exit code" )
2019-04-07 17:07:55 +00:00
kubeConfigFlags := genericclioptions . NewConfigFlags ( true ) . WithDeprecatedPasswordFlag ( )
2019-01-12 04:58:27 +00:00
kubeConfigFlags . AddFlags ( flags )
matchVersionKubeConfigFlags := cmdutil . NewMatchVersionFlags ( kubeConfigFlags )
matchVersionKubeConfigFlags . AddFlags ( cmds . PersistentFlags ( ) )
2021-03-18 22:40:29 +00:00
// Updates hooks to add kubectl command headers: SIG CLI KEP 859.
addCmdHeaderHooks ( cmds , kubeConfigFlags )
2019-01-12 04:58:27 +00:00
cmds . PersistentFlags ( ) . AddGoFlagSet ( flag . CommandLine )
f := cmdutil . NewFactory ( matchVersionKubeConfigFlags )
// Sending in 'nil' for the getLanguageFn() results in using
// the LANG environment variable.
//
// TODO: Consider adding a flag or file preference for setting
// the language, instead of just loading from the LANG env. variable.
i18n . LoadTranslations ( "kubectl" , nil )
// From this point and forward we get warnings on flags that contain "_" separators
2019-04-07 17:07:55 +00:00
cmds . SetGlobalNormalizationFunc ( cliflag . WarnWordSepNormalizeFunc )
2019-01-12 04:58:27 +00:00
ioStreams := genericclioptions . IOStreams { In : in , Out : out , ErrOut : err }
2021-03-18 22:40:29 +00:00
// Proxy command is incompatible with CommandHeaderRoundTripper, so
// clear the WrapConfigFn before running proxy command.
proxyCmd := proxy . NewCmdProxy ( f , ioStreams )
proxyCmd . PreRun = func ( cmd * cobra . Command , args [ ] string ) {
kubeConfigFlags . WrapConfigFn = nil
}
2019-01-12 04:58:27 +00:00
groups := templates . CommandGroups {
{
Message : "Basic Commands (Beginner):" ,
Commands : [ ] * cobra . Command {
create . NewCmdCreate ( f , ioStreams ) ,
expose . NewCmdExposeService ( f , ioStreams ) ,
run . NewCmdRun ( f , ioStreams ) ,
set . NewCmdSet ( f , ioStreams ) ,
} ,
} ,
{
Message : "Basic Commands (Intermediate):" ,
Commands : [ ] * cobra . Command {
2019-08-30 18:33:25 +00:00
explain . NewCmdExplain ( "kubectl" , f , ioStreams ) ,
2019-01-12 04:58:27 +00:00
get . NewCmdGet ( "kubectl" , f , ioStreams ) ,
edit . NewCmdEdit ( f , ioStreams ) ,
delete . NewCmdDelete ( f , ioStreams ) ,
} ,
} ,
{
Message : "Deploy Commands:" ,
Commands : [ ] * cobra . Command {
rollout . NewCmdRollout ( f , ioStreams ) ,
scale . NewCmdScale ( f , ioStreams ) ,
autoscale . NewCmdAutoscale ( f , ioStreams ) ,
} ,
} ,
{
Message : "Cluster Management Commands:" ,
Commands : [ ] * cobra . Command {
certificates . NewCmdCertificate ( f , ioStreams ) ,
clusterinfo . NewCmdClusterInfo ( f , ioStreams ) ,
top . NewCmdTop ( f , ioStreams ) ,
drain . NewCmdCordon ( f , ioStreams ) ,
drain . NewCmdUncordon ( f , ioStreams ) ,
drain . NewCmdDrain ( f , ioStreams ) ,
taint . NewCmdTaint ( f , ioStreams ) ,
} ,
} ,
{
Message : "Troubleshooting and Debugging Commands:" ,
Commands : [ ] * cobra . Command {
describe . NewCmdDescribe ( "kubectl" , f , ioStreams ) ,
logs . NewCmdLogs ( f , ioStreams ) ,
attach . NewCmdAttach ( f , ioStreams ) ,
cmdexec . NewCmdExec ( f , ioStreams ) ,
portforward . NewCmdPortForward ( f , ioStreams ) ,
2021-03-18 22:40:29 +00:00
proxyCmd ,
2019-01-12 04:58:27 +00:00
cp . NewCmdCp ( f , ioStreams ) ,
auth . NewCmdAuth ( f , ioStreams ) ,
2021-03-18 22:40:29 +00:00
debug . NewCmdDebug ( f , ioStreams ) ,
2019-01-12 04:58:27 +00:00
} ,
} ,
{
Message : "Advanced Commands:" ,
Commands : [ ] * cobra . Command {
2019-08-30 18:33:25 +00:00
diff . NewCmdDiff ( f , ioStreams ) ,
2019-01-12 04:58:27 +00:00
apply . NewCmdApply ( "kubectl" , f , ioStreams ) ,
patch . NewCmdPatch ( f , ioStreams ) ,
replace . NewCmdReplace ( f , ioStreams ) ,
wait . NewCmdWait ( f , ioStreams ) ,
2019-08-30 18:33:25 +00:00
kustomize . NewCmdKustomize ( ioStreams ) ,
2019-01-12 04:58:27 +00:00
} ,
} ,
{
Message : "Settings Commands:" ,
Commands : [ ] * cobra . Command {
label . NewCmdLabel ( f , ioStreams ) ,
annotate . NewCmdAnnotate ( "kubectl" , f , ioStreams ) ,
completion . NewCmdCompletion ( ioStreams . Out , "" ) ,
} ,
} ,
}
groups . Add ( cmds )
filters := [ ] string { "options" }
// Hide the "alpha" subcommand if there are no alpha commands in this build.
2021-07-02 08:43:15 +00:00
alpha := NewCmdAlpha ( ioStreams )
2019-01-12 04:58:27 +00:00
if ! alpha . HasSubCommands ( ) {
filters = append ( filters , alpha . Name ( ) )
}
templates . ActsAsRootCommand ( cmds , filters , groups ... )
2021-07-02 08:43:15 +00:00
util . SetFactoryForCompletion ( f )
registerCompletionFuncForGlobalFlags ( cmds , f )
2019-01-12 04:58:27 +00:00
cmds . AddCommand ( alpha )
2021-07-02 08:43:15 +00:00
cmds . AddCommand ( cmdconfig . NewCmdConfig ( clientcmd . NewDefaultPathOptions ( ) , ioStreams ) )
cmds . AddCommand ( plugin . NewCmdPlugin ( ioStreams ) )
2019-01-12 04:58:27 +00:00
cmds . AddCommand ( version . NewCmdVersion ( f , ioStreams ) )
cmds . AddCommand ( apiresources . NewCmdAPIVersions ( f , ioStreams ) )
cmds . AddCommand ( apiresources . NewCmdAPIResources ( f , ioStreams ) )
cmds . AddCommand ( options . NewCmdOptions ( ioStreams . Out ) )
return cmds
}
2021-03-18 22:40:29 +00:00
// addCmdHeaderHooks performs updates on two hooks:
// 1) Modifies the passed "cmds" persistent pre-run function to parse command headers.
// These headers will be subsequently added as X-headers to every
// REST call.
// 2) Adds CommandHeaderRoundTripper as a wrapper around the standard
// RoundTripper. CommandHeaderRoundTripper adds X-Headers then delegates
// to standard RoundTripper.
2021-07-02 08:43:15 +00:00
// For beta, these hooks are updated unless the KUBECTL_COMMAND_HEADERS environment variable
// is set, and the value of the env var is false (or zero).
2021-03-18 22:40:29 +00:00
// See SIG CLI KEP 859 for more information:
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/859-kubectl-headers
func addCmdHeaderHooks ( cmds * cobra . Command , kubeConfigFlags * genericclioptions . ConfigFlags ) {
2021-07-02 08:43:15 +00:00
// If the feature gate env var is set to "false", then do no add kubectl command headers.
if value , exists := os . LookupEnv ( kubectlCmdHeaders ) ; exists {
if value == "false" || value == "0" {
klog . V ( 5 ) . Infoln ( "kubectl command headers turned off" )
return
}
2021-03-18 22:40:29 +00:00
}
2021-07-02 08:43:15 +00:00
klog . V ( 5 ) . Infoln ( "kubectl command headers turned on" )
2021-03-18 22:40:29 +00:00
crt := & genericclioptions . CommandHeaderRoundTripper { }
existingPreRunE := cmds . PersistentPreRunE
// Add command parsing to the existing persistent pre-run function.
cmds . PersistentPreRunE = func ( cmd * cobra . Command , args [ ] string ) error {
crt . ParseCommandHeaders ( cmd , args )
return existingPreRunE ( cmd , args )
}
// Wraps CommandHeaderRoundTripper around standard RoundTripper.
kubeConfigFlags . WrapConfigFn = func ( c * rest . Config ) * rest . Config {
c . Wrap ( func ( rt http . RoundTripper ) http . RoundTripper {
crt . Delegate = rt
return crt
} )
return c
}
}
2019-01-12 04:58:27 +00:00
func runHelp ( cmd * cobra . Command , args [ ] string ) {
cmd . Help ( )
}
2021-07-02 08:43:15 +00:00
func registerCompletionFuncForGlobalFlags ( cmd * cobra . Command , f cmdutil . Factory ) {
cmdutil . CheckErr ( cmd . RegisterFlagCompletionFunc (
"namespace" ,
func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
return get . CompGetResource ( f , cmd , "namespace" , toComplete ) , cobra . ShellCompDirectiveNoFileComp
} ) )
cmdutil . CheckErr ( cmd . RegisterFlagCompletionFunc (
"context" ,
func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
return util . ListContextsInConfig ( toComplete ) , cobra . ShellCompDirectiveNoFileComp
} ) )
cmdutil . CheckErr ( cmd . RegisterFlagCompletionFunc (
"cluster" ,
func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
return util . ListClustersInConfig ( toComplete ) , cobra . ShellCompDirectiveNoFileComp
} ) )
cmdutil . CheckErr ( cmd . RegisterFlagCompletionFunc (
"user" ,
func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
return util . ListUsersInConfig ( toComplete ) , cobra . ShellCompDirectiveNoFileComp
} ) )
}