logrun

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Feb 28, 2019 License: MIT Imports: 4 Imported by: 0

README

Logrun

Logrun is a Go (golang) package that wraps the standard Go os/exec package and golang.org/x/crypto/ssh package to run commands either locally or over ssh while capturing stdout, stderr, exit codes. Commands are optionally logged using a specified logging function.

GoDoc

Features

  • Run commands either locally or remotely over SSH.
  • Run commands in a shell or directly ala glibc's exec().
  • Capture stdout, stderr, and exit code.
  • Output can be redirected to any Writer.
  • Commands are logged using a specified logging function.

Documentation

Documentation can be found at GoDoc

Installation

Install go-logrun using the "go get" command:

$ go get github.com/apatters/go-logrun

Example

package main

import (
	"fmt"
	"log"

	"github.com/apatters/go-logrun"
)

func main() {
	locRunner := logrun.NewLocalLogRun(logrun.LocalConfig{
		LogFunc: log.Println,
	})
	rmtRunner, err := logrun.NewRemoteLogRun(logrun.RemoteConfig{
		LogFunc: log.Println,
		// We are faking credentials here to run in a test
		// environment.
		Credentials: logrun.Credentials{
			Hostname: "localhost",
		},
	})
	if err != nil {
		// Report error.
	}

	fmt.Println(`Run the "seq 1 3" command locally.`)
	stdout, stderr, code := locRunner.Run("seq", "1", "3")
	fmt.Printf("Stdout = %q\n", stdout)
	fmt.Printf("Stderr = %q\n", stderr)
	fmt.Printf("Exit code = %d\n", code)
	fmt.Println()

	fmt.Println(`Run the "seq 1 3" command remotely.`)
	stdout, stderr, code = rmtRunner.Run("seq", "1", "3")
	fmt.Printf("Stdout = %q\n", stdout)
	fmt.Printf("Stderr = %q\n", stderr)
	fmt.Printf("Exit code = %d\n", code)
	fmt.Println()

	fmt.Println(`Run the "seq 1 3 | grep 2" command locally.`)
	stdout, stderr, code = locRunner.Shell("seq 1 3 | grep 2")
	fmt.Printf("Stdout = %q\n", stdout)
	fmt.Printf("Stderr = %q\n", stderr)
	fmt.Printf("Exit code = %d\n", code)
	fmt.Println()

	fmt.Println(`Run the "seq 1 3 | grep 2" command remotely.`)
	stdout, stderr, code = rmtRunner.Shell("seq 1 3 | grep 2")
	fmt.Printf("Stdout = %q\n", stdout)
	fmt.Printf("Stderr = %q\n", stderr)
	fmt.Printf("Exit code = %d\n", code)
}

Output:

Run the "seq 1 3" command locally.
2019/02/28 14:12:10 seq 1 3
Stdout = "1\n2\n3\n"
Stderr = ""
Exit code = 0

Run the "seq 1 3" command remotely.
2019/02/28 14:12:10 ssh andrew@localhost seq 1 3
Stdout = "1\n2\n3\n"
Stderr = ""
Exit code = 0

Run the "seq 1 3 | grep 2" command locally.
2019/02/28 14:12:10 /bin/sh -c "seq 1 3 | grep 2"
Stdout = "2\n"
Stderr = ""
Exit code = 0

Run the "seq 1 3 | grep 2" command remotely.
2019/02/28 14:12:10 ssh andrew@localhost /bin/sh -c "seq 1 3 | grep 2"
Stdout = "2\n"
Stderr = ""
Exit code = 0

License

The go-logrun package is available under the MITLicense.

Thanks

Thanks to Secure64 for contributing this code.

Documentation

Overview

Package logrun is a Go (golang) package that wraps the standard Go os/exec and golang.org/x/crypto/ssh packages to run commands either locally or over ssh while capturing stdout, stderr, and exit codes. Commands are logged using a specified logging function.

Example
package main

// This example illustrates how to use the standard log runner to run
// local commands without using a constructor. It is is useful when
// you do not need to customize behavior.

import (
	"fmt"
	"os"

	"github.com/apatters/go-logrun"
)

// Custom logging function.
func MyLogger(v ...interface{}) {
	fmt.Printf("Command: ")
	fmt.Println(v...)
}

