352 lines
6.7 KiB
Bash
352 lines
6.7 KiB
Bash
|
#!/bash
|
||
|
|
||
|
# shellcheck disable=2030,2031,2154
|
||
|
|
||
|
set -e -u -o pipefail
|
||
|
|
||
|
# shellcheck disable=2206
|
||
|
declare -a docker_run_options=(${RUN_OR_DOCKER_OPTIONS-})
|
||
|
RUN_OR_DOCKER_PULL=${RUN_OR_DOCKER_PULL:-false}
|
||
|
RUN_OR_DOCKER_PUSH=${RUN_OR_DOCKER_PUSH:-false}
|
||
|
|
||
|
run() (
|
||
|
verbose=${RUN_OR_DOCKER_VERBOSE:-false}
|
||
|
bin=$(dirname "${BASH_SOURCE[1]}")
|
||
|
self=$(basename "${BASH_SOURCE[1]}")
|
||
|
root=${ROOT:-$PWD}
|
||
|
prog=$(cd "$bin" && echo "$self".*)
|
||
|
version=${version:-$(calculate-version)}
|
||
|
image=yamlio/$self:$version
|
||
|
|
||
|
if [[ $prog == *'.*' ]]; then
|
||
|
prog=$self
|
||
|
lang=bash
|
||
|
else
|
||
|
case $prog in
|
||
|
*.sh) lang=bash ;;
|
||
|
*.bash) lang=bash ;;
|
||
|
*.pl) lang=perl ;;
|
||
|
*.py) lang=python3 ;;
|
||
|
*.rb) lang=ruby ;;
|
||
|
*) die "Don't recognize language of '$prog'" ;;
|
||
|
esac
|
||
|
fi
|
||
|
|
||
|
if $RUN_OR_DOCKER_PUSH; then
|
||
|
build-docker-image
|
||
|
exit
|
||
|
fi
|
||
|
|
||
|
if [[ -e /.dockerenv || ${RUN_OR_DOCKER-} == local ]]; then
|
||
|
if [[ $self == "$prog" ]]; then
|
||
|
main "$@"
|
||
|
else
|
||
|
run-local "$@"
|
||
|
fi
|
||
|
return
|
||
|
fi
|
||
|
|
||
|
if [[ ${RUN_OR_DOCKER-} == force* || ${GITHUB_ACTIONS-} ]]; then
|
||
|
run-docker "$@"
|
||
|
return
|
||
|
fi
|
||
|
|
||
|
out=$(check 2>/tmp/out) && rc=0 || rc=$?
|
||
|
|
||
|
err=$(< /tmp/out)
|
||
|
|
||
|
[[ $rc == 0 || $err == FAIL:* ]] || die "Error: $err"
|
||
|
|
||
|
if [[ $rc -eq 0 ]]; then
|
||
|
run-local "$@"
|
||
|
else
|
||
|
$verbose &&
|
||
|
echo "Can't run '$self' locally: ${err#FAIL:\ }" >&2
|
||
|
echo "Running '$self' with docker..." >&2
|
||
|
run-docker "$@"
|
||
|
fi
|
||
|
)
|
||
|
|
||
|
run-local() (
|
||
|
export RUN_OR_DOCKER=local
|
||
|
"$lang" "$bin/$prog" "$@"
|
||
|
)
|
||
|
|
||
|
run-docker() (
|
||
|
if [[ ${RUN_OR_DOCKER-} == force-build ]]; then
|
||
|
build-docker-image
|
||
|
else
|
||
|
docker inspect --type=image "$image" &>/dev/null &&
|
||
|
ok=true || ok=false
|
||
|
if ! $ok; then
|
||
|
if $RUN_OR_DOCKER_PULL; then
|
||
|
(
|
||
|
$verbose && set -x
|
||
|
docker pull "$image"
|
||
|
)
|
||
|
else
|
||
|
build-docker-image
|
||
|
fi
|
||
|
fi
|
||
|
fi
|
||
|
|
||
|
args=()
|
||
|
docker_run_options+=(
|
||
|
--env ROOT=/home/host
|
||
|
)
|
||
|
for arg; do
|
||
|
if [[ $arg == "$root"/* ]]; then
|
||
|
arg=/home/host/${arg#$root/}
|
||
|
fi
|
||
|
args+=("$arg")
|
||
|
done
|
||
|
|
||
|
flags=()
|
||
|
[[ -t 0 ]] && flags+=('--tty')
|
||
|
|
||
|
workdir=/home/host
|
||
|
[[ ${RUN_OR_DOCKER_WORKDIR-} ]] &&
|
||
|
workdir=$workdir/$RUN_OR_DOCKER_WORKDIR
|
||
|
|
||
|
uid=$(id -u)
|
||
|
gid=$(id -g)
|
||
|
|
||
|
$verbose && set -x
|
||
|
docker run "${flags[@]}" --interactive --rm \
|
||
|
--volume "$root":/home/host \
|
||
|
--workdir "$workdir" \
|
||
|
--user "$uid:$gid" \
|
||
|
--entrypoint '' \
|
||
|
"${docker_run_options[@]}" \
|
||
|
"$image" \
|
||
|
"$self" "${args[@]}"
|
||
|
)
|
||
|
|
||
|
force() {
|
||
|
fail 'docker is forced here'
|
||
|
}
|
||
|
|
||
|
need() {
|
||
|
cmd=$1
|
||
|
|
||
|
[[ $(command -v "$cmd") ]] ||
|
||
|
fail "requires command '$cmd'"
|
||
|
|
||
|
[[ ${2-} ]] || return 0
|
||
|
|
||
|
if [[ ${2-} =~ ^[0-9]+(\.|$) ]]; then
|
||
|
check=need-version
|
||
|
else
|
||
|
check=need-modules
|
||
|
fi
|
||
|
|
||
|
"$check" "$@"
|
||
|
}
|
||
|
|
||
|
need-version() {
|
||
|
cmd=$1 ver=$2
|
||
|
|
||
|
if [[ $("$cmd" --version) =~ ([0-9]+)\.([0-9]+)(\.[0-9]+)? ]]; then
|
||
|
set -- "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]#.}"
|
||
|
else
|
||
|
die "Could not get version from '$cmd --version'"
|
||
|
fi
|
||
|
|
||
|
vers=$ver
|
||
|
while [[ $vers && $* ]]; do
|
||
|
v=${vers%%.*}
|
||
|
if [[ $1 -gt $v ]]; then
|
||
|
return
|
||
|
elif [[ $1 -lt $v ]]; then
|
||
|
fail "requires '$cmd' version '$ver' or higher"
|
||
|
fi
|
||
|
if [[ $vers != *.* ]]; then
|
||
|
return
|
||
|
fi
|
||
|
vers=${vers#*.}
|
||
|
shift
|
||
|
done
|
||
|
fail "requires '$cmd' version '$ver' or higher"
|
||
|
}
|
||
|
|
||
|
need-modules() {
|
||
|
cmd=$1; shift
|
||
|
|
||
|
for module; do
|
||
|
case $cmd in
|
||
|
perl)
|
||
|
if [[ $module == *=* ]]; then
|
||
|
want=$module
|
||
|
version=${module#*=}
|
||
|
module=${module%=*}
|
||
|
perl -M"$module"\ "$version" -e1 &>/dev/null ||
|
||
|
fail "'$cmd' requires Perl module '$want'"
|
||
|
else
|
||
|
want=$module
|
||
|
perl -M"$module" -e1 &>/dev/null ||
|
||
|
fail "'$cmd' requires Perl module '$module'"
|
||
|
fi
|
||
|
;;
|
||
|
node)
|
||
|
node -e "require('$module');" &>/dev/null ||
|
||
|
fail "'$cmd' requires NodeJS module '$module'"
|
||
|
;;
|
||
|
python)
|
||
|
python3 -c "import $module" &>/dev/null ||
|
||
|
fail "'$cmd' requires Python(3) module '$module'"
|
||
|
;;
|
||
|
ruby)
|
||
|
list=$(gem list)
|
||
|
if [[ $module == *=* ]]; then
|
||
|
version=${module#*=}
|
||
|
module=${module%=*}
|
||
|
else
|
||
|
version=0
|
||
|
fi
|
||
|
(set -x; ruby -e "gem '$module', '>=$version'") &>/dev/null ||
|
||
|
fail "'$cmd' requires Ruby module '$module' >= v$version"
|
||
|
;;
|
||
|
*) die "Can't check module '$module' for '$cmd'" ;;
|
||
|
esac
|
||
|
done
|
||
|
}
|
||
|
|
||
|
build-docker-image() (
|
||
|
build=$(mktemp -d)
|
||
|
|
||
|
build-fail() { fail "docker-build failed: $*"; }
|
||
|
|
||
|
cmd() (
|
||
|
_args=${1//\ \+\ /\ &&\ }
|
||
|
echo "$_args"
|
||
|
echo
|
||
|
)
|
||
|
|
||
|
run() (
|
||
|
cmd "RUN $*"
|
||
|
)
|
||
|
|
||
|
from() {
|
||
|
_from=$1
|
||
|
case $_from in
|
||
|
alpine)
|
||
|
cmd 'FROM alpine'
|
||
|
cmd 'WORKDIR /home'
|
||
|
cmd 'RUN apk update && apk add bash build-base coreutils'
|
||
|
;;
|
||
|
ubuntu)
|
||
|
cmd 'FROM ubuntu:20.04'
|
||
|
cmd 'WORKDIR /home'
|
||
|
cmd 'RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential'
|
||
|
;;
|
||
|
*) build-fail "from $*"
|
||
|
esac
|
||
|
}
|
||
|
|
||
|
pkg() (
|
||
|
case $_from in
|
||
|
alpine)
|
||
|
cmd "RUN apk add $*"
|
||
|
;;
|
||
|
ubuntu)
|
||
|
cmd "RUN DEBIAN_FRONTEND=noninteractive apt-get install -y $*"
|
||
|
;;
|
||
|
*) build-fail "pkg $*"
|
||
|
esac
|
||
|
)
|
||
|
|
||
|
cpan() (
|
||
|
case $_from in
|
||
|
alpine)
|
||
|
pkg perl perl-dev perl-app-cpanminus wget
|
||
|
;;
|
||
|
ubuntu)
|
||
|
pkg cpanminus
|
||
|
;;
|
||
|
*) build-fail "cpan $*"
|
||
|
esac
|
||
|
|
||
|
cmd "RUN cpanm -n $*"
|
||
|
)
|
||
|
|
||
|
npm() (
|
||
|
case $_from in
|
||
|
alpine)
|
||
|
pkg nodejs npm
|
||
|
;;
|
||
|
*) build-fail "npm $*"
|
||
|
esac
|
||
|
|
||
|
cmd "RUN mkdir node_modules && npm install $*"
|
||
|
)
|
||
|
|
||
|
gem() (
|
||
|
case $_from in
|
||
|
alpine)
|
||
|
pkg ruby
|
||
|
;;
|
||
|
*) build-fail "npm $*"
|
||
|
esac
|
||
|
|
||
|
for module; do
|
||
|
if [[ $module == *=* ]]; then
|
||
|
module="${module%=*} -v ${module#*=}"
|
||
|
fi
|
||
|
|
||
|
cmd "RUN gem install $module"
|
||
|
done
|
||
|
)
|
||
|
|
||
|
pip() (
|
||
|
case $_from in
|
||
|
alpine)
|
||
|
pkg python3 py3-pip
|
||
|
;;
|
||
|
*) build-fail "npm $*"
|
||
|
esac
|
||
|
|
||
|
cmd "RUN pip3 install $*"
|
||
|
)
|
||
|
|
||
|
(
|
||
|
dockerfile
|
||
|
bin=$(dirname "$0")
|
||
|
bin=${bin#$root/}
|
||
|
if [[ $bin == bin ]]; then
|
||
|
cmd "ENV PATH=/home/host/bin:\$PATH"
|
||
|
else
|
||
|
cmd "ENV PATH=/home/host/$bin:/home/host/bin:\$PATH"
|
||
|
fi
|
||
|
) > "$build/Dockerfile"
|
||
|
|
||
|
|
||
|
(
|
||
|
set -x
|
||
|
cd "$build"
|
||
|
docker build -t "$image" .
|
||
|
)
|
||
|
|
||
|
rm -fr "$build"
|
||
|
|
||
|
if $RUN_OR_DOCKER_PUSH; then
|
||
|
(
|
||
|
set -x
|
||
|
docker push "$image"
|
||
|
)
|
||
|
fi
|
||
|
)
|
||
|
|
||
|
calculate-version() (
|
||
|
[[ ${uses-} ]] ||
|
||
|
die "'$0' requires either '\$version' variable or '\$uses' array"
|
||
|
|
||
|
cd "$(dirname "$0")" || exit
|
||
|
|
||
|
cat "${uses[@]}" |
|
||
|
md5sum |
|
||
|
cut -d' ' -f1
|
||
|
)
|
||
|
|
||
|
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||
|
die() { echo "$*" >&2; exit 1; }
|