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  }