func main() {
	// Set our custom logger.
	logrun.SetLogFunc(MyLogger)
	defer logrun.SetLogFunc(logrun.DefaultLogFunc)

	fmt.Println("Execute a command using Run().")
	stdout, stderr, code := logrun.Run("/usr/bin/seq", "1", "3")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("code = %d\n", code)
	fmt.Println()

	fmt.Println("Execute a command using Shell().")
	stdout, stderr, code = logrun.Shell("seq 1 3 | grep 2")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("code = %d\n", code)
	fmt.Println()

	fmt.Println("See if a file exists.")
	exists, err := logrun.FileExists("/bin/true")
	if err != nil {
		// report error.
	}
	fmt.Printf("/bin/true exists: %t\n", exists)
	exists, err = logrun.FileExists("/bin/xyzzy")
	if err != nil {
		// report error.
	}
	fmt.Printf("/bin/xyzzy exists: %t\n", exists)
	fmt.Println()

	fmt.Println("See if a directory exists.")
	exists, err = logrun.DirExists("/etc")
	if err != nil {
		// report error.
	}
	fmt.Printf("/bin/etc exists: %t\n", exists)
	exists, err = logrun.FileExists("/xyzzy")
	if err != nil {
		// report error.
	}
	fmt.Printf("/xyzzy exists: %t\n", exists)
	fmt.Println()

	fmt.Println("List files using a shell glob pattern.")
	paths, err := logrun.Glob("/etc/passwd*")
	if err != nil {
		// Report the error.
	}
	for _, path := range paths {
		fmt.Println(path)
	}
	fmt.Println()

	fmt.Println("Copy the contents of a remote directory to a local temporary directory.")
	destDir := "/tmp/go-logrun-XXXXX"
	err = os.MkdirAll(destDir, 0755)
	if err != nil {
		// Report the error
		return
	}
	defer os.RemoveAll(destDir)
	err = logrun.Rsync("localhost:/etc/cron.daily/", fmt.Sprintf("%s/", destDir))
	if err != nil {
		// Report the error.
	}
	fmt.Println()

	fmt.Println("Log commands but do not execute them.")
	logrun.SetDryrun(true)
	stdout, stderr, code = logrun.Run("/usr/bin/seq", "1", "3")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("code = %d\n", code)
	stdout, stderr, code = logrun.Run("/bin/false")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("code = %d\n", code)
	fmt.Println()

	fmt.Println("Disable logging")
	logrun.SetLogFunc(logrun.DiscardLogFunc)
	stdout, stderr, code = logrun.Run("/usr/bin/seq", "1", "3")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("code = %d\n", code)

}
Output:
Execute a command using Run().
Command: /usr/bin/seq 1 3
stdout = "1\n2\n3\n"
stderr = ""
code = 0

Execute a command using Shell().
Command: /bin/sh -c "seq 1 3 | grep 2"
stdout = "2\n"
stderr = ""
code = 0

See if a file exists.
Command: /usr/bin/stat --dereference --format %n:%F /bin/true
/bin/true exists: true
Command: /usr/bin/stat --dereference --format %n:%F /bin/xyzzy
/bin/xyzzy exists: false

See if a directory exists.
Command: /usr/bin/stat --dereference --format %n:%F /etc
/bin/etc exists: true
Command: /usr/bin/stat --dereference --format %n:%F /xyzzy
/xyzzy exists: false

List files using a shell glob pattern.
Command: /bin/sh -c "/bin/ls -1 --directory /etc/passwd*"
/etc/passwd
/etc/passwd-

Copy the contents of a remote directory to a local temporary directory.
Command: /usr/bin/rsync --rsh ssh -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o GlobalKnownHostsFile=/dev/null --recursive --links --times localhost:/etc/cron.daily/ /tmp/go-logrun-XXXXX/

Log commands but do not execute them.
Command: /usr/bin/seq 1 3
stdout = ""
stderr = ""
code = 0
Command: /bin/false
stdout = ""
stderr = ""
code = 0

Disable logging
stdout = ""
stderr = ""
code = 0

Index

Examples

Constants

