github.com/Equinix-Metal/virtlet@v1.5.2-0.20210807010419-342346535dc5/tests/e2e/framework/ssh_interface.go (about)

     1  /*
     2  Copyright 2017 Mirantis
     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 framework
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"strings"
    24  
    25  	"golang.org/x/crypto/ssh"
    26  
    27  	"github.com/Equinix-Metal/virtlet/pkg/tools"
    28  )
    29  
    30  type sshInterface struct {
    31  	vmInterface *VMInterface
    32  	client      *ssh.Client
    33  	fwStopCh    chan struct{}
    34  }
    35  
    36  func newSSHInterface(vmInterface *VMInterface, user, secret string) (*sshInterface, error) {
    37  	var authMethod ssh.AuthMethod
    38  	key := trimBlock(secret)
    39  	signer, err := ssh.ParsePrivateKey([]byte(key))
    40  	if err != nil {
    41  		authMethod = ssh.Password(secret)
    42  	} else {
    43  		authMethod = ssh.PublicKeys(signer)
    44  	}
    45  
    46  	config := &ssh.ClientConfig{
    47  		User: user,
    48  		Auth: []ssh.AuthMethod{authMethod},
    49  	}
    50  
    51  	vmPod, err := vmInterface.Pod()
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	ports := []*tools.ForwardedPort{
    57  		{RemotePort: 22},
    58  	}
    59  	fwStopCh, err := vmPod.PortForward(ports)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	sshClient, err := ssh.Dial("tcp", fmt.Sprintf("localhost:%d", ports[0].LocalPort), config)
    65  	if err != nil {
    66  		defer close(fwStopCh)
    67  		return nil, err
    68  	}
    69  
    70  	return &sshInterface{
    71  		vmInterface: vmInterface,
    72  		client:      sshClient,
    73  		fwStopCh:    fwStopCh,
    74  	}, nil
    75  }
    76  
    77  func (si *sshInterface) Run(stdin io.Reader, stdout, stderr io.Writer, command ...string) error {
    78  	cmd, err := si.Start(stdin, stdout, stderr, command...)
    79  	if err != nil {
    80  		return err
    81  	}
    82  
    83  	return cmd.Wait()
    84  }
    85  
    86  func (si *sshInterface) Start(stdin io.Reader, stdout, stderr io.Writer, command ...string) (Command, error) {
    87  	session, err := si.client.NewSession()
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	session.Stdout = stdout
    93  	session.Stderr = stderr
    94  	session.Stdin = stdin
    95  
    96  	for i, arg := range command {
    97  		if i == 0 {
    98  			continue
    99  		}
   100  		command[i] = fmt.Sprintf("'%s'", strings.Replace(arg, "'", "\\'", -1))
   101  	}
   102  	if err := session.Start(strings.Join(command, " ")); err != nil {
   103  		defer session.Close()
   104  		return nil, err
   105  	}
   106  
   107  	return sshCommand{session: session}, err
   108  }
   109  
   110  func (si *sshInterface) Close() error {
   111  	if si.client != nil {
   112  		defer close(si.fwStopCh)
   113  		err := si.client.Close()
   114  		si.client = nil
   115  		return err
   116  	}
   117  	return nil
   118  }
   119  
   120  // Logs is a placeholder for fulfilling Executor interface
   121  func (*sshInterface) Logs() (string, error) {
   122  	return "", errors.New("not implemented")
   123  }
   124  
   125  type sshCommand struct {
   126  	session *ssh.Session
   127  }
   128  
   129  var _ Command = &sshCommand{}
   130  
   131  func (sc sshCommand) Wait() error {
   132  	err := sc.session.Wait()
   133  	defer sc.session.Close()
   134  	if err != nil {
   135  		if s, ok := err.(*ssh.ExitError); ok {
   136  			return CommandError{ExitCode: s.ExitStatus()}
   137  		}
   138  		return err
   139  	}
   140  
   141  	return nil
   142  }
   143  
   144  func (sc sshCommand) Kill() error {
   145  	defer sc.session.Close()
   146  	return sc.session.Signal(ssh.SIGKILL)
   147  }