github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/exec/exec.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package exec 21 22 import ( 23 "context" 24 "fmt" 25 "io" 26 27 corev1 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/cli-runtime/pkg/genericiooptions" 30 "k8s.io/client-go/dynamic" 31 "k8s.io/client-go/kubernetes" 32 "k8s.io/client-go/kubernetes/scheme" 33 restclient "k8s.io/client-go/rest" 34 "k8s.io/client-go/tools/remotecommand" 35 cmdexec "k8s.io/kubectl/pkg/cmd/exec" 36 cmdutil "k8s.io/kubectl/pkg/cmd/util" 37 "k8s.io/kubectl/pkg/cmd/util/podcmd" 38 ) 39 40 type ExecOptions struct { 41 cmdexec.StreamOptions 42 43 Factory cmdutil.Factory 44 Executor cmdexec.RemoteExecutor 45 Config *restclient.Config 46 Client *kubernetes.Clientset 47 Dynamic dynamic.Interface 48 49 // Pod target pod to execute command 50 Pod *corev1.Pod 51 52 // Command is the command to execute 53 Command []string 54 } 55 56 func NewExecOptions(f cmdutil.Factory, streams genericiooptions.IOStreams) *ExecOptions { 57 return &ExecOptions{ 58 Factory: f, 59 StreamOptions: cmdexec.StreamOptions{ 60 IOStreams: streams, 61 Stdin: true, 62 TTY: true, 63 }, 64 Executor: &cmdexec.DefaultRemoteExecutor{}, 65 } 66 } 67 68 // Complete receives exec parameters 69 func (o *ExecOptions) Complete() error { 70 var err error 71 o.Config, err = o.Factory.ToRESTConfig() 72 if err != nil { 73 return err 74 } 75 76 o.Namespace, _, err = o.Factory.ToRawKubeConfigLoader().Namespace() 77 if err != nil { 78 return err 79 } 80 81 o.Dynamic, err = o.Factory.DynamicClient() 82 if err != nil { 83 return err 84 } 85 86 o.Client, err = o.Factory.KubernetesClientSet() 87 return err 88 } 89 90 func (o *ExecOptions) validate() error { 91 var err error 92 93 // pod is not set, try to get it by pod name 94 if o.Pod == nil && len(o.PodName) > 0 { 95 if o.Pod, err = o.Client.CoreV1().Pods(o.Namespace).Get(context.TODO(), o.PodName, metav1.GetOptions{}); err != nil { 96 return err 97 } 98 } 99 100 if o.Pod == nil { 101 return fmt.Errorf("failed to get the pod to execute") 102 } 103 if len(o.Command) == 0 { 104 return fmt.Errorf("you must specify at least one command for the container") 105 } 106 if o.Out == nil || o.ErrOut == nil { 107 return fmt.Errorf("both output and error output must be provided") 108 } 109 110 if o.Pod.Status.Phase == corev1.PodSucceeded || 111 o.Pod.Status.Phase == corev1.PodFailed { 112 return fmt.Errorf("cannot exec into a container in a completed pod; current phase is %s", o.Pod.Status.Phase) 113 } 114 115 // check and get the container to execute command 116 if len(o.ContainerName) == 0 { 117 container, err := podcmd.FindOrDefaultContainerByName(o.Pod, "", o.Quiet, o.ErrOut) 118 if err != nil { 119 return err 120 } 121 o.ContainerName = container.Name 122 } 123 124 return nil 125 } 126 127 func (o *ExecOptions) Run() error { 128 return o.RunWithRedirect(o.Out, o.ErrOut) 129 } 130 131 func (o *ExecOptions) RunWithRedirect(outWriter io.Writer, errWriter io.Writer) error { 132 if err := o.validate(); err != nil { 133 return err 134 } 135 136 // ensure we can recover the terminal while attached 137 t := o.SetupTTY() 138 139 var sizeQueue remotecommand.TerminalSizeQueue 140 if t.Raw { 141 // this call spawns a goroutine to monitor/update the terminal size 142 sizeQueue = t.MonitorSize(t.GetSize()) 143 144 // unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is 145 // true 146 o.ErrOut = nil 147 } 148 149 fn := func() error { 150 restClient, err := restclient.RESTClientFor(o.Config) 151 if err != nil { 152 return err 153 } 154 155 req := restClient.Post(). 156 Resource("pods"). 157 Name(o.Pod.Name). 158 Namespace(o.Pod.Namespace). 159 SubResource("exec") 160 req.VersionedParams(&corev1.PodExecOptions{ 161 Container: o.ContainerName, 162 Command: o.Command, 163 Stdin: o.Stdin, 164 Stdout: outWriter != nil, 165 Stderr: errWriter != nil, 166 TTY: t.Raw, 167 }, scheme.ParameterCodec) 168 169 return o.Executor.Execute("POST", req.URL(), o.Config, o.In, outWriter, errWriter, t.Raw, sizeQueue) 170 } 171 172 if err := t.Safe(fn); err != nil { 173 return err 174 } 175 return nil 176 }