View Source
const (
	// ExitOK is the exit code indicating that no unrecovered
	// error occurred.
	ExitOK int = iota

	// ExitErrorInternal is the exit code indicating that some
	// internal error occurred where the condition would only make
	// sense to the programmer.
	ExitErrorInternal

	// ExitErrorUsage is the exit code indicating that some sort
	// of command-line usage error occurred, e.g., an invalid
	// command-line option or missing arguments was specified.
	ExitErrorUsage

	// ExitErrorUser is the exit code indicating that some sort of
	// user error occurred, e.g., the program is being used
	// incorrectly.
	ExitErrorUser

	// ExitErrorIO is the exit code indicating that some sort of
	// I/O error occurred, e.g., a truncated write occurred.
	ExitErrorIO

	// ExitErrorPerm is the exit code indicatating that some sort
	// of permission error occurred, e.g, super-user access to a
	// file is needed.
	ExitErrorPerm

	// ExitErrorInvalid is the exit code indicating that something
	// was invalid, e.g., an incorrect parameter was passed.
	ExitErrorInvalid

	// ExitErrorExists is the exit code indicating that something
	// already exists, e.g., the file that you are trying to
	// create already exists.
	ExitErrorExists

	// ExitErrorNotFound is the exit code indicating that
	// something cannot be found, e.g., you are trying to read a
	// file that does not exist.
	ExitErrorNotFound

	// ExitErrorSignal is the exit code indicating that an
	// untrapped or invalid signal occurred. e.g., an unmasked
	// SIGTERM was received.
	ExitErrorSignal

	// ExitErrorRuntime is the exit code indicating that some sort
	// of run-time error occurred. This exit code is mostly a
	// catchall.
	ExitErrorRuntime
)

Suggested exit code definitions.

Variables

View Source
var (
	// DefaultLogFunc is the function called when not specified in
	// the LocalConfig or RemoteConfig objects.  The function's
	// type must match LogFunc.
	DefaultLogFunc = DiscardLogFunc

	// FileExistsCmd is the external command used to determine if
	// a file exists or not. This command has been tested on
	// RHEL/CentOS 7 and Ubuntu 18.04.
	FileExistsCmd = "/usr/bin/stat"

	// FileExistsCmdOptions are the command-line options added to
	// FileExistsCmd used to determine if a file exists or
	// not. This command and options has been tested on
	// RHEL/CentOS 7 and Ubuntu 18.04.
	FileExistsCmdOptions = []string{
		"--dereference",
		"--format",
		"%n:%F",
	}

	// DirExistsCmd is the external command used to determine if
	// a directory exists or not. This command has been tested on
	// RHEL/CentOS 7 and Ubuntu 18.04.
	DirExistsCmd = "/usr/bin/stat"

	// DirExistsCmdOptions are the command-line options added to
	// DirExistsCmd used to determine if a file directory or
	// not. This command and options has been tested on
	// RHEL/CentOS 7 and Ubuntu 18.04.
	DirExistsCmdOptions = []string{
		"--dereference",
		"--format",
		"%n:%F",
	}

	// GlobCmd is the external command used to return a list of
	// paths that match a shell glob pattern.  This command has
	// been tested on RHEL/CentOS 7 and Ubuntu 18.04.
	GlobCmd = "/bin/ls"

	// GlobCmdOptions are the command-line options added to
	// GlobCmd used to return a list of paths that match a shell
	// glob pattern. This command and options has been tested on
	// RHEL/CentOS 7 and Ubuntu 18.04.
	GlobCmdOptions = []string{
		"-1",
		"--directory",
	}

	// RsyncCmd is the external command used to copy a directory
	// or file to or from a local or remote destination. This
	// command has been tested on RHEL/CentOS 7 and Ubuntu 18.04.
	RsyncCmd = "/usr/bin/rsync"

	// RsyncCmdOptions are the command-line options added to
	// RsyncCmd used to opy a directory or file to or from a local
	// or remote destination. This command and options has been
	// tested on RHEL/CentOS 7 and Ubuntu 18.04.
	RsyncCmdOptions = []string{
		"--rsh",
		"ssh -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o GlobalKnownHostsFile=/dev/null",
		"--recursive",
		"--links",
		"--times",
	}
)
View Source
var (
	// ExitErrorExecute is returned when the Run() or Shell()
	// methods has an issue with executing the command, It is not
	// the non-zero return code of the command itself. An issue is
	// typically either: a permission problem when running the
	// command, running a command that does not exist, using the
	// wrong path to a command, or using missing/incorrect
	// credentials.
	ExitErrorExecute = ExitErrorInternal
)

Functions

func DirExists

func DirExists(dirname string) (bool, error)

DirExists returns true if dirname exists and is a directory using the standard log runner's DirExists() method..

func DiscardLogFunc

func DiscardLogFunc(v ...interface{})

DiscardLogFunc is used to disable logging. It is the default logging function.

func FileExists

func FileExists(filename string) (bool, error)

