From 0829962d221970240f27bffb3d8b33a937465396 Mon Sep 17 00:00:00 2001 From: cryobry <38270216+cryobry@users.noreply.github.com> Date: Wed, 1 Apr 2020 18:35:44 -0400 Subject: [PATCH] Initial commit --- .atom-build.yml | 20 +++ README.md | 1 + buildWrapper | 51 ++++++ functions | 107 +++++++++++ plugins/podman/podmanRunEasy | 304 ++++++++++++++++++++++++++++++++ plugins/podman/podmanRunWrapper | 277 +++++++++++++++++++++++++++++ tests/scripts/script.php | 6 + tests/scripts/script.sh | 3 + 8 files changed, 769 insertions(+) create mode 100755 .atom-build.yml create mode 100644 README.md create mode 100755 buildWrapper create mode 100644 functions create mode 100755 plugins/podman/podmanRunEasy create mode 100755 plugins/podman/podmanRunWrapper create mode 100755 tests/scripts/script.php create mode 100755 tests/scripts/script.sh diff --git a/.atom-build.yml b/.atom-build.yml new file mode 100755 index 0000000..d001ea5 --- /dev/null +++ b/.atom-build.yml @@ -0,0 +1,20 @@ +cmd: 'echo "Pick a command (see .atom-build.yml)"' +name: '' +targets: + # Fedora + Run silently in fedora: + cmd: 'buildWrapper podmanRunEasy -m ephemeral -i fedora:latest -n bw-fedora-test -w {FILE_ACTIVE_PATH} --silent --mkexec {FILE_ACTIVE}' + Run debug in fedora: + cmd: 'buildWrapper podmanRunEasy -m ephemeral -i fedora:latest -n bw-fedora-test -w {FILE_ACTIVE_PATH} --debug --mkexec {FILE_ACTIVE}' + # PHP + Run silently with php: + cmd: 'buildWrapper podmanRunEasy -m ephemeral -i php:latest -n bw-php-test -w {FILE_ACTIVE_PATH} --silent --mkexec {FILE_ACTIVE}' + Run debug with php: + cmd: 'buildWrapper podmanRunEasy -m ephemeral -i php:latest -n bw-php-test -w {FILE_ACTIVE_PATH} --debug --mkexec {FILE_ACTIVE}' + # Jekyll + Run jekyll serve: + cmd: 'buildWrapper podmanRunWrapper -m ephemeral -o "-it -p 4000:4000 -v {FILE_ACTIVE_PATH}:/srv/jekyll -v {FILE_ACTIVE_PATH}/vendor/bundle:/usr/local/bundle" -i jekyll/jekyll -n "bw-jekyll-serve-test" "bundle exec jekyll serve --watch --drafts"' + # Bundle + Run bundle update: + cmd: 'buildWrapper podmanRunWrapper -m ephemeral -o "-it -v {FILE_ACTIVE_PATH}:/srv/jekyll -v {FILE_ACTIVE_PATH}/vendor/bundle:/usr/local/bundle" -i jekyll/jekyll -n "bw-jekyll-bundle-update-test" --selinuxfix "chmod a+w /srv/jekyll/Gemfile.lock && bundle update --all"' +# Please add more! diff --git a/README.md b/README.md new file mode 100644 index 0000000..06a0672 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Coming soon, this program is still in development diff --git a/buildWrapper b/buildWrapper new file mode 100755 index 0000000..9d7bd18 --- /dev/null +++ b/buildWrapper @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +######################## +###### FUNCTIONS ####### +######################## + +source functions + +_printHelpAndExit () { + + cat <<-'EOF' +USAGE + buildWrapper plugin [command] + +EXAMPLES + buildWrapper podmanRunEasy --mode 0 ./myscript.sh -d -b + buildWrapper podmanRunEasy --mode 1 bundle exec jekyll build --watch + buildWrapper podmanRunWrapper -m ephemeral -o "--rm -it -v $PWD:$PWD -w $PWD" -i "php:latest" -c "php ./script.php" + +ARGUMENTS + plugin + Plugin to run (found in plugins/plugin_group/plugin) + + command + Command to be passed to the plugin +EOF + # Exit using passed exit code + [[ -z $1 ]] && exit 0 || exit "$1" +} + + +######################## +####### EXECUTE ######## +######################## + +# check if bash version supports nameref +checkBashVersion + +# source all plugins +sourcePlugins + +# make sure that the chosen plugin exists +pluginExists "$1" + +# Create a new named array with the arguments to be passed to the function +# We will access this array by name using a nameref (-n attribute) in our functions +# shellcheck disable=SC2034 +declare -a _BW_ARGS=("${@:2}") + +# Pass the name of the array to the function +"${1}" -a _BW_ARGS diff --git a/functions b/functions new file mode 100644 index 0000000..7719913 --- /dev/null +++ b/functions @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# shellcheck disable=SC1090,SC2034 + +checkBashVersion () { + + if ! declare -n _assoc; then + echo "You must use bash >= 4.3 (supports namerefs) to use build-wrapper" + echo "You can still call most functions directly using arguments instead of arrays." + _printHelpAndExit 1 + fi +} + + +getOS () { + + if [[ -e /etc/os-release ]]; then + source /etc/os-release + else + echo "No /etc/os-release found!" + echo "Your OS is unsupported" + _printHelpAndExit 1 + fi +} + + +installPackage () { + + # We will add packages to this array if their command is not available + local -a _pkg_array + + # parse commands + for _pkg in "$@"; do + _pkg=$(packageOverrides "$_pkg") + # Insert the package name to test if already installed one element from the end + # and silence output + if ! "${_pkg_query_cmd[@]}" "$_pkg" > /dev/null 2>&1; then + _pkg_array+=("$_pkg") + else + echo "$_pkg is already installed!" + fi + done + + if [[ ${#_pkg_array[@]} -ge 1 ]]; then + [[ -n $_debug ]] && echo "${_install_cmd[@]}" "${_pkg_array[@]}" + "${_install_cmd[@]}" "${_pkg_array[@]}" + fi +} + + +packageOverrides () { + + if [[ "$ID" == "ubuntu" || "$ID" == "debian" ]]; then + if [[ "$1" == "rpm-build" ]]; then + echo "rpm" + elif [[ "$1" == "createrepo_c" ]]; then + echo "createrepo" + else + echo "$1" + fi + else + echo "$1" + fi +} + + +getBaseDir () { + # Get base directory name of where this script resides + # https://stackoverflow.com/questions/59895/how-to-get-the-source-directory-of-a-bash-script-from-within-the-script-itself#comment54598418_246128 + _basedir=$(dirname "$(readlink -f "$0")") +} + + +sourcePlugin () { + + getBaseDir + _plugin="$_basedir/plugins/$1" + if [[ -f "$_plugin" ]]; then + source "$_plugin" + else + echo "Plugin $_plugin does not exist!" + fi +} + + +sourcePlugins () { + + getBaseDir + for _file in "$_basedir"/plugins/*/*; do + [[ -f "$_file" ]] && source "$_file" + done +} + + +pluginExists () { + + if [[ $# -lt 1 || ! $(type -t "$1") == "function" ]]; then + echo "Plugin not found" + _printHelpAndExit 1 + fi +} + + +fixPermissions () { + + # Allow container access to the workdir (SELinux) + chcon -t container_file_t -R "$1" +} diff --git a/plugins/podman/podmanRunEasy b/plugins/podman/podmanRunEasy new file mode 100755 index 0000000..a8d22aa --- /dev/null +++ b/plugins/podman/podmanRunEasy @@ -0,0 +1,304 @@ +#!/usr/bin/env bash +# shellcheck disable=SC1090,SC2004 + +podmanRunEasy () { + + sourcePlugin "podman/podmanRunWrapper" + + + ######################## + ####### DEFAULTS ####### + ######################## + + # Can be overridden using environment variables + [[ -z $_image ]] && _image="fedora:latest" + [[ -z $_mode ]] && _mode="ephemeral" + [[ -z $_workdir ]] && _workdir="$PWD" + + + _smartDefaults () { + echo "" + ### UNDER CONSTRUCTION ### + # I would like to handle some options automatically to reduce the number of configuration + # options necessary to run podman and also make it easy for users to define defaults based + # on file extension, command, shebang, etc + + # check the basename for defaults + #[[ -z $COMMAND_DEF ]] \ + # && COMMAND_BASENAME="${COMMAND_ARR[0]##*/}" \ + # && _set_defaults "${COMMAND_BASENAME}" + + # check the extension for defaults + #[[ -z $COMMAND_DEF ]] \ + # && COMMAND_EXT="${COMMAND_BASENAME##*.}" \ + # && _set_defaults "${COMMAND_EXT}" + + # add defaults to the string + #if [[ -n $COMMAND_DEF ]]; then + # [[ -z $_silent ]] && echo "Loaded command defaults, executing with \"${COMMAND_DEF}\"" + # COMMAND_STR="${COMMAND_DEF} ${COMMAND_STR}" + #else + # [[ -z $_silent ]] && echo "Could not find command defaults from name or extension" + # [[ -z $_silent ]] && echo "Executing script directly" + #fi + } + + ######################## + ###### FUNCTIONS ####### + ######################## + + _printHelpAndExit () { + + cat <<-'EOF' +USAGE + podman-run-easy [-m _mode] [-w PATH] [-d PATH] [-i _image] [--systemd] [--mkexec] [--help] + [--silent] [--debug] [COMMANDS [ARGS...]] + +COMMANDS + COMMANDS to run in the container (e.g. the current active file, an external + build script, a program residing in the container, etc.) + Can be empty (default entrypoint) + +OPTIONS + --mode, -m MODE + MODE can be any of the following: + + 1. ephemeral + 2. persistent + 3. recreate-persistent + 4. remove-persistent + + --workdir, -w PATH + The directory in the container where we execute the COMMANDS + Default: The present working directory + + --maskdir, -d PATH + Hide this directory from the host OS, store contents in the container only + + --image, -i IMAGE + IMAGE used to create the container + Default: fedora:latest + + --mkexec, -x + Makes the first argument of COMMANDS executable + + --name, -n CONTAINER_NAME + This will form the base of the container name and should be unique to each project + Default: Container name will be set based on a concatenation of the image and commands + + --systemd + Force container to init with systemd (--systemd=always) + Default: --systemd=true (systemd will only start if CMD is systemd, /usr/sbin/init or + /sbin/init) + + --array, -a ARRAY + Read arguments from an existing or new ARRAY (bash >= 4.3) + This is useful to reduce parsing errors and recommended for build-wrapper plugins + + --silent, -s + Don't output anything from this program (container output will still be passed to stdout + if -it option is used instead of -d, see `man podman run` for more information) + + --help, -h + Print this help message and exit (overrides --silent) + +EOF + + # Exit using passed exit code + [[ -z $1 ]] && exit 0 || exit "$1" + } + + + _parseInput () { + + [[ -n $_debug ]] && echo "$@" + + # Unset array variable when reparsing array + unset _array + + # Parse input and set switches using getopt + if _input=$(getopt -o +m:w:d:i:a:n:xsh -l mode:,workdir:,maskdir:,image:,array:,name:,mkexec,systemd,silent,debug,help -- "$@"); then + eval set -- "$_input" + while true; do + case "$1" in + --mode|-m) + shift + _mode="$1" + ;; + --workdir|-w) + shift + _workdir="$1" + ;; + --maskdir|-d) + shift + _maskdir="$1" + ;; + --image|-i) + shift + _image="$1" + ;; + --name|-n) + shift + _name="$1" + ;; + --array|-a) + shift + _array="$1" + break + ;; + --mkexec|-x) + _mkexec="true" + ;; + --systemd|-s) + _systemd="true" + ;; + --silent) + _silent="true" + ;; + --debug) + _debug="true" + echo "Debugging on!" + ;; + --help|-h) + _printHelpAndExit 0 + ;; + --) + shift + break + ;; + esac + shift + done + else + echo "Incorrect options provided" + _printHelpAndExit 1 + fi + + # If array mode, load input array, reparse input, and return + if [[ -n $_array ]]; then + checkBashVersion + local _n_array + declare -n _n_array="$_array" + _parseInput "${_n_array[@]}" + return + fi + + # Create _pre_commands_array from remaining arguments + # shift getopt parameters away + shift $((OPTIND - 1)) + # create array + declare -ga _pre_commands_array + _pre_commands_array=("$@") + [[ -n $_debug ]] && echo "_pre_commands_array:" "${_pre_commands_array[@]}" + + # Sanitize mode numbers + [[ "$_mode" == "1" ]] && _mode="ephemeral" + [[ "$_mode" == "2" ]] && _mode="persistent" + [[ "$_mode" == "3" ]] && _mode="recreate-persistent" + [[ "$_mode" == "4" ]] && _mode="remove-persistent" + + # build the podman options array + declare -ga _pre_options_array + _pre_options_array+=("-it") + #[[ "$_mode" == "ephemeral" ]] && _pre_options_array+=("--rm") + _pre_options_array+=("-v" "${_workdir}:${_workdir}") + _pre_options_array+=("-w" "${_workdir}") + [[ -n $_maskdir ]] && _pre_options_array+=("-v" "${_maskdir}") + [[ -n $_systemd ]] && _pre_options_array+=("--systemd=always") + [[ -n $_debug ]] && echo "_pre_options_array:" "${_pre_options_array[@]}" + + } + + + _makeExec () { + + # make executable + if [[ -n $_mkexec ]]; then + # assume script/program is the first argument in the command and break on whitespace + _program="${_pre_commands_array[0]}" + # make executable on the host + chmod +x "${_program}" + # make executable in the container + #COMMAND="chmod 755 ${_program} && $COMMAND" + fi + } + + + _sanityChecks () { + + # If missing, create workdir + if ! [[ -d "$_workdir" ]]; then + [[ -z $_silent ]] && echo "--workdir does not exist!" + [[ -z $_silent ]] && echo "Creating ${_workdir} now..." + mkdir -p "${_workdir}" + fi + + # Grant container SELinux access to the _workdir + fixPermissions "${_workdir}" + } + + + _runPodmanRunWrapper() { + + _makeExec + local _prw_array + _prw_array=("-m" "${_mode}" "--optionsarray" "_pre_options_array" "-i" "${_image}" "-n" "${_name}" "--commandsarray" "_pre_commands_array") + [[ -n $_debug ]] && _prw_array+=("--debug") + [[ -n $_silent ]] && _prw_array+=("--silent") + if [[ -n $_debug ]]; then + echo -n "PRE->PRW argument array: " + printf '"%s" ' "${_prw_array[@]}" + echo "" + fi + + podmanRunWrapper -a _prw_array + } + + + +######################### +####### EXECUTE ######### +######################### + + _execute () { + + # Get input + _parseInput "$@" + + # Call podman-run-wrapper + _runPodmanRunWrapper + } + + # Allow this function to be executed directly + _execute "$@" +} + + +# Allow this file to be executed directly if not being sourced +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + + # The following functions are usually handled by build-wrapper + + _getBaseDir () { + # Get base directory name of where this script resides + # https://stackoverflow.com/questions/59895/how-to-get-the-source-directory-of-a-bash-script-from-within-the-script-itself#comment54598418_246128 + _basedir=$(dirname "$(readlink -f "$0")") + } + + + _sourceFunctions () { + # Get the location of this file + _getBaseDir + # Go up two directories + ff="${_basedir%/*/*}/functions" + # Source functions file + if [[ -f "$ff" ]]; then + source "$ff" + else + echo "Cannot find functions file: ${ff}" + fi + } + + _sourceFunctions + podmanRunEasy "$@" +fi diff --git a/plugins/podman/podmanRunWrapper b/plugins/podman/podmanRunWrapper new file mode 100755 index 0000000..c04dfef --- /dev/null +++ b/plugins/podman/podmanRunWrapper @@ -0,0 +1,277 @@ +#!/usr/bin/env bash +# shellcheck disable=SC1090,SC2004 + +podmanRunWrapper () { + + ######################## + ###### FUNCTIONS ####### + ######################## + + _printHelpAndExit () { + + if [[ -z $_debug ]]; then + cat <<-'EOF' +USAGE + Argument mode: + podman-run-wrapper -m MODE -o OPTIONS -i IMAGE [-n CONTAINER_NAME] + [--help] [--silent] [--debug] [COMMANDS [ARGS...]] + + Array mode (bash >= 4.3): + podman-run-wrapper -a ARRAY + +EXAMPLE + podman-run-wrapper -m ephemeral -o "-it -v $PWD:$PWD -w $PWD" -i "php:latest" -c "php ./script.php" + + ARRAY=( "-m" "ephemeral" "-o" "--rm -it -v $PWD:$PWD -w $PWD" "-i" "php:latest" "-c" "php ./script.php") + podman-run-wrapper -a ARRAY + +COMMANDS + COMMANDS to run in the container (e.g. the current active file, an external build script, a + program residing in the container, etc.) + Can be empty (run default container entrypoint) + +OPTIONS + --mode, -m MODE + MODE can be any of the following: + + 1. ephemeral + 2. persistent + 3. recreate-persistent + 4. remove-persistent + + --options, -o OPTIONS + OPTIONS to pass directly to `podman run` or `podman exec` depending on the mode or + container state + + --image, -i IMAGE + IMAGE used to create the container + + --array, -a ARRAY + Read arguments array named ARRAY (bash >= 4.3) + This is useful to reduce parsing errors and recommended for build-wrapper plugins + + --optionsarray OPTIONS_ARRAY + Read podman options from array named OPTIONS_ARRAY (bash >= 4.3) + This is useful to reduce parsing errors and recommended for build-wrapper plugins + + --commandsarray COMMANDS_ARRAY + Read container commands from array named COMMANDS_ARRAY (bash >= 4.3) + This is useful to reduce parsing errors and recommended for build-wrapper plugins + + --name, -n CONTAINER_NAME + Use the CONTAINER_NAME base to name containers (should be unique to each project) + Default: Container name will be set based on a concatenation of the image and commands + + --selinuxfix + A temporary hack to grant SELinux write access on $PWD until a better fix is found + + --silent, -s + Only print errors + + --debug, -d + Print debugging + + --help, -h + Print this help message and exit + +EOF + fi + + # Exit using passed exit code + [[ -z $1 ]] && exit 0 || exit "$1" + } + + + # Parse input + _parseInput () { + + unset _mode _cmds_arr _opts_arr _options _prw_opts_arr _image _name _array _selinux_fix + + # Use getopt to print help + if INPUT=$(getopt -o +m:o:i:x:n:a:sdh -l mode:,options:,image:,name:,array:,optionsarray:,commandsarray:,selinuxfix,silent,debug,help -- "$@"); then + eval set -- "$INPUT" + while true; do + case "$1" in + --mode|-m) + shift + _mode="$1" + ;; + --options|-o) + shift + _options="$1" + ;; + --image|-i) + shift + _image="$1" + ;; + --name|-n) + shift + _name="$1" + ;; + --array|-a) + shift + _array="$1" + ;; + --optionsarray) + shift + _opts_arr="$1" + ;; + --commandsarray) + shift + _cmds_arr="$1" + ;; + --selinuxfix) + _selinux_fix="1" + ;; + --help|-h) + _printHelpAndExit 0 + ;; + --silent|-s) + _silent="1" + ;; + --debug|-d) + _debug="1" + echo "Debugging on!" + ;; + --) + shift + break + ;; + esac + shift + done + else + echo "Incorrect options provided!" + _printHelpAndExit 1 + fi + + + # If array mode, load and parse input array + if [[ -n $_array ]]; then + checkBashVersion + local _n_array + declare -n _n_array="$_array" + _parseInput "${_n_array[@]}" + return + fi + + # Parse podman options + if [[ -n $_opts_arr ]]; then + # namerefs are awesome + declare -gn _prw_opts_arr="$_opts_arr" + # If not array mode optionally load podman options from input string + elif [[ -n $_options ]]; then + declare -ga _prw_opts_arr + for _option in $_options; do + _prw_opts_arr+=("$_option") + done + else + echo "Must provide --options or the name of an existing --optionsarray" + _printHelpAndExit 1 + fi + + # Parse commands + if [[ -n $_cmds_arr ]]; then + declare -gn _prw_cmds_arr="$_cmds_arr" + else + # Create COMMANDS array from remaining arguments + # shift getopt parameters away + shift $((OPTIND - 1)) + # create array + declare -ga _prw_cmds_arr + _prw_cmds_arr=("$@") + if [[ ${#_prw_cmds_arr[@]} -lt 1 ]]; then + [[ -z $_silent ]] && echo "Warning: running container without any commands" + fi + fi + + [[ -n $_debug ]] && echo "_prw_opts_arr:" "${_prw_opts_arr[@]}" + [[ -n $_debug ]] && echo "_prw_cmds_arr:" "${_prw_cmds_arr[@]}" + } + + + _addCName () { + + # autogenerate _name if missing + [[ -z $_name ]] && _name="${_image}${_prw_cmds_arr[*]}" + + # sanitize container name + _name="${_name//_/}" && _name="${_name// /}" && \ + _name="${_name//[^a-zA-Z0-9_]/}" && _name="${_name,,}" + + # append flag + if [[ "$_mode" == "ephemeral" ]]; then + _cname="prw-e-$_name" + else + _cname="prw-p-$_name" + fi + _prw_opts_arr+=("--name" "$_cname") + } + + + _removeContainer () { + + if podman container exists "$_cname"; then + [[ -z $_silent ]] && echo "Removing container: $_cname" + [[ -n $_debug ]] && echo "podman rm -v -f $_cname" + podman rm -v -f "$_cname" + fi + } + + + _runContainer () { + + # Run _remove_container first to not run in existing container + if podman container exists "${_cname}"; then + [[ -z $_silent ]] && echo "Reusing container: $_cname" + [[ -n $_debug ]] && echo podman exec "$_cname" sh -c "${_prw_cmds_arr[@]}" + podman exec "$_cname" sh -c "${_prw_cmds_arr[@]}" + exit $? + else + [[ -z $_silent ]] && echo "Running in container: $_cname" + [[ -n $_debug ]] && echo "Command: podman run" "${_prw_opts_arr[@]}" "$_image" sh -c "${_prw_cmds_arr[@]}" + podman run "${_prw_opts_arr[@]}" "$_image" "${_prw_cmds_arr[@]}" + exit $? + fi + } + + +######################### +####### EXECUTE ######### +######################### + + _execute () { + + # Get input + _parseInput "$@" + + # Set container name + _addCName + + # SELinux fix + [[ -n $_selinux_fix ]] && fixPermissions "$PWD" + + # Execute podman + if [[ $_mode == "ephemeral" || $_mode == "recreate-persistent" ]]; then + _removeContainer + _runContainer + elif [[ $_mode == "remove-persistent" ]]; then + _removeContainer + elif [[ $_mode == "persistent" ]]; then + _runContainer + else + echo "Unknown mode!" + _printHelpAndExit 1 + fi + } + + # Allow this function to be executed directly + _execute "$@" +} + +# Allow script to be called directly +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + # No imported functions + #source-functions + podmanRunWrapper "$@" +fi diff --git a/tests/scripts/script.php b/tests/scripts/script.php new file mode 100755 index 0000000..d9fcb18 --- /dev/null +++ b/tests/scripts/script.php @@ -0,0 +1,6 @@ +#!/usr/bin/env php + + + diff --git a/tests/scripts/script.sh b/tests/scripts/script.sh new file mode 100755 index 0000000..38660e4 --- /dev/null +++ b/tests/scripts/script.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo "Hello World"