github.com/oam-dev/kubevela@v1.9.11/references/cli/exec.go (about)

     1  /*
     2  Copyright 2021 The KubeVela 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 cli
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/spf13/cobra"
    25  	"k8s.io/cli-runtime/pkg/genericclioptions"
    26  	"k8s.io/client-go/kubernetes"
    27  	"k8s.io/client-go/rest"
    28  	cmdexec "k8s.io/kubectl/pkg/cmd/exec"
    29  	k8scmdutil "k8s.io/kubectl/pkg/cmd/util"
    30  
    31  	pkgmulticluster "github.com/kubevela/pkg/multicluster"
    32  
    33  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    34  	"github.com/oam-dev/kubevela/apis/types"
    35  	"github.com/oam-dev/kubevela/pkg/multicluster"
    36  	"github.com/oam-dev/kubevela/pkg/utils/common"
    37  	"github.com/oam-dev/kubevela/pkg/utils/util"
    38  	querytypes "github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
    39  	"github.com/oam-dev/kubevela/references/appfile"
    40  )
    41  
    42  const (
    43  	podRunningTimeoutFlag = "pod-running-timeout"
    44  	defaultPodExecTimeout = 60 * time.Second
    45  	defaultStdin          = true
    46  	defaultTTY            = true
    47  )
    48  
    49  // VelaExecOptions creates options for `exec` command
    50  type VelaExecOptions struct {
    51  	Cmd   *cobra.Command
    52  	Args  []string
    53  	Stdin bool
    54  	TTY   bool
    55  
    56  	ComponentName string
    57  	PodName       string
    58  	ClusterName   string
    59  	ContainerName string
    60  
    61  	Ctx   context.Context
    62  	VelaC common.Args
    63  	Env   *types.EnvMeta
    64  	App   *v1beta1.Application
    65  
    66  	namespace     string
    67  	podName       string
    68  	podNamespace  string
    69  	f             k8scmdutil.Factory
    70  	kcExecOptions *cmdexec.ExecOptions
    71  	ClientSet     kubernetes.Interface
    72  }
    73  
    74  // NewExecCommand creates `exec` command
    75  func NewExecCommand(c common.Args, order string, ioStreams util.IOStreams) *cobra.Command {
    76  	o := &VelaExecOptions{
    77  		kcExecOptions: &cmdexec.ExecOptions{
    78  			StreamOptions: cmdexec.StreamOptions{
    79  				IOStreams: genericclioptions.IOStreams{
    80  					In:     ioStreams.In,
    81  					Out:    ioStreams.Out,
    82  					ErrOut: ioStreams.ErrOut,
    83  				},
    84  			},
    85  			Executor: &cmdexec.DefaultRemoteExecutor{},
    86  		},
    87  	}
    88  	cmd := &cobra.Command{
    89  		Use:   "exec",
    90  		Short: "Execute command in a container.",
    91  		Long:  "Execute command inside container based vela application.",
    92  		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
    93  			o.VelaC = c
    94  			return nil
    95  		},
    96  		RunE: func(cmd *cobra.Command, args []string) error {
    97  			if len(args) < 1 {
    98  				ioStreams.Error("Please specify an application name.")
    99  				return nil
   100  			}
   101  			if len(args) == 1 {
   102  				ioStreams.Error("Please specify at least one command for the container.")
   103  				return nil
   104  			}
   105  			argsLenAtDash := cmd.ArgsLenAtDash()
   106  			if argsLenAtDash != 1 {
   107  				ioStreams.Error("vela exec APP_NAME COMMAND is not supported. Use vela exec APP_NAME -- COMMAND instead.")
   108  				return nil
   109  			}
   110  			var err error
   111  			o.namespace, err = GetFlagNamespaceOrEnv(cmd, c)
   112  			if err != nil {
   113  				return err
   114  			}
   115  			if err := o.Init(context.Background(), cmd, args); err != nil {
   116  				return err
   117  			}
   118  			if err := o.Complete(); err != nil {
   119  				return err
   120  			}
   121  			if err := o.Run(); err != nil {
   122  				return err
   123  			}
   124  			return nil
   125  		},
   126  		Annotations: map[string]string{
   127  			types.TagCommandOrder: order,
   128  			types.TagCommandType:  types.TypeApp,
   129  		},
   130  		Example: `
   131  		exec [flags] APP_NAME -- COMMAND [args...]
   132  
   133  		# Get output from running 'date' command from app pod, using the first container by default
   134  		vela exec my-app -- date
   135  
   136  		# Switch to raw terminal mode, sends stdin to 'bash' in containers of application my-app
   137  		# and sends stdout/stderr from 'bash' back to the client
   138  		vela exec my-app -i -t -- bash -il
   139  		`,
   140  	}
   141  	cmd.Flags().BoolVarP(&o.Stdin, "stdin", "i", defaultStdin, "Pass stdin to the container")
   142  	cmd.Flags().BoolVarP(&o.TTY, "tty", "t", defaultTTY, "Stdin is a TTY")
   143  	cmd.Flags().Duration(podRunningTimeoutFlag, defaultPodExecTimeout,
   144  		"The length of time (like 5s, 2m, or 3h, higher than zero) to wait until at least one pod is running",
   145  	)
   146  	cmd.Flags().StringVarP(&o.ComponentName, "component", "c", "", "filter the pod by the component name")
   147  	cmd.Flags().StringVarP(&o.ClusterName, "cluster", "", "", "filter the pod by the cluster name")
   148  	cmd.Flags().StringVarP(&o.PodName, "pod", "p", "", "specify the pod name")
   149  	cmd.Flags().StringVarP(&o.ContainerName, "container", "", "", "specify the container name")
   150  	addNamespaceAndEnvArg(cmd)
   151  
   152  	return cmd
   153  }
   154  
   155  // Init prepares the arguments accepted by the Exec command
   156  func (o *VelaExecOptions) Init(ctx context.Context, c *cobra.Command, argsIn []string) error {
   157  	o.Cmd = c
   158  	o.Args = argsIn
   159  
   160  	app, err := appfile.LoadApplication(o.namespace, o.Args[0], o.VelaC)
   161  	if err != nil {
   162  		return err
   163  	}
   164  	o.App = app
   165  
   166  	pods, err := GetApplicationPods(ctx, app.Name, app.Namespace, o.VelaC, Filter{
   167  		Component: o.ComponentName,
   168  		Cluster:   o.ClusterName,
   169  	})
   170  	if err != nil {
   171  		return err
   172  	}
   173  	var selectPod *querytypes.PodBase
   174  	if o.PodName != "" {
   175  		for i, pod := range pods {
   176  			if pod.Metadata.Name == o.PodName {
   177  				selectPod = &pods[i]
   178  				break
   179  			}
   180  		}
   181  		if selectPod == nil {
   182  			fmt.Println("The Pod you specified does not exist, please select it from the list.")
   183  		}
   184  	}
   185  	if selectPod == nil {
   186  		selectPod, err = AskToChooseOnePod(pods)
   187  		if err != nil {
   188  			return err
   189  		}
   190  	}
   191  
   192  	if selectPod == nil {
   193  		return nil
   194  	}
   195  
   196  	cf := genericclioptions.NewConfigFlags(true)
   197  	var namespace = selectPod.Metadata.Namespace
   198  	cf.Namespace = &namespace
   199  	cf.WrapConfigFn = func(cfg *rest.Config) *rest.Config {
   200  		cfg.Wrap(pkgmulticluster.NewTransportWrapper(pkgmulticluster.ForCluster(selectPod.Cluster)))
   201  		return cfg
   202  	}
   203  	o.f = k8scmdutil.NewFactory(k8scmdutil.NewMatchVersionFlags(cf))
   204  	o.podName = selectPod.Metadata.Name
   205  	o.Ctx = multicluster.ContextWithClusterName(ctx, selectPod.Cluster)
   206  	o.podNamespace = namespace
   207  	config, err := o.VelaC.GetConfig()
   208  	if err != nil {
   209  		return err
   210  	}
   211  	config.Wrap(pkgmulticluster.NewTransportWrapper())
   212  	k8sClient, err := kubernetes.NewForConfig(config)
   213  	if err != nil {
   214  		return err
   215  	}
   216  	o.ClientSet = k8sClient
   217  
   218  	o.kcExecOptions.In = c.InOrStdin()
   219  	o.kcExecOptions.Out = c.OutOrStdout()
   220  	o.kcExecOptions.ErrOut = c.OutOrStderr()
   221  	return nil
   222  }
   223  
   224  // Complete loads data from the command environment
   225  func (o *VelaExecOptions) Complete() error {
   226  	o.kcExecOptions.StreamOptions.Stdin = o.Stdin
   227  	o.kcExecOptions.StreamOptions.TTY = o.TTY
   228  	o.kcExecOptions.StreamOptions.ContainerName = o.ContainerName
   229  
   230  	args := make([]string, len(o.Args))
   231  	copy(args, o.Args)
   232  	// args for kcExecOptions MUST be in such format:
   233  	// [podName, COMMAND...]
   234  	args[0] = o.podName
   235  	return o.kcExecOptions.Complete(o.f, o.Cmd, args, 1)
   236  }
   237  
   238  // Run executes a validated remote execution against a pod
   239  func (o *VelaExecOptions) Run() error {
   240  	return o.kcExecOptions.Run()
   241  }