FileExists returns true if filename exists and is a regular file using the standard log runner's FileExist() method.

func FormatRun

func FormatRun(cmd string, args ...string) string

FormatRun returns a string representation of the what command would be run using the standard runner's Run() method. Useful for logging commands.

func FormatShell

func FormatShell(cmd string) string

FormatShell returns a string representation of the what command would be run using the standard runner's Shell() method. Useful for logging commands.

func Glob

func Glob(pattern string) ([]string, error)

Glob returns a list of files matching a glob pattern using the standard log runner's Glob() method.

func Rsync

func Rsync(src string, dest string) error

Rsync copies files/directories using the rsync command by calling the standard log runner's Rsync() method().

func Run

func Run(cmd string, args ...string) (string, string, int)

Run runs a command like glibc's exec() call using the standard runner. It returns the standard out, standard error, and exit code of the command when it completes.

func SetDryrun

func SetDryrun(dryrun bool)

SetDryrun is used to enable/disable whether commands are just logged and not executed by calling the run logger's SetDryrun() method.

func SetLogFunc

func SetLogFunc(f LogFunc)

SetLogFunc sets the logging function used to log commands by calling the standard run logger's SetLogFunc() method.

func Shell

func Shell(cmd string) (string, string, int)

Shell runs a command in a shell using the standard runner. The command is passed to the shell as the -c option, so just about any shell code that can be used on the command-line will be passed to it. It returns the standard out, standard error, and exit code of the command when it completes

Types

type Credentials

type Credentials struct {
	// Hostname is either the hostname or IP of the remote host.
	Hostname string

	// Port is the port used to connect to the ssh server on the
	// remote host.
	Port int

	// Username is the account name used to authenticate on the
	// remote host.
	Username string

	// Password is password used to authenticate on the remote
	// host. Not needed if using PrivateKeyFilename.
	Password string

	// PrivateKeyFilename is the full path the SSH private key
	// used to authenticate with the remote host.  Not used if
	// Password is specified. You must use ssh-agent or something
	// similar to provide the passphrase if the key is passphrase
	// protected.
	PrivateKeyFilename string
}

Credentials contains needed credentials to SSH to a host. It can use either a password or SSH private key.

type LocalConfig

type LocalConfig struct {
	// LogFunc is used to set the logging function used to log a
	// command. The function is typically something like
	// log.Println() or logrus.Debug. A custom function of type
	// LogFunc can also be used.
	LogFunc LogFunc

	// ShellExecutable is the full path to the shell to be run
	// when executing shell commands.
	ShellExecutable string

	// Env specifies the environment of the process.
	// Each entry is of the form "key=value".
	// If Env is nil, the new process uses the current process's
	// environment.
	// If Env contains duplicate environment keys, only the last
	// value in the slice for each duplicate key is used.
	Env []string

	// Dir specifies the working directory of the command.  If Dir
	// is the empty string, Run runs the command in the calling
	// process's current directory.
	Dir string

	// Stdin specifies the process's standard input.
	//
	// If Stdin is nil, the process reads from the null device
	// (os.DevNull).
	//
	// If Stdin is an *os.File, the process's standard input is connected
	// directly to that file.
	//
	// Otherwise, during the execution of the command a separate
	// goroutine reads from Stdin and delivers that data to the
	// command over a pipe. In this case, Wait does not complete
	// until the goroutine stops copying, either because it has
	// reached the end of Stdin (EOF or a read error) or because
	// writing to the pipe returned an error.
	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer

	// Dryrun enables/disables the execution of commands. If
	// Dryrun is true, the command is only logged.
	Dryrun bool
}

LocalConfig is used to set options in the NewLocalLogRun constructor.

type LogFunc

type LogFunc func(...interface{})

LogFunc is the type for the function that will be called to log the command.

type LogRun

type LogRun struct {
	Runner run.Runner

	Dryrun bool
	// contains filtered or unexported fields
}

LogRun encapsulates a logger used to log and run and either a local or remote command.

func NewLocalLogRun

func NewLocalLogRun(config LocalConfig) *LogRun

NewLocalLogRun is the constructor for LogRun used to log and run a local command.

func NewRemoteLogRun

func NewRemoteLogRun(config RemoteConfig) (*LogRun, error)

NewRemoteLogRun is the constructor for RemoteLogRun used to log and run a remote command.

func (*LogRun) DirExists

func (r *LogRun) DirExists(dirname string) (bool, error)

