github.com/alibaba/sealer@v0.8.6-0.20220430115802-37a2bdaa8173/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 // then returns a TTY object based on connectOpts. 120 func (connector *Connector) SetTTY() TTY { 121 t := TTY{ 122 Out: connector.Out, 123 } 124 125 // Stdin is false, then tty and stdin both false 126 if !connector.Stdin { 127 connector.In = nil 128 connector.TTY = false 129 return t 130 } 131 132 t.In = connector.In 133 if !connector.TTY { 134 return t 135 } 136 137 // check whether t.In is a terminal 138 if !t.IsTerminalIn() { 139 connector.TTY = false 140 return t 141 } 142 143 t.Raw = true 144 145 return t 146 } 147 148 // GetDefaultAttachFunc returns the default attach function. 149 func (connector *Connector) GetDefaultAttachFunc(containerToAttach *corev1.Container, sizeQueue remotecommand.TerminalSizeQueue) func() error { 150 return func() error { 151 restClient, err := restclient.RESTClientFor(connector.Config) 152 if err != nil { 153 return err 154 } 155 156 req := restClient.Post(). 157 Resource("pods"). 158 Name(connector.Pod.Name). 159 Namespace(connector.Pod.Namespace). 160 SubResource("attach") 161 req.VersionedParams(&corev1.PodAttachOptions{ 162 Container: containerToAttach.Name, 163 Stdin: connector.Stdin, 164 Stdout: connector.Out != nil, 165 Stderr: connector.ErrOut != nil, 166 TTY: connector.TTY, 167 }, scheme.ParameterCodec) 168 169 return connector.DoConnect("POST", req.URL(), sizeQueue) 170 } 171 } 172 173 // GetDefaultExecFunc returns the default exec function. 174 func (connector *Connector) GetDefaultExecFunc(containerToAttach *corev1.Container, sizeQueue remotecommand.TerminalSizeQueue) func() error { 175 return func() error { 176 restClient, err := restclient.RESTClientFor(connector.Config) 177 if err != nil { 178 return err 179 } 180 181 req := restClient.Post(). 182 Resource("pods"). 183 Name(connector.Pod.Name). 184 Namespace(connector.Pod.Namespace). 185 SubResource("exec") 186 req.VersionedParams(&corev1.PodExecOptions{ 187 Container: containerToAttach.Name, 188 Command: connector.Command, 189 Stdin: connector.Stdin, 190 Stdout: connector.Out != nil, 191 Stderr: connector.ErrOut != nil, 192 TTY: connector.TTY, 193 }, scheme.ParameterCodec) 194 195 return connector.DoConnect("POST", req.URL(), sizeQueue) 196 } 197 } 198 199 // DoConnect executes attach to a running container with url. 200 func (connector *Connector) DoConnect(method string, url *url.URL, terminalSizeQueue remotecommand.TerminalSizeQueue) error { 201 exec, err := remotecommand.NewSPDYExecutor(connector.Config, method, url) 202 if err != nil { 203 return err 204 } 205 206 return exec.Stream(remotecommand.StreamOptions{ 207 Stdin: connector.In, 208 Stdout: connector.Out, 209 Stderr: connector.ErrOut, 210 Tty: connector.TTY, 211 TerminalSizeQueue: terminalSizeQueue, 212 }) 213 }