diff options
Diffstat (limited to 'cloyster')
-rwxr-xr-x | cloyster | 239 |
1 files changed, 180 insertions, 59 deletions
@@ -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 |