issh

package module
v0.0.0-...-870518d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 7, 2024 License: MIT Imports: 11 Imported by: 0

README

GoReportCard

go-interactive-ssh

Go interactive ssh client.

It makes it possible to run commands in remote shell and "Expect" each command's behaviors like checking output, handling errors and so on.

Support use-case is ssh access from Windows, MacOS or Linux as client and access to Linux as a remote host.

Install

go get -u "github.com/jlandowner/go-interactive-ssh"

Example

package main

import (
	"context"
	"log"

	"golang.org/x/crypto/ssh"

	issh "github.com/jlandowner/go-interactive-ssh"
)

func main() {
	ctx := context.Background()

	config := &ssh.ClientConfig{
		User:            "pi",
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		Auth: []ssh.AuthMethod{
			ssh.Password("raspberry"),
		},
	}

	// create client
	client := issh.NewClient(config, "raspberrypi.local", "22", []issh.Prompt{issh.DefaultPrompt})

	// give Commands to client and Run
	err := client.Run(ctx, commands())
	if err != nil {
		log.Fatal(err)
	}
	log.Println("OK")
}

// make Command structs executed sequentially in remote host.  
func commands() []*issh.Command {
	return []*issh.Command{
		issh.CheckUser("pi"),
		issh.NewCommand("pwd", issh.WithOutputLevelOption(issh.Output)),
		issh.ChangeDirectory("/tmp"),
		issh.NewCommand("ls -l", issh.WithOutputLevelOption(issh.Output)),
	}
}

Usage

Setup Client

Setup Client by NewClient().

func NewClient(sshconfig *ssh.ClientConfig, host string, port string, prompts []Prompt) *Client 

SSH client settings is the same as standard ssh.ClientConfig.

The last argument is []Prompt, which is a list of Prompt struct.

Prompt is used to confirm whether command execution is completed in the SSH login shell.

type Prompt struct {
    SufixPattern  byte
    SufixPosition int
}

Normally, a prompt is like pi@raspberrypi: ~ $, so '$' and '#' prompts are predefined in the package.

Client wait until each command outputs match this.

var (
	// DefaultPrompt is prompt pettern like "pi @ raspberrypi: ~ $"
	DefaultPrompt = Prompt {
		SufixPattern: '$',
		SufixPosition: 2,
	}
	// DefaultRootPrompt is prompt pettern like "pi @ raspberrypi: ~ $"
	DefaultRootPrompt = Prompt {
		SufixPattern: '#',
		SufixPosition: 2,
	}
)
Run by Command struct

All you have to do is just Run() with a list of commands you want to execute in a remote host.

func (c *Client) Run(ctx context.Context, cmds []*Command) error

The command is passed as a Command struct that contains some options, a callback function and also its results.

// Command has Input config and Output in remote host.
// Input is line of command execute in remote host.
// Callback is called after input command is finished. You can check whether Output is exepected in this function.
// NextCommand is called after Callback and called only Callback returns "true". NextCommand cannot has another NextCommand.
// ReturnCodeCheck is "true", Input is added ";echo $?" and check after Output is 0. Also you can manage retrun code in Callback.
// OutputLevel is logging level of command. Secret command should be set Silent
// Result is Command Output. You can use this in Callback, NextCommand, DefaultNextCommand functions.
type Command struct {
	Input           string
	Callback        func(c *Command) (bool, error)
	NextCommand     func(c *Command) *Command
	ReturnCodeCheck bool
	OutputLevel     OutputLevel
	Timeout         time.Duration
	Result          *CommandResult
}

You can generate a Command struct by NewCommand()

func NewCommand(input string, options ...Option) *Command

In addition to Input which is a command sent to remote shell, it can be set some options with a function that implements the Option interface named WithXXXOption().

func WithNoCheckReturnCodeOption() *withNoCheckReturnCode
func WithOutputLevelOption(v OutputLevel) *withOutputLevel
func WithTimeoutOption(v time.Duration) *withTimeout
func WithCallbackOption(v func (c *Command) (bool, error)) *withCallback
func WithNextCommandOption(v func (c *Command) * Command) *withNextCommand

By default, all Input is added "; echo $?" and the return code is checked. If you do not want to check the return code, such as when command has standard inputs, add WithNoCheckReturnCodeOption() option to NewCommand().

cmd := issh.NewCommand("ls -l")                                // input is "ls -l; echo $?" and checked if return code is 0
cmd := issh.NewCommand("ls -l", WithNoCheckReturnCodeOption()) // input is just "ls -l"

Expect

As a feature like "Expect", you can use standard outputs in Callback function.

You can describe a function executing after each commands (like checking output or handling errors executing), as Command struct that contains stdout is given by callback's argument.

Set Callback function by a option of NewCommand()

// WithCallbackOption is option function called after command is finished
func WithCallbackOption(v func(c *Command) (bool, error)) *withCallback

You can refer to the command execution result with c.Result in callback.

type Command struct {
	...
	Result          *CommandResult
}

type CommandResult struct {
	Output     []string
	Lines      int
	ReturnCode int
}

Plus, you can add more command that can be executed only when the callback function returns true.

// WithNextCommandOption is option function called after Callback func return true
func WithNextCommandOption(v func(c *Command) *Command) *withNextCommand

