aboutsummaryrefslogtreecommitdiff
path: root/cloyster
diff options
context:
space:
mode:
authorJordan Bracco <href@random.sh>2022-03-25 15:44:44 +0000
committerJordan Bracco <href@random.sh>2022-03-25 15:44:44 +0000
commitbabd144393c0cbfdc28fb475190d9a552070b240 (patch)
tree927edd0818be95134ece7b819311b0269b3c5fdc /cloyster
parentInitial PoC (diff)
slowly but surely (maybe)HEADmain
Diffstat (limited to 'cloyster')
-rwxr-xr-xcloyster239
1 files changed, 180 insertions, 59 deletions
diff --git a/cloyster b/cloyster
index 18f72ca..16b1c23 100755
--- a/cloyster
+++ b/cloyster
@@ -6,6 +6,9 @@ hostname="${HOSTNAME:-$(hostname)}"
_executing=
_exec_error=
_script_name=$(basename "$0" | cut -d '.' -f 1)
+CLOYSTER_SCRIPT_ROOT=$(dirname "$0" | realpath)
+_testing=
+_silent=
# is_true VARIABLE
# return 0 if VARIABLE is "y", "yes", "t", "true", "1". Otherwise returns 1.
@@ -42,11 +45,14 @@ _debug() {
fi
}
+# Shadowed in the command subshell
_execution_error() {
_debug "fun=_execution_error() executing=${_executing}"
>&2 jo error="$(jo type=execution "$@")"
}
+. ./lib/vars.sh
+
. ./lib/dependencies.sh
ensure_command_exists jq
ensure_command_exists bc
@@ -60,21 +66,7 @@ fi
. ./lib/identifiers.sh
. ./lib/datetime.sh
-## getopts
-input_decode=
-output_encode=
-stdin=
-while getopts :Bbs _opt; do
- case "${_opt}" in
- b) input_decode="base64";;
- B) output_encode="base64";;
- s) stdin=yes;;
- *) _execution_error error=invalid_option option="${_opt}";;
- esac
-done
-shift $((OPTIND - 1))
-
-if is_true "${stdin}" || [ "${1}" = "-" ]; then
+if is_true "${stdin}" || [ "${1}" = "\-" ]; then
if [ "${1}" = "-" ]; then shift; fi
stdin=yes
_exec="STDIN"
@@ -90,6 +82,42 @@ else
fi
fi
+## getopts
+input_decode=
+output_encode=
+stdin=
+while getopts :BbsTqVh _opt; do
+ case "${_opt}" in
+ b) input_decode="base64";;
+ B) output_encode="base64";;
+ s) stdin=yes;;
+ T) _testing=yes;;
+ q) _silent=yes;;
+ V)
+ echo "cloyster vXXX??"
+ exit 0
+ ;;
+ h)
+ if [ -n "{_exec}" ] && [ ! "${_exec}" = "stdin" ]; then
+ echo "${_exec}: cloyster command"
+ echo "${_exec} [exec_options] [command json arguments]"
+ else
+ echo "cloyster help:"
+ fi
+ echo "execution options:"
+ echo -e " -s, -\texecute command from stdin"
+ echo -e " -q\tquiet (do not copy stdout/stderr to stderr)"
+ echo -e " -b\tdecode input from base64"
+ echo -e " -B\tencode output to base64"
+ echo -e " -T\trun tests"
+ echo -e " -V\tcloyster version"
+ exit 0
+ ;;
+ *) _execution_error error=invalid_option option="${_opt}";;
+ esac
+done
+shift $((OPTIND - 1))
+
_args_json=
if [ ! "${#}" = "0" ]; then
_args_json="$(jo -d. $@)"
@@ -113,49 +141,61 @@ json_unquote() {
echo "$1" | jq -r '.'
}
-_trap_exit() {
- _debug "TRAPPED EXIT >:( $@ executing? ${_executing}"
- if is_true "${_executing}"; then _exec_post "TRAPEXIT"; fi
- _debug "TRAPPED EXIT >:( $@ executing? ${_executing}"
- #rm "${_tmp_base}"* > /dev/null 2>&1 || true
- ( _json_loggers_stop ) > /dev/null 2>&1
- _debug "$(jobs)"
- _debug "bye"
- if is_true "${_executing}"; then exit 1; else exit 0; fi
+_cloyster_trap_exit() {
+ if is_true "${_executing}"; then
+ _debug "_cloyster_trap_exit: ignoring: is executing"
+ else
+ _debug "_cloyster_trap_exit: bye"
+ ( _json_loggers_stop ) > /dev/null 2>&1 || true
+ ( _cloyster_delete_tmp_dir ) > /dev/null 2>&1 || true
+ fi
}
-#trap _trap_exit EXIT
+trap _cloyster_trap_exit EXIT
_exec_name=$(basename "$_exec")
-_tmp_base="/tmp/${_script_name}@${_exec_name}@$(make_random_identifier 16)"
-_stdout_dest="${_tmp_base}_stdout.log"
-_stderr_dest="${_tmp_base}_stderr.log"
-_cloyster_dest="${_tmp_base}_cloyster.jsonstream"
-_output_dest="${_tmp_base}_output.jsonstream"
-_cntrl_dest="${_tmp_base}_cntrl"
+_tmp_dir="/tmp/${_script_name}@${_exec_name}@$(make_random_identifier 16)"
+_stdout_dest="${_tmp_dir}/stdout.log"
+_stderr_dest="${_tmp_dir}/stderr.log"
+_cloyster_dest="${_tmp_dir}/cloyster.jsonstream"
+_output_dest="${_tmp_dir}/output.jsonstream"
+_cntrl_dest="${_tmp_dir}/return"
_tmp_files="${_stdout_dest} ${_stderr_dest} ${_output_dest} ${_cloyster_dest} ${_cntrl_dest}"
-_debug "tmp_files: ${_tmp_files}"
+_debug "tmp_dir: ${_tmp_dir} tmp_files: ${_tmp_files}"
+mkdir "${_tmp_dir}"
for _f in ${_tmp_files}; do
if ! touch "${_f}"; then _execution_error error=tmpfile_creation_failed tmpfile="${_f}"; fi
done
+_cloyster_delete_tmp_dir() {
+ if [ -n "${_tmp_dir}" ] && ! is_true "$_tmp_dir_deleted" && [ -d "${_tmp_dir}" ]; then
+ _debug "Cleaning ${_tmp_dir}"
+ rm -rfx "${_tmp_dir}"
+ _tmp_dir_deleted=yes
+ else
+ _debug "Tmpdir already cleaned!"
+ fi
+}
+
# _logger_pretty_check NAME ISO806ZDATE LINE...
_logger_pretty_echo() {
- _name=$1
- _date=$2
- shift 2
- _line=$@
- _time=$(echo "$_date" | cut -d 'T' -f 2)
- _bold=$(tput md)
- _format_rst=$(tput me)
- _color=$(tput AF 3)
- _time_color=$(tput AF 59)
- case "${_name}" in
- stdout) _color=$(tput AF 2);;
- stderr) _color=$(tput AF 1);;
- cloyster) _color=$(tput AF 111);;
- esac
- >&2 echo -e "${_time_color}${_time} ${_color}${_bold}[${_name}]${_format_rst} ${_line}"
+ if ! is_true "${_silent}"; then
+ _name=$1
+ _date=$2
+ shift 2
+ _line=$@
+ _time=$(echo "$_date" | cut -d 'T' -f 2)
+ _bold=$(tput md)
+ _format_rst=$(tput me)
+ _color=$(tput AF 3)
+ _time_color=$(tput AF 59)
+ case "${_name}" in
+ stdout) _color=$(tput AF 2);;
+ stderr) _color=$(tput AF 1);;
+ cloyster) _color=$(tput AF 111);;
+ esac
+ >&2 echo -e "${_time_color}${_time} ${_color}${_bold}[${_name}]${_format_rst} ${_line}"
+ fi
}
_logger_pids=""
@@ -165,8 +205,9 @@ _json_logger() {
_format=$3
_combined_log=${4:-${_output_dest}}
_json_logger_job() {
+ trap - EXIT
_debug "_json_logger ${_name} ${_log}"
- tail -F "${_log}" | while read -r _line; do
+ stdbuf -oL tail -F "${_log}" | while read -r _line; do
_date=$(date_now_iso_utc)
case "${_format}" in
text)
@@ -181,24 +222,28 @@ _json_logger() {
esac
done
}
- $(_json_logger_job) &
+ _json_logger_job &
_logger_pids="${_logger_pids} $!"
}
_json_loggers_stop() {
- for tailpid in $(ps auxww | grep "tail -F ${_tmp_base}" | grep -v grep | awk '{ print $2 }'); do
+ if ! is_true "$_json_loggers_stop_done"; then
+ for tailpid in $(ps auxww | grep "tail -F ${_tmp_dir}" | grep -v grep | awk '{ print $2 }'); do
kill -s PIPE "${tailpid}" || true
- done
- for pid in ${_logger_pids}; do
+ done
+ for pid in ${_logger_pids}; do
kill "${pid}" > /dev/null 2>&1 || true
wait "${pid}" 2>/dev/null || true;
- done
+ done
+ _logger_pids=
+ _json_loggers_stop_done=yes
+ fi
+ #sleep 4
}
_json_logger "cloyster" "${_cloyster_dest}" "rawjson"
_json_logger "stdout" "${_stdout_dest}" "text"
_json_logger "stderr" "${_stderr_dest}" "text"
_exec_post() {
- trap - EXIT
_arg=$1
_executing=
_debug "_exec_post $@"
@@ -210,7 +255,7 @@ _exec_post() {
cat ${_output_dest} | jq -sr '. | @json' > "${_output_dest}.json"
eval $(cat $_cntrl_dest)
_format_output
- rm "${_tmp_base}"* || true
+ _cloyster_delete_tmp_dir
if [ ! "${_arg}" = "TRAPEXIT" ]; then exit "${_exec_exit_status:-1}"; fi
}
_format_output() {
@@ -253,24 +298,100 @@ _exec__put_return() {
echo "_exec_${_key}=${_value}" >> ${_cntrl_dest}
}
+_cloyster_exec() {
_debug "executing: command=${_exec} args=${@}"
_exec_pre
if is_true "$stdin"; then
eval "$(cat -)" 2> "${_stderr_dest}" > "${_stdout_dest}"
else
SCRIPT="${_exec}"
+ if [ "${_exec}" = "stdin" ]; then
+ CLOYSTER_COMMAND=stdin
+ CLOYSTER_COMMAND_FILE=/dev/null
+ CLOYSTER_COMMAND_DIR=/dev/null
+ _debug "Running: stdin"
+ else
+ CLOYSTER_COMMAND="$(basename "${_exec}")"
+ CLOYSTER_COMMAND_DIR="$(realpath "$(dirname "${_exec}")")"
+ CLOYSTER_COMMAND_FILE="${CLOYSTER_COMMAND_DIR}/${CLOYSTER_COMMAND}"
+ _debug "Running: ${CLOYSTER_COMMAND} (${CLOYSTER_COMMAND_FILE})"
+ fi
+ CLOYSTER_TMP_DIR="${_tmp_dir}"
+
+ _cloyster_test_tests=
+
# shellcheck source=/dev/null
- eval "$(
+ (
trap _exec__trap_exit EXIT
_execution_error() {
_exec__put_return "error" "$( jo type=cmd $@)" "json"
exit 2
}
- return_result() {
+
+ include_cmd_dir() {
+ if [ "${CLOYSTER_COMMAND}" = "stdin" ]; then
+ _execution_error function=include_cmd_dir error=include_cmd_dir_with_stdin msg="cannot be used with stdin commands"
+ exit 1
+ else
+ if [ -z "${1}" ]; then _execution_error function=include_cmd_dir error=missing_argument argument=file; exit 1; fi
+ _debug "Including ${CLOYSTER_COMMAND_DIR}/${1}"
+ . "${CLOYSTER_COMMAND_DIR}/${1}" 2>"${_stderr_dest}" 1>"${_stdout_dest}"
+ fi
+ }
+
+ return_result() {
_exec__put_return "result" "$1" "json"
exit 0
}
- . "${SCRIPT}" 2>"${_stderr_dest}" 1>"${_stdout_dest}"
- )" || true
+
+ return_error() {
+ _exec__put_return "error" "$(jo type=cmd "$@")" "json"
+ exit 1
+ }
+
+ alias RUN="{ runcmd() {"
+ alias ENDR="}; }"
+
+
+. "${CLOYSTER_SCRIPT_ROOT}/lib/test.sh"
+cloyster_test_init
+# if is_true "${_testing}"; then
+ _test() {
+ id=$(make_random_identifier 12)
+ export _cloyster_test_tests="${_cloyster_test_tests} ${id}"
+ setvar "_cloyster_test_tests_${id}__name" "$1"
+ shift
+ setvar "_cloyster_test_tests_${id}__args" "$*"
+ alias TEST="{ setvar \"_cloyster_test_tests_${id}__line\" \$LINENO; _test_${id}() {"
+ alias ENDT="}; unalias TEST; unalias ENDT; }"
+ }
+# else
+# _test() {
+# alias TEST="{ __cloyster_test_disabled() {"
+# alias ENDT="}; unalias TEST; unalias ENDT; }"
+# }
+# fi
+
+ cd "${CLOYSTER_TMP_DIR}"
+
+ _debug "Including command ${CLOYSTER_COMMAND_FILE}"
+ . "${CLOYSTER_COMMAND_FILE}" 2>"${_stderr_dest}" 1>"${_stdout_dest}"
+
+ if [ "$_executing" = "y" ]; then
+# if command -v "runcmd"; then
+ if is_true "${_testing}"; then
+ cloyster_test_run
+ else
+ _debug "runcmd!"
+ runcmd "$@" 2>"${_stderr_dest}" 1>"${_stdout_dest}"
+ fi
+# else
+# _execution_error error=no_run_function
+# fi
+ fi
+ ) || true
fi
_exec_post
+}
+
+_cloyster_exec