aboutsummaryrefslogtreecommitdiff

cloyster: a shell wrapper for machine-oriented shell scripts

cloyster helps you making small, as portable as possible, single-task focused shell scripts that are primarily consumed by other scripts or machines.

cloyster provides a portable utilitary functions library to help you be efficient transforming and manipulating data.

cloyster scripts are called "commands". A good command does a single thing/action (list something, create an interface, bridge an interface, ...).

Being machine-oriented and automation-oriented, commands accepts arguments as JSON, and return JSON: this also to easily combine and use commands in commands, filter outputs easily (using jq), pipe them together...

Base dependencies: POSIX shell, jq, jo

Scripting commands

A command is a POSIX shell script, using /usr/local/libexec/cloyster as shebang.

It will run in the cloyster environment and has three outputs:

  • the standards outputs, stdout and stderr, which acts as usual: unstructured, line based text, automatically handled by cloyster and returned as output,
  • the structured JSON result of your command, set by calling return_result, returned as result.
  • a structured JSON fatal error, set by calling return_error, returned as error.

API

  • return_result JSON|JO exits successfully with a JSON result
  • return_error JSON|JO [exit_code=1] exists with exit_code and a JSON error
  • args_get [JQ_QUERY=.] query the arguments using jq

Utilitary Functions

  • ensure_command_exists BINARY ensures a binary exists, exits with error if not
  • os_get [KEY=.|jq] [JQ_QUERY] operating system version (type, name, pretty_name, version, version_id, supported)
  • make_random_identifier [SIZE=32]
  • date_now_iso_utc
  • posix_time_microseconds

Arguments/Input

Arguments, or inputs to the command, are also passed in JSON. You have three ways of passing the arguments:

  • human friendly: COMMAND arg1=something ... (uses jo),
  • escaped json string: COMMAND '{"something": true}'
  • json wrapped in base64: COMMAND -b eyJiYW5uZXIiOiJiZWFzdGllIGZvciB0aGUgd2luIn0K

Output

{
  "error": null /* Error */,
  "exit_status": 0,
  "command": "./commands/ifconfig-interfaces-list.sh",
  "arguments": {},
  "duration_microseconds": /* microseconds */,
  "started_at_utc": /* ISO8061Z */,
  "finished_at_utc": /* ISO8061Z */,
  "result": /* any */,
  "output": [
    // Output Line
  ]
}

Output Line:

{"output": "stdout|stderr", "date":"ISO8061Z", line: "...."}

Error:

{
  "type": "execution|command",
  "error": "error_name",
  // Error specific field
}

Reading script from stdin

You can tell cloyster to execute a script from stdin by setting the -s or - option to cloyster:

cat commands/ifconfig-interfaces-list | cloyster -s
cloyster - <<EOF
json=$(jls --libxo json | jq -r '.["jail-information"].jail | @json')
set_result "${json}"
EOF

Examples

Example: commands/ifconfig-interfaces-names.sh

#!/usr/local/libexec/cloyster
echo "stdout something"
>&2 "stderr something"
set_result $(ifconfig | grep -E "^[a-z0-9_]+:" | cut -d ':' -f 1 | jq -nRr '[inputs | select(length>0)] | @json')

Run it (piped to jq so you beautify JSON output):

./commands/ifconfig-interfaces-names.sh | jq

Result:

{
  "error": false,
  "exit_status": 0,
  "command": "./commands/ifconfig-interfaces-list.sh",
  "arguments": [],
  "duration_microseconds": 67,
  "started_at_utc": "2022-03-22T09:28:51Z",
  "finished_at_utc": "2022-03-22T09:28:51Z",
  "result": ["eth0", "eth1", "lo0"],
  "output": [
    {"output": "stdout", "date":"iso8601 utc", line: "stdout something"},
    {"output": "stderr", "date":"iso8601 utc", line: "stderr something"}
  ]
}

Code guide

meh

  • We write POSIX Shell compliant code.
  • Use shellcheck.
  • Variables:
  • are considered private/local if they begin by a _
  • are considered public if they are in lowercase
  • are exported/env if they are in uppercase
  • Cloyster itself must be portable (POSIX Shell) and should be able to work on macOS, Linux, ...