executive

package module
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Jun 27, 2022 License: GPL-3.0 Imports: 23 Imported by: 0

README

Executive

Go Reference GitHub tag (latest SemVer)

os.exec, but a little fancier! A simple wrapper providing extra functionality and cleaner usage when using it heavily.

Added features include:

  • Support for handlebar values in scripts (go templates)
  • Function chaining for cleaner code
  • Complex output parsing
  • Dynamic evaluation of output using expressions (expr)
  • Script execution on the local machine, ssh target, or docker container

Understanding Executive

Executive is divided into 5 core components:

  • Template: A template is a normal script file that can also contain handlebars for use with Go's templating engine. These can be more powerful than simple ENV vars (although this still allows for ENV vars to be set), as they are script language agnostic and support features such as loops.
  • Script: A script is somewhat self explantory. A script can either be created from a source (string, file, http), or be a 'compiled' template. A script is not executed upon creation. When executing a script, a remote target can be optionally specified.
  • Process: A process is an executing or executed script instance. Calling for a Result from this will wait for execution to be complete.
  • Result: A result is the output of an executed script, including the exit code, stdout and stderr.
  • Output: Output is key/value mapping of explicitly set outputs. This is done similiarly to github actions, where outputs are picked up from stdout/stderr with a prefix similar to ::set-output name=example::.... As these values can be typed (string, int, json), they can also be evaluated based on expressions.

Key Features

Templating

Using templates in scripts is easy, just have a handlebar value in the script named as desired. For example:

#!/bin/bash
{{.Command}} "Hello, {{.Name}}"

Then to set the value to replace {{.Name}}, the following go code can be used:

...
script, err := myTemplate.WithField("Command", "echo").WithField("Name", "world").Compile()
if err != nil {
	panic(err)
}
...

The templating system powering this supports other features too, such as loops when fields are slices of data etc...

Remote Execution

Scripts can be executed on your local machine when the Execute method is called, However, they can also be executed (with some limitations) on remote entities, such as SSH targets and Docker containers.

  • SSH: Replace Execute() with ExecuteOverSSH("username", "10.0.0.1:22", ssh.Password("mysecurepassword")). Any ssh auth can be used, including key-based auth.
  • Docker: Replace Execute() with ExecuteOverDocker(cli, "containerid", "sh -c"), where cli is a configured docker client and "sh -c" is the subprocess in which the script is passes as the next argument (a common alternative might be /bin/bash -c).

The limitations are that optional working directories for script execution are not available when executing over SSH, and signals such as SIGINT are not supported when using a remote Docker target.

⚠️ When using env vars over SSH, be sure to allow any (*) env var on the SSH server by setting the AcceptEnv option in sshd

Output Handling & Evaluation

If specific output is desired to be able to evaluate a response to a script, this package allows for specific typed outputs to be set. If a line in StdOut or StdErr has a prefix similar to ::set-output name=example::, the rest of the line is stored as an output value with the key being provided in the name field. For example, the output key/value Hello/world can be set like so if a script is executing via a shell such as bash:

echo ::set-output name=Hello::world

By default all outputted keys and values are considered to be string types. Optionally, a value can be an int, or a json structure. For example:

  • INT: echo ::set-output name=example type=int::42
  • JSON: echo ::set-output name=example type=json::{"sometext": "json", "anumber": 42}

Script resuts can then be programatically evaluated to boolean values using expressions. For example, to ensure the number set in the above JSON example is 42, the expression could be given:

...
if ok, err := result.CombinedOutput().Evaluate("example.anumber == 42"); err == nil && ok {
	fmt.Println("number is 42")
} else {
	panic(fmt.Errorf("number is not 42: %w", err))
}
...

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func SetTempFilePrefix

func SetTempFilePrefix(prefixPattern string) error

SetTempFilePrefix defines the prefix pattern for the temporary files created when "compiling" scripts into "executables". Any prefix must be only alphanumeric, with hypens and underscores permitted. It also must end in a single asterisk.

Types

type DockerProcess added in v0.2.2

type DockerProcess struct {
	OriginExecutable Script
	// contains filtered or unexported fields
}

DockerProcess represents a single instance of the executed on a docker container. script. Note that scripts over Docker have some limitations not found on local processes. For example, DockerProcess does not support a signals.

func (*DockerProcess) Kill added in v0.2.2

func (p *DockerProcess) Kill() error

func (*DockerProcess) Result added in v0.2.2

func (p *DockerProcess) Result() (*Result, error)

func (*DockerProcess) SigInt added in v0.2.2

func (p *DockerProcess) SigInt() error

func (*DockerProcess) Write added in v0.2.2

func (p *DockerProcess) Write(input string) error

type LocalProcess added in v0.2.2

type LocalProcess struct {
	OriginExecutable Script
	// contains filtered or unexported fields
}

Process represents a single instance of the script running or completed on the local device.

func (*LocalProcess) Exited added in v0.2.2

func (p *LocalProcess) Exited() bool

func (*LocalProcess) Kill added in v0.2.2

func (p *LocalProcess) Kill() error

func (*LocalProcess) Result added in v0.2.2

func (p *LocalProcess) Result() (*Result, error)

func (*LocalProcess) SigInt added in v0.2.2

func (p *LocalProcess) SigInt() error

func (*LocalProcess) Write added in v0.2.2

func (p *LocalProcess) Write(input string) error

type Output added in v0.2.2

type Output map[string]any

func NewOutput added in v0.2.2

func NewOutput(source string) Output

func (Output) Evaluate added in v0.2.2

func (o Output) Evaluate(expression string) (bool, error)

type Process

type Process interface {
	// Kill sends a SIGKILL to the running process. If this fails, for example if
	// the process is not running, this will return an error.
	Kill() error

	// SigInt sends a SIGINT to the running process. If this fails, for example if
	// the process is not running, this will return an error.
	SigInt() error

	// Write sends a string to the process's STDIN. Note that the string is sent
	// as-is. Thus if the program is looking for a newline before the read is
	// complete, this must be included in the string provided. If the write fails,
	// an error is returned.
	Write(string) error

	// Result waits for a script to complete execution, then a result is returned.
	// If the script returns an unknown error, this will also error.
	Result() (*Result, error)
}

Process is a single instance of the script, either running or exited. A process can be used to control the script and extract results from a script that has completed its execution.

type Result

type Result struct {
	StdOut   string `json:"stdout"`
	StdErr   string `json:"stderr"`
	ExitCode int    `json:"exitCode"`

	TotalTime  time.Duration `json:"executionTime"`
	SystemTime time.Duration
	UserTime   time.Duration
}

Result represents the output of a completed script execution. This is only created by the `Result` function of a "Process" and thus the process must have exited. The time metrics included are from the process and do not incorporate the time used by executive.

func (Result) CombinedOutput added in v0.2.2

func (r Result) CombinedOutput() Output

Output parses the specified outputs from the script's stdOut and stdErr. In the event that stdOut and stdErr specify an output of the same name, the value from stdOut is preferred. This is returned as a map. Any field that is not correctly parsed, will simply be ignored.

func (Result) Output added in v0.2.2

func (r Result) Output(useErr bool) Output

Output parses the specified outputs from the script's stdOut (or stdErr if specified). This is returned as a map. Any field that is not correctly parsed, will simply be ignored.

type SSHProcess added in v0.2.2

type SSHProcess struct {
	OriginExecutable Script
	// contains filtered or unexported fields
}

SSHProcess represents a single instance of the remotely executed (ssh) script. Note that scripts over SSH have some limitations not found on local processes. For example, SSHProcess does not support a WorkDir.

func (*SSHProcess) Kill added in v0.2.2

func (p *SSHProcess) Kill() error

func (*SSHProcess) Result added in v0.2.2

func (p *SSHProcess) Result() (*Result, error)

func (*SSHProcess) SigInt added in v0.2.2

func (p *SSHProcess) SigInt() error

func (*SSHProcess) Write added in v0.2.2

func (p *SSHProcess) Write(input string) error

type Script

type Script struct {
	OriginTemplate Template `json:"origin"`
	// contains filtered or unexported fields
}

Script is a "compiled" script template that can be executed. Unlike a Template, an executable has already been "compiled" (parsed through the golang templating engine).

func NewScript

func NewScript(originScript *Template, compiled string) (*Script, error)

NewScript takes a compiled Template to generate a file that can be executed, running the script. If none of the Template features are needed, this can be used directly with a nil originScript.

func NewScriptFromFile

func NewScriptFromFile(path string) (*Script, error)

NewScriptFromFile is similar to creating a template from file, however this assumes the template is already "compiled" and so does not need to be parsed by the template system.

func NewScriptFromHTTP

func NewScriptFromHTTP(link string) (*Script, error)

NewScriptFromHTTP is similar to creating a template from a http endpoint, however this assumes the template is already "compiled" and so does not need to be parsed by the template system.

func (Script) Execute added in v0.2.2

func (e Script) Execute() (Process, error)

Execute simply runs the "compiled" script with the env and workdir given. If the process returned is nil the process failed to start. If a single use script fails to be removed, the process is still returned along with the error.

func (Script) ExecuteOverDocker added in v0.2.2

func (e Script) ExecuteOverDocker(client *docker.Client, containerID string, subprocessCmd string) (Process, error)

ExecuteOverDocker simply runs the "compiled" script with the env vars and workdir provided. To run over Docker, some configuration must be supplied, including a docker client linked to the releveant docker host, the container ID that the script should be executed on, and a subprocess command to execute the script. In many cases, the subprocess command 'sh -c' will suffice, but others such as '/bin/bash -c' can also be used. The limitations here come from interrupts: docker processes will not support them.

func (Script) ExecuteOverSSH added in v0.2.2

func (e Script) ExecuteOverSSH(user, addr string, auth ssh.AuthMethod) (Process, error)

ExecuteOverSSH simply runs the "compiled" script with the env vars provided. To run over SSH, some configuration must be supplied, including the remote user, the target ssh server address (and port), and an SSH auth method (such as a password). If the process returned is nil the process failed to start. If a single use script fails to be removed, the process is still returned along with the error. Note that WorkDir is not supported for SSH.

func (*Script) Remove added in v0.2.2

func (e *Script) Remove() error

Remove simply deletes the temporary file that is generated upon creation of an Script. Doing so is not vital as the file is stored in a temporary dir, but it can help long running programs that frequently use executive.

func (Script) ScriptPath added in v0.2.2

func (e Script) ScriptPath() string

ScriptPath simply returns the path containing the temporary file of the script.

func (Script) ScriptPathExists added in v0.2.2

func (e Script) ScriptPathExists() bool

ScriptPathExists simply returns a bool representing the existence of the temporary script file (without discoling errors). THis can be used to determine if the executable has been removed. In the case where Stat returns an error that is not ErrNotExist, this assumes the file does exist as to allow for remove to operate more optimistically.

func (*Script) SetArgs added in v0.2.2

func (e *Script) SetArgs(args []string) error

SetArgs defines all the args that will be provided to the script upon execution. This overwrites any changes made via WithArg.

func (*Script) SetEnv added in v0.2.2

func (e *Script) SetEnv(key, value string) error

SetEnv sets an environment variable that should be set within the script's runtime env.

func (*Script) SetOSEnv added in v0.2.2

func (e *Script) SetOSEnv()

SetOSEnv ensures that the script will be able to use the environment vars from the os env.

func (*Script) SetSingleUse added in v0.2.2

func (e *Script) SetSingleUse(isSingleUse bool)