DirExists returns true if dirname exists and is a directory. This method is more suited to run remotely.

Example
package main

import (
	"fmt"

	"github.com/apatters/go-conlog"
	"github.com/apatters/go-logrun"
)

func main() {
	// Set up logger to output at debug level and add a log level
	// prefix using title case.
	formatter := conlog.NewStdFormatter()
	formatter.Options.LogLevelFmt = conlog.LogLevelFormatLongTitle
	conlog.SetFormatter(formatter)
	conlog.SetLevel(conlog.DebugLevel)

	fmt.Println(`See if /etc exists on the local host.`)
	runner := logrun.NewLocalLogRun(logrun.LocalConfig{
		LogFunc: conlog.Debug,
	})
	exists, err := runner.DirExists("/etc")
	if err != nil {
		// Report the error.
	}
	fmt.Printf("exists = %t\n", exists)
	fmt.Println()

	fmt.Println(`See if /etc exists on the remote host.`)
	runner, err = logrun.NewRemoteLogRun(logrun.RemoteConfig{
		LogFunc: conlog.Debug,
		// We are faking credentials here to run in a test
		// environment.
		Credentials: logrun.Credentials{
			Hostname: "localhost",
		},
	})
	if err != nil {
		// Report the error.
	}
	exists, err = runner.DirExists("/etc")
	if err != nil {
		// Report the error.
	}
	fmt.Printf("exists = %t\n", exists)

}
Output:
See if /etc exists on the local host.
Debug /usr/bin/stat --dereference --format %n:%F /etc
exists = true

See if /etc exists on the remote host.
Debug ssh buildman@localhost /usr/bin/stat --dereference --format %n:%F /etc
exists = true

func (*LogRun) FileExists

func (r *LogRun) FileExists(filename string) (bool, error)

FileExists returns true if filename exists and is a regular file. This function is more suited to run remotely.

Example
package main

import (
	"fmt"

	"github.com/apatters/go-conlog"
	"github.com/apatters/go-logrun"
)

func main() {
	// Set up logger to output at debug level and add a log level
	// prefix using title case.
	formatter := conlog.NewStdFormatter()
	formatter.Options.LogLevelFmt = conlog.LogLevelFormatLongTitle
	conlog.SetFormatter(formatter)
	conlog.SetLevel(conlog.DebugLevel)

	fmt.Println(`See if /bin/true exists on the local host.`)
	runner := logrun.NewLocalLogRun(logrun.LocalConfig{
		LogFunc: conlog.Debug,
	})
	exists, err := runner.FileExists("/bin/true")
	if err != nil {
		// Report the error.
	}
	fmt.Printf("exists = %t\n", exists)
	fmt.Println()

	fmt.Println(`See if /bin/true exists on the remote host.`)
	runner, err = logrun.NewRemoteLogRun(logrun.RemoteConfig{
		LogFunc: conlog.Debug,
		// We are faking credentials here to run in a test
		// environment.
		Credentials: logrun.Credentials{
			Hostname: "localhost",
		},
	})
	if err != nil {
		// Report the error.
	}
	exists, err = runner.FileExists("/bin/true")
	if err != nil {
		// Report the error.
	}
	fmt.Printf("exists = %t\n", exists)

}
Output:
See if /bin/true exists on the local host.
Debug /usr/bin/stat --dereference --format %n:%F /bin/true
exists = true

See if /bin/true exists on the remote host.
Debug ssh buildman@localhost /usr/bin/stat --dereference --format %n:%F /bin/true
exists = true

func (*LogRun) FormatRun

func (r *LogRun) FormatRun(cmd string, args ...string) string

FormatRun returns a string representation of the command that would be executed using Run().

Example
package main

import (
	"fmt"

	"github.com/apatters/go-conlog"
	"github.com/apatters/go-logrun"
)

func main() {
	// Set up logger to output at debug level and add a log level
	// prefix using title case.
	formatter := conlog.NewStdFormatter()
	formatter.Options.LogLevelFmt = conlog.LogLevelFormatLongTitle
	conlog.SetFormatter(formatter)
	conlog.SetLevel(conlog.DebugLevel)

	fmt.Println("Get the log output of local command using Run().")
	runner := logrun.NewLocalLogRun(logrun.LocalConfig{
		LogFunc: conlog.Debug,
	})
	cmdLog := runner.FormatRun("seq", "1", "3")
	fmt.Printf("cmdLog = %q\n", cmdLog)
	fmt.Println()

	fmt.Println("Get the log output of remote command using Run().")
	runner, err := logrun.NewRemoteLogRun(logrun.RemoteConfig{
		LogFunc: conlog.Debug,
		// We are faking credentials here to run in a test
		// environment.
		Credentials: logrun.Credentials{
			Hostname: "localhost",
		},
	})
	if err != nil {
		// Report error
	}
	cmdLog = runner.FormatRun("seq", "1", "3")
	fmt.Printf("cmdLog = %q\n", cmdLog)

}
Output:
Get the log output of local command using Run().
cmdLog = "seq 1 3"