The summary is as follows.

  • WithCallbackOption(v func(c *Command) (bool, error))

    • Augument "c" is previous Command pointer. You can get command output in stdout by c.Result.Output.
    • Returned bool is whether run WithNextCommandOption function or not.
    • If err is not nil, issh.Run will exit. (Example see CheckUser)
  • WithNextCommandOption(v v func(c *Command) *Command)

    • Augument "c" is previous Command pointer.
    • Returned Command is executed after previous Command in remote host.
    • It's useful when you have to type sequentially in stdin. (Example see SwitchUser)

Documentation

Index

Constants

View Source
const (
	// DefaultTimeOut archeve, Command is canneled. You can change by WithTimeoutOption
	DefaultTimeOut = time.Second * 5
)

Variables

View Source
var (
	// DefaultCallback is called after Command and just sleep in a second. You can change by WithCallbackOption
	DefaultCallback = func(c *Command) (bool, error) {
		time.Sleep(time.Millisecond * 500)
		if c.Result.ReturnCode != 0 {
			return false, fmt.Errorf("cmd [%v] %v", c.Input, ErrReturnCodeNotZero)
		}
		return true, nil
	}
	// ErrReturnCodeNotZero is error in command exit with non zero
	ErrReturnCodeNotZero = errors.New("return code is not 0")
)
View Source
var (
	// DefaultPrompt is prompt pettern like "pi@raspberrypi:~ $ "
	DefaultPrompt = Prompt{
		SufixPattern:  '$',
		SufixPosition: 2,
	}
	// DefaultRootPrompt is prompt pettern like "# "
	DefaultRootPrompt = Prompt{
		SufixPattern:  '#',
		SufixPosition: 2,
	}
)

Functions

func WithCallbackOption

func WithCallbackOption(v func(c *Command) (bool, error)) *withCallback

WithCallbackOption is option function called after command is finished

func WithNextCommandOption

func WithNextCommandOption(v func(c *Command) *Command) *withNextCommand

WithNextCommandOption is option function called after Callback func return true

func WithNoCheckReturnCodeOption

func WithNoCheckReturnCodeOption() *withNoCheckReturnCode

NoCheckReturnCodeOption is option whether add ";echo $?" and check return code after command

func WithOutputLevelOption

func WithOutputLevelOption(v OutputLevel) *withOutputLevel

WithOutputLevelOption is option of command log print

func WithTimeoutOption

func WithTimeoutOption(v time.Duration) *withTimeout

WithTimeoutOption is option time.Duration to command timeout

Types

type Client

type Client struct {
	Sshconfig *ssh.ClientConfig
	Host      string
	Port      string
	Prompt    []Prompt
}

Client has configuration to interact a host

func NewClient

func NewClient(sshconfig *ssh.ClientConfig, host string, port string, prompts []Prompt) *Client

NewClient return new go-interactive-ssh client prompts []Prompt is list of all expected prompt pettern. for example if you use normal user and switch to root user, '$' and '#' prompts must be given.

func (*Client) Run

func (c *Client) Run(ctx context.Context, cmds []*Command) error

Run execute given commands in remote host

type Command

type Command struct {
	Input           string
	Callback        func(c *Command) (bool, error)
	NextCommand     func(c *Command) *Command
	ReturnCodeCheck bool
	OutputLevel     OutputLevel
	Timeout         time.Duration
	Result          *CommandResult
}

Command has Input config and Output in remote host. Input is line of command execute in remote host. Callback is called after input command is finished. You can check whether Output is exepected in this function. NextCommand is called after Callback and called only Callback returns "true". NextCommand cannot has another NextCommand. ReturnCodeCheck is "true", Input is added ";echo $?" and check after Output is 0. Also you can manage retrun code in Callback. OutputLevel is logging level of command. Secret command should be set Silent Result is Command Output. You can use this in Callback, NextCommand, DefaultNextCommand functions.

func ChangeDirectory

func ChangeDirectory(tgtdir string) *Command

ChangeDirectory run "cd" command in remote host

func CheckUser

func CheckUser(expectUser string) *Command

CheckUser check current login user is expected in remote host

func Exit

func Exit() *Command

Exit run "exit" command in remote host

func NewCommand

func NewCommand(input string, options ...Option) *Command

NewCommand return Command with given options

func SwitchUser

func SwitchUser(user, password string, newUserPrompt Prompt) *Command

SwitchUser run "su - xxx" command in remote host

type CommandResult

type CommandResult struct {
	Output     []string
	Lines      int
	ReturnCode int
}

CommandResult has command output and return code in remote host

type Option

type Option interface {
	Apply(*Command)
}

Option is Command struct option

type OutputLevel

type OutputLevel int

OutputLevel set logging level of command

const (
	// Silent logs nothing
	Silent OutputLevel = iota
	// Info logs only start and end of command
	Info
	// Output logs command output in remote host
	Output
)

type Prompt

type Prompt struct {
	SufixPattern  byte
	SufixPosition int
}

Prompt represent ssh prompt pettern used in check whether command is finished. Example:

Terminal Prompt "pi@raspberrypi:~ $ "
  SufixPattern='$' (rune to byte)
  SufixPosition=2 ($ + space)

When you use multi prompt such as Root user (often '#'), you must give all prompt pattern before Run

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL