Procs is a library to make working with command line applications a little nicer.
The primary use case is when you have to use a command line client in place of an API. Often times you want to do things like output stdout within your own logs or ensure that every time the command is called, there are a standard set of flags that are used.
The majority of this functionality is intended to be included the procs.Process.
A command can be defined by a string rather than a []string. Normally, this also implies that the library will run the command in a shell, exposing a potential man in the middle attack. Rather than using a shell, procs lexically parses the command for the different arguments. It also allows for pipes in order to string commands together.
p := procs.NewProcess("kubectl get events | grep dev")
You can also define a new Process
by passing in predefined commands.
cmds := []*exec.Cmd{
exec.Command("kubectl", "get", "events"),
exec.Command("grep", "dev"),
}
p := procs.Process{Cmds: cmds}
One use case that is cumbersome is using the piped output from a command. For example, lets say we wanted to start a couple commands and have each command have its own prefix in stdout, while still capturing the output of the command as-is.
p := procs.NewProcess("cmd1")
p.OutputHandler = func(line string) string {
fmt.Printf("cmd1 | %s\n")
return line
}
out, _ := p.Run()
fmt.Println(out)
Whatever is returned from the OutputHandler
will be in the buffered
output. In this way you can choose to filter or skip output buffering
completely.
You can also define a ErrHandler
using the same signature to get the
same filtering for stderr.
Rather than use the exec.Cmd
[]string
environment variables, a
procs.Process
uses a map[string]string
for environment variables.
p := procs.NewProcess("echo $FOO")
p.Env = map[string]string{"FOO": "foo"}
Also, environment variables defined by the Process.Env
can be
expanded automatically using the os.Expand
semantics and the
provided environment.
There is a ParseEnv
function that can help to merge the parent
processes' environment with any new values.
env := ParseEnv(os.Environ())
env["USER"] = "foo"
Finally, if you are building commands manually, the Env
function can
take a map[string]string
and convert it to a []string
for use with
an exec.Cmd
. The Env
function also accepts a useEnv
bool to help
include the parent process environment.
cmd := exec.Command("knife", "cookbook", "show", cb)
cmd.Env = Env(map[string]string{"USER": "knife-user"}, true)
Take a look in the cmd
dir for some simple applications
that use the library. You can also make all
to build them. The
examples below assume you've built them locally.
The prelog
command allows running a command and prefixing the output
with a value.
$ ./prelog -prefix foo -- echo 'hello world!'
Running the command
foo | hello world!
Accessing the output without a prefix.
hello world!
Running the command with Start / Wait
foo | hello world!
The cmdtmpl
command uses the procs.Builder
to create a command
based on some paramters. It will take a data.yml
file and
template.yml
file to create a command.
$ cat example/data.json
{
"source": "https://my.example.org",
"user": "foo",
"model": "widget",
"action": "create",
"args": "-f new -i improved"
}
$ cat example/template.json
[
"mysvc ${model} ${action} ${args}",
"--endpoint ${source}",
"--username ${user}"
]
$ ./cmdtmpl -data example/data.json -template example/template.json
Command: mysvc foo widget create -f new -i imporoved --endpoint https://my.example.org --username foo
$ ./cmdtmpl -data example/data.json -template example/template.json -field user=bar
Command: mysvc foo widget create -f new -i imporoved --endpoint https://my.example.org --username bar
The procmon
command acts like
foreman with the difference
being it uses a JSON file with key value pairs instead of a
Procfile. This example uses the procs.Manager
to manage a set of
procs.Processes
.
$ cat example/procfile.json
{
"web": "python -m SimpleHTTPServer"
}
$ ./procmon -procfile example/procfile.json
web | Starting web with python -m SimpleHTTPServer
You can then access http://localhost:8000 to see the logs. You can
also kill the child process and see procmon
recognizing it has
exited and exit itself.