SetSingleUse sets the temporary file associated with the script to remove itself immediately after it starts execution. This can help keep things neat, but can also cause issues. Use carefully. Script is returned to facilitate function chinaing.

func (*Script) SetWorkDir added in v0.2.2

func (e *Script) SetWorkDir(path string) error

SetWorkDir sets the working directory for the context of the script. This does not check if the directory exists in order to be compatible with non-local execution.

func (Script) SingleUse added in v0.2.2

func (e Script) SingleUse() Script

SingleUse sets the temporary file associated with the script to remove itself immediately after it starts execution. This can help keep things neat, but can also cause issues. Use carefully. Script is returned to facilitate function chinaing.

func (Script) WithArg added in v0.2.2

func (e Script) WithArg(arg string) Script

WithArg adds an argument that will be provided to the script upon execution. Args are provided to the script in the the order they are provided. Script is returned and not directly modified as to support function chains.

func (Script) WithEnv added in v0.2.2

func (e Script) WithEnv(key, value string) Script

WithEnv sets an environment variable that should be set within the script's runtime env. Script is returned and not directly modified as to support function chains.

func (Script) WithOSEnv added in v0.2.2

func (e Script) WithOSEnv() Script

WithOSEnv ensures that the script will be able to use the environment vars from the os env. Script is returned and not directly modified as to support function chains.

func (Script) WithWorkDir added in v0.2.2

func (e Script) WithWorkDir(path string) Script

WithWorkDir sets the working directory for the context of the script. Script is returned and not directly modified as to support function chains.

type Template added in v0.2.2

type Template struct {
	Name string `json:"name"`
	Raw  string `json:"raw"`
	// contains filtered or unexported fields
}

Template is a named raw script that may contain template handlebars, along with the data it is to be "compiled" with.

func NewTemplate added in v0.2.2

func NewTemplate(name, raw string) (*Template, error)

NewTemplate returns a Template from a raw string with no deafult data.

func NewTemplateFromFile added in v0.2.2

func NewTemplateFromFile(name, filepath string) (*Template, error)

NewTemplateFromFile creates a new Template using the contents of a given file and with no default data. An error is returned if the file contents can not be extracted.

func NewTemplateFromHTTP added in v0.2.2

func NewTemplateFromHTTP(name, link string) (*Template, error)

NewTemplateFromHTTP creates a new Template using the contents retrived from a given link and with no default data. An error is returned if the link's contents can not be extracted.

func (Template) Compile added in v0.2.2

func (s Template) Compile() (*Script, error)

Compile runs the raw script string though the template engine, providing any data specified via WithData or WithField to the parser. Provided no template errors arise, a new Script is generated.

func (Template) CompileDryRun added in v0.2.2

func (s Template) CompileDryRun() (string, error)

CompileDryRun runs the template script string though the template engine, providing any data specified via WithData or WithField to the parser. Provided no template errors arise, the "compiled" script is returned.

func (Template) Data added in v0.2.2

func (s Template) Data() map[string]any

Data simply returns the current supplementary data that will be used when "compiling" the script.

func (Template) SetData added in v0.2.2

func (s Template) SetData(data map[string]any) error

SetData provides a data structure to the template so that handlebar values in the raw script can be populated when "compiled". This is merged with any existing data. If the data fails to be merged with any existing data, and error is returned.

func (Template) WithData added in v0.2.2

func (s Template) WithData(data map[string]any) Template

WithData provides a data structure to the script so that handlebar values in the raw script can be populated when "compiled". If any key exists already, the data is overwritten with no warning. Note that the Script is not modifed, instead a modifed Template is returned, better suited for function chaining.

func (Template) WithField added in v0.2.2

func (s Template) WithField(key string, value any) Template

WithField adds a single entry to the map of data that a Template is "compiled" with. If the key exists already, the data is overwritten with no warning. Note that the Script is not modifed, instead a modifed Template is returned, better suited for function chaining.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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