Get the log output of remote command using Run().
cmdLog = "ssh buildman@localhost seq 1 3"

func (*LogRun) FormatShell

func (r *LogRun) FormatShell(cmd string) string

FormatShell returns a string representation of the command that would be executed using Shell().

Example
package main

import (
	"fmt"

	"github.com/apatters/go-conlog"
	"github.com/apatters/go-logrun"
)

func main() {
	// Set up logger to output at debug level and add a log level
	// prefix using title case.
	formatter := conlog.NewStdFormatter()
	formatter.Options.LogLevelFmt = conlog.LogLevelFormatLongTitle
	conlog.SetFormatter(formatter)
	conlog.SetLevel(conlog.DebugLevel)

	fmt.Println("Get the log output of a local command using Shell().")
	runner := logrun.NewLocalLogRun(logrun.LocalConfig{
		LogFunc: conlog.Debug,
	})
	cmdLog := runner.FormatShell("seq 1 3")
	fmt.Printf("cmdLog = %q\n", cmdLog)
	fmt.Println()

	fmt.Println("Get the log output of a remote command using Shell().")
	runner, err := logrun.NewRemoteLogRun(logrun.RemoteConfig{
		LogFunc: conlog.Debug,
		// We are faking credentials here to run in a test
		// environment.
		Credentials: logrun.Credentials{
			Hostname: "localhost",
		},
	})
	if err != nil {
		// Report error
	}
	cmdLog = runner.FormatShell("seq 1 3")
	fmt.Printf("cmdLog = %q\n", cmdLog)

}
Output:
Get the log output of a local command using Shell().
cmdLog = "/bin/sh -c \"seq 1 3\""

Get the log output of a remote command using Shell().
cmdLog = "ssh buildman@localhost /bin/sh -c \"seq 1 3\""

func (*LogRun) Glob

func (r *LogRun) Glob(pattern string) ([]string, error)

Glob returns a list of files matching a shell glob pattern. This method is more suited to run remotely.

Example
package main

import (
	"fmt"

	"github.com/apatters/go-conlog"
	"github.com/apatters/go-logrun"
)

func main() {
	// Set up logger to output at debug level and add a log level
	// prefix using title case.
	formatter := conlog.NewStdFormatter()
	formatter.Options.LogLevelFmt = conlog.LogLevelFormatLongTitle
	conlog.SetFormatter(formatter)
	conlog.SetLevel(conlog.DebugLevel)

	fmt.Println(`Glob local passwd files`)
	runner := logrun.NewLocalLogRun(logrun.LocalConfig{
		LogFunc: conlog.Debug,
	})
	paths, err := runner.Glob("/etc/passwd*")
	if err != nil {
		// Report the error.
	}
	for _, path := range paths {
		fmt.Println(path)
	}
	fmt.Println()

	fmt.Println(`Glob remote passwd files.`)
	runner, err = logrun.NewRemoteLogRun(logrun.RemoteConfig{
		LogFunc: conlog.Debug,
		// We are faking credentials here to run in a test
		// environment.
		Credentials: logrun.Credentials{
			Hostname: "localhost",
		},
	})
	if err != nil {
		// Report the error.
	}
	paths, err = runner.Glob("/etc/passwd*")
	if err != nil {
		// Report the error.
	}
	for _, path := range paths {
		fmt.Println(path)
	}
	fmt.Println()

}
Output:
Glob local passwd files
Debug /bin/sh -c "/bin/ls -1 --directory /etc/passwd*"
/etc/passwd
/etc/passwd-

Glob remote passwd files.
Debug ssh buildman@localhost /bin/sh -c "/bin/ls -1 --directory /etc/passwd*"
/etc/passwd
/etc/passwd-

func (*LogRun) Rsync

func (r *LogRun) Rsync(src string, dest string) error

Rsync copies files/directories to or from local and remote locations using the rsync command. This method is more suited to run locally.

Example
package main

import (
	"fmt"
	"os"

	"github.com/apatters/go-conlog"
	"github.com/apatters/go-logrun"
)

func main() {
	// Set up logger to output at debug level and add a log level
	// prefix using title case.
	formatter := conlog.NewStdFormatter()
	formatter.Options.LogLevelFmt = conlog.LogLevelFormatLongTitle
	conlog.SetFormatter(formatter)
	conlog.SetLevel(conlog.DebugLevel)

	fmt.Println(`Copy the contents of directory on a remote host to local temporary directory.`)
	destDir := "/tmp/go-logrun-XXXXX"
	err := os.MkdirAll(destDir, 0755)
	if err != nil {
		// Report the error
		return
	}
	defer os.RemoveAll(destDir)

	runner := logrun.NewLocalLogRun(logrun.LocalConfig{
		LogFunc: conlog.Debug,
	})
	err = runner.Rsync("localhost:/etc/cron.daily/", fmt.Sprintf("%s/", destDir))
	if err != nil {
		// Report the error.
	}

}
Output:
Copy the contents of directory on a remote host to local temporary directory.
Debug /usr/bin/rsync --rsh ssh -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o GlobalKnownHostsFile=/dev/null --recursive --links --times localhost:/etc/cron.daily/ /tmp/go-logrun-XXXXX/

func (*LogRun) Run

func (r *LogRun) Run(cmd string, args ...string) (string, string, int)

Run first logs the command and then runs the command. Only logging is performed if DryRun is true.

Example
package main

import (
	"fmt"

	"github.com/apatters/go-conlog"
	"github.com/apatters/go-logrun"
)

func main() {
	// Set up logger to output at debug level and add a log level
	// prefix using title case.
	formatter := conlog.NewStdFormatter()
	formatter.Options.LogLevelFmt = conlog.LogLevelFormatLongTitle
	conlog.SetFormatter(formatter)
	conlog.SetLevel(conlog.DebugLevel)

	fmt.Println(`Run the "seq 1 3" command locally.`)
	runner := logrun.NewLocalLogRun(logrun.LocalConfig{
		LogFunc: conlog.Debug,
	})
	stdout, stderr, code := runner.Run("seq", "1", "3")
	fmt.Printf("Stdout = %q\n", stdout)
	fmt.Printf("Stderr = %q\n", stderr)
	fmt.Printf("Exit code = %d\n", code)
	fmt.Println()

	fmt.Println(`Run the "seq 1 3" command remotely.`)
	runner, err := logrun.NewRemoteLogRun(logrun.RemoteConfig{
		LogFunc: conlog.Debug,
		// We are faking credentials here to run in a test
		// environment.
		Credentials: logrun.Credentials{
			Hostname: "localhost",
		},
	})
	if err != nil {
		// Report the error.
	}
	stdout, stderr, code = runner.Run("seq", "1", "3")
	fmt.Printf("Stdout = %q\n", stdout)
	fmt.Printf("Stderr = %q\n", stderr)
	fmt.Printf("Exit code = %d\n", code)

}
Output:
Run the "seq 1 3" command locally.
Debug seq 1 3
Stdout = "1\n2\n3\n"
Stderr = ""
Exit code = 0

Run the "seq 1 3" command remotely.
Debug ssh buildman@localhost seq 1 3
Stdout = "1\n2\n3\n"
Stderr = ""
Exit code = 0

func (*LogRun) SetDryrun

func (r *LogRun) SetDryrun(dryrun bool)

SetDryrun enables/disables the execution of commands. If Dryrun is true, the command is only logged.

func (*LogRun) SetLogFunc

func (r *LogRun) SetLogFunc(f LogFunc)

SetLogFunc is used to set the logging function used to log a command. The function is typically something like log.Println() or logrus.Debug. A custom function of type LogFunc can also be used.

func (*LogRun) Shell

func (r *LogRun) Shell(cmd string) (string, string, int)

Shell first logs the command and then runs the command in a shell. Only logging is performed if DryRun is true.

Example
package main

import (
	"fmt"

	"github.com/apatters/go-conlog"
	"github.com/apatters/go-logrun"
)

