go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/_motor/providers/ssh/command.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package ssh
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"time"
    10  
    11  	"github.com/rs/zerolog/log"
    12  	"go.mondoo.com/cnquery/motor/providers/os"
    13  	"golang.org/x/crypto/ssh"
    14  )
    15  
    16  type Command struct {
    17  	os.Command
    18  	SSHProvider *Provider
    19  }
    20  
    21  func (c *Command) Exec(command string) (*os.Command, error) {
    22  	c.Command.Command = command
    23  	c.Command.Stats.Duration = time.Since(c.Command.Stats.Start)
    24  
    25  	stdoutBuffer := &bytes.Buffer{}
    26  	stderrBuffer := &bytes.Buffer{}
    27  
    28  	c.Command.Stdout = stdoutBuffer
    29  	c.Command.Stderr = stderrBuffer
    30  
    31  	if c.SSHProvider.SSHClient == nil {
    32  		return nil, errors.New("ssh session not established")
    33  	}
    34  
    35  	session, err := c.SSHProvider.SSHClient.NewSession()
    36  	if err != nil {
    37  		log.Debug().Msg("could not open new session, try to re-establish connection")
    38  		err = c.SSHProvider.Reconnect()
    39  		if err != nil {
    40  			return nil, err
    41  		}
    42  
    43  		session, err = c.SSHProvider.SSHClient.NewSession()
    44  		if err != nil {
    45  			return nil, err
    46  		}
    47  	}
    48  	defer session.Close()
    49  
    50  	// start ssh call
    51  	session.Stdout = stdoutBuffer
    52  	session.Stderr = stderrBuffer
    53  	err = session.Run(c.Command.Command)
    54  	c.Command.Stats.Duration = time.Since(c.Command.Stats.Start)
    55  
    56  	// command completed successfully, great :-)
    57  	if err == nil {
    58  		return &c.Command, nil
    59  	}
    60  
    61  	// if the program failed, we do not return err but its exit code
    62  	var e *ssh.ExitError
    63  	match := errors.As(err, &e)
    64  	if match {
    65  		c.Command.ExitStatus = e.ExitStatus()
    66  		return &c.Command, nil
    67  	}
    68  
    69  	// all other errors are real errors and not expected
    70  	return &c.Command, err
    71  }