github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/pkg/debug/connect.go (about) 1 // Copyright © 2021 Alibaba Group Holding Ltd. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package debug 16 17 import ( 18 "fmt" 19 "net/url" 20 21 corev1 "k8s.io/api/core/v1" 22 "k8s.io/cli-runtime/pkg/genericclioptions" 23 "k8s.io/client-go/kubernetes/scheme" 24 restclient "k8s.io/client-go/rest" 25 "k8s.io/client-go/tools/remotecommand" 26 ) 27 28 // Connector holds the options to connect a running container. 29 type Connector struct { 30 NameSpace string 31 ContainerName string 32 Command []string 33 Stdin bool 34 TTY bool 35 genericclioptions.IOStreams 36 37 Motd string 38 39 Pod *corev1.Pod 40 Config *restclient.Config 41 } 42 43 // Connect connects to a running container. 44 func (connector *Connector) Connect() error { 45 container, err := connector.ContainerToConnect() 46 if err != nil { 47 return err 48 } 49 50 if connector.TTY && !container.TTY { 51 connector.TTY = false 52 } 53 54 // set the TTY 55 t := connector.SetTTY() 56 57 // get the terminal size queue 58 var sizeQueue remotecommand.TerminalSizeQueue 59 if t.Raw { 60 // this spawns a goroutine to monitor/update the terminal size 61 if size := t.GetSize(); size != nil { 62 sizePlusOne := *size 63 sizePlusOne.Width++ 64 sizePlusOne.Height++ 65 66 sizeQueue = t.MonitorSize(&sizePlusOne, size) 67 } 68 69 showMotd(connector.Out, connector.Motd) 70 } 71 72 if len(connector.Command) == 0 { 73 if err := t.Safe(connector.GetDefaultAttachFunc(container, sizeQueue)); err != nil { 74 return err 75 } 76 } else { 77 if err := t.Safe(connector.GetDefaultExecFunc(container, sizeQueue)); err != nil { 78 return err 79 } 80 } 81 82 return nil 83 } 84 85 // ContainerToConnect checks if there is a container to attach, and if exists returns the container object to attach. 86 func (connector *Connector) ContainerToConnect() (*corev1.Container, error) { 87 pod := connector.Pod 88 89 if len(connector.ContainerName) > 0 { 90 for i := range pod.Spec.Containers { 91 if pod.Spec.Containers[i].Name == connector.ContainerName { 92 return &pod.Spec.Containers[i], nil 93 } 94 } 95 96 for i := range pod.Spec.InitContainers { 97 if pod.Spec.InitContainers[i].Name == connector.ContainerName { 98 return &pod.Spec.InitContainers[i], nil 99 } 100 } 101 102 for i := range pod.Spec.EphemeralContainers { 103 if pod.Spec.EphemeralContainers[i].Name == connector.ContainerName { 104 return (*corev1.Container)(&pod.Spec.EphemeralContainers[i].EphemeralContainerCommon), nil 105 } 106 } 107 108 return nil, fmt.Errorf("there is no container named %s", connector.ContainerName) 109 } 110 111 return &pod.Spec.Containers[0], nil 112 } 113 114 // SetTTY handles the stdin and tty with following: 115 // 1. stdin false, tty false --- stdout 116 // 2. stdin false, tty true --- stdout 117 // 3. stdin true, tty false --- stdin、stdout 118 // 4. stdin true, tty true --- stdin、stdout、tty --- t.Raw 119 // 120 // then returns a TTY object based on connectOpts. 121 122 func (connector *Connector) SetTTY() TTY { 123 t := TTY{ 124 Out: connector.Out, 125 } 126 127 // Stdin is false, then tty and stdin both false 128 if !connector.Stdin { 129 connector.In = nil 130 connector.TTY = false 131 return t 132 } 133 134 t.In = connector.In 135 if !connector.TTY { 136 return t 137 } 138 139 // check whether t.In is a terminal 140 if !t.IsTerminalIn() { 141 connector.TTY = false 142 return t 143 } 144 145 t.Raw = true 146 147 return t 148 } 149 150 // GetDefaultAttachFunc returns the default attach function. 151 func (connector *Connector) GetDefaultAttachFunc(containerToAttach *corev1.Container, sizeQueue remotecommand.TerminalSizeQueue) func() error { 152 return func() error { 153 restClient, err := restclient.RESTClientFor(connector.Config) 154 if err != nil { 155 return err 156 } 157 158 req := restClient.Post(). 159 Resource("pods"). 160 Name(connector.Pod.Name). 161 Namespace(connector.Pod.Namespace). 162 SubResource("attach") 163 req.VersionedParams(&corev1.PodAttachOptions{ 164 Container: containerToAttach.Name, 165 Stdin: connector.Stdin, 166 Stdout: connector.Out != nil, 167 Stderr: connector.ErrOut != nil, 168 TTY: connector.TTY, 169 }, scheme.ParameterCodec) 170 171 return connector.DoConnect("POST", req.URL(), sizeQueue) 172 } 173 } 174 175 // GetDefaultExecFunc returns the default exec function. 176 func (connector *Connector) GetDefaultExecFunc(containerToAttach *corev1.Container, sizeQueue remotecommand.TerminalSizeQueue) func() error { 177 return func() error { 178 restClient, err := restclient.RESTClientFor(connector.Config) 179 if err != nil { 180 return err 181 } 182 183 req := restClient.Post(). 184 Resource("pods"). 185 Name(connector.Pod.Name). 186 Namespace(connector.Pod.Namespace). 187 SubResource("exec") 188 req.VersionedParams(&corev1.PodExecOptions{ 189 Container: containerToAttach.Name, 190 Command: connector.Command, 191 Stdin: connector.Stdin, 192 Stdout: connector.Out != nil, 193 Stderr: connector.ErrOut != nil, 194 TTY: connector.TTY, 195 }, scheme.ParameterCodec) 196 197 return connector.DoConnect("POST", req.URL(), sizeQueue) 198 } 199 } 200 201 // DoConnect executes attach to a running container with url. 202 func (connector *Connector) DoConnect(method string, url *url.URL, terminalSizeQueue remotecommand.TerminalSizeQueue) error { 203 exec, err := remotecommand.NewSPDYExecutor(connector.Config, method, url) 204 if err != nil { 205 return err 206 } 207 208 return exec.Stream(remotecommand.StreamOptions{ 209 Stdin: connector.In, 210 Stdout: connector.Out, 211 Stderr: connector.ErrOut, 212 Tty: connector.TTY, 213 TerminalSizeQueue: terminalSizeQueue, 214 }) 215 }