func main() {
	// Set up logger to output at debug level and add a log level
	// prefix using title case.
	formatter := conlog.NewStdFormatter()
	formatter.Options.LogLevelFmt = conlog.LogLevelFormatLongTitle
	conlog.SetFormatter(formatter)
	conlog.SetLevel(conlog.DebugLevel)

	fmt.Println(`Run the "seq 1 3 | grep 2" command locally.`)
	runner := logrun.NewLocalLogRun(logrun.LocalConfig{
		LogFunc: conlog.Debug,
	})
	stdout, stderr, code := runner.Shell("seq 1 3 | grep 2")
	fmt.Printf("Stdout = %q\n", stdout)
	fmt.Printf("Stderr = %q\n", stderr)
	fmt.Printf("Exit code = %d\n", code)
	fmt.Println()

	fmt.Println(`Run the "seq 1 3 | grep 2" command remotely.`)
	runner, err := logrun.NewRemoteLogRun(logrun.RemoteConfig{
		LogFunc: conlog.Debug,
		// We are faking credentials here to run in a test
		// environment.
		Credentials: logrun.Credentials{
			Hostname: "localhost",
		},
	})
	if err != nil {
		// Report the error.
	}
	stdout, stderr, code = runner.Run("seq 1 3 | grep 2")
	fmt.Printf("Stdout = %q\n", stdout)
	fmt.Printf("Stderr = %q\n", stderr)
	fmt.Printf("Exit code = %d\n", code)

}
Output:
Run the "seq 1 3 | grep 2" command locally.
Debug /bin/sh -c "seq 1 3 | grep 2"
Stdout = "2\n"
Stderr = ""
Exit code = 0

Run the "seq 1 3 | grep 2" command remotely.
Debug ssh buildman@localhost seq 1 3 | grep 2
Stdout = "2\n"
Stderr = ""
Exit code = 0

type LogRunner

type LogRunner interface {
	SetLogFunc(f LogFunc)
	SetDryrun(dryrun bool)
	Run(cmd string, args ...string) (string, string, int)
	FormatRun(cmd string, args ...string) string
	Shell(cmd string) (string, string, int)
	FormatShell(cmd string) string
	FileExists(filename string) (bool, error)
	DirExists(dirname string) (bool, error)
	Glob(pattern string) ([]string, error)
	Rsync(src string, dest string) error
}

LogRunner is the interface for both LocalLogRun and RemoteLogRun.

type RemoteConfig

type RemoteConfig struct {
	// LogFunc is used to set the logging function used to log a
	// command. The function is typically something like
	// log.Println() or logrus.Debug. A custom function of type
	// LogFunc can also be used.
	LogFunc LogFunc

	// ShellExecutable is the full path to the shell on the remote
	// host to be run when executing shell commands.
	ShellExecutable string

	// Env specifies the environment of the process.
	// Each entry is of the form "key=value".
	// If Env is nil, the new process uses the current process's
	// environment.
	// If Env contains duplicate environment keys, only the last
	// value in the slice for each duplicate key is used.
	Env []string

	// Dir specifies the working directory of the command.
	// If Dir is the empty string, Run runs the command in the
	// calling process's current directory.
	Dir string

	// Stdin specifies the process's standard input.
	//
	// If Stdin is nil, the process reads from the null device (os.DevNull).
	//
	// If Stdin is an *os.File, the process's standard input is connected
	// directly to that file.
	//
	// Otherwise, during the execution of the command a separate
	// goroutine reads from Stdin and delivers that data to the command
	// over a pipe. In this case, Wait does not complete until the goroutine
	// stops copying, either because it has reached the end of Stdin
	// (EOF or a read error) or because writing to the pipe returned an error.
	Stdin io.Reader

	// Stdout and Stderr specify the process's standard output and error.
	//
	// If either is nil, the descriptor's output is captured and
	// is returned in the Run and Shell functions.
	//
	// If either is an *os.File, the corresponding output from the process
	// is connected directly to that file.
	//
	// Otherwise, during the execution of the command a separate goroutine
	// reads from the process over a pipe and delivers that data to the
	// corresponding Writer. In this case, Wait does not complete until the
	// goroutine reaches EOF or encounters an error.
	//
	// If Stdout and Stderr are the same writer, and have a type that can
	// be compared with ==, at most one goroutine at a time will call Write.
	Stdout io.Writer
	Stderr io.Writer

	// Credentials are used to authenticate with the remote host.
	Credentials Credentials

	// Dryrun enables/disables the execution of commands. If
	// Dryrun is true, the command is only logged.
	Dryrun bool
}

RemoteConfig is used to set options in the NewRemoteLoggingRunner constructor.

Jump to

Keyboard shortcuts

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