diff --git a/.gitignore b/.gitignore index f64df3f..4288ac8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ _opt/ .src TODO.md mnt/ -logs/ \ No newline at end of file +logs/ +Dockerfile +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 32a9ba7..0000000 --- a/Dockerfile +++ /dev/null @@ -1,63 +0,0 @@ -# syntax=docker/dockerfile:latest -ARG BASE_IMAGE -FROM $BASE_IMAGE -ARG BASE_IMAGE -ARG SYSADMIN_PW -ARG LINUX_DISTRO=alpine -WORKDIR /build - -# PACKAGES -RUN --mount=type=bind,source=.src/packages,target=/build/packages \ -<> /etc/profile - tail /etc/profile - echo "%%%%%%%%%%%%%%%%%%%%%%%%%%%" - fi - echo -e "\n ************* End Initialzation ************************" -eot -# END INITIALIZATION - -# default command -CMD ["/bin/bash", "-l"] -# default -WORKDIR /opt - - diff --git a/Dockerfile.d/Dockerfile.tpl b/Dockerfile.d/Dockerfile.tpl new file mode 100644 index 0000000..2aa6c00 --- /dev/null +++ b/Dockerfile.d/Dockerfile.tpl @@ -0,0 +1,30 @@ +# syntax=docker/dockerfile:latest +ARG BASE_IMAGE +FROM $BASE_IMAGE +ARG BASE_IMAGE +ARG SYSADMIN_PW +ARG VERBOSE +ARG LINUX_DISTRO=alpine +WORKDIR /build + +# PACKAGES +RUN --mount=type=bind,source=.src/packages,target=/build/packages \ +< /dev/null || return 1 + source <(../lib/bash-tpl Dockerfile.tpl ) | grep -v '^# ' > ../Dockerfile + echo " ************* uci build Dockerfile created *****************" + popd > /dev/null || return 2 diff --git a/Dockerfile.d/init.tpl b/Dockerfile.d/init.tpl new file mode 100644 index 0000000..7f2fa68 --- /dev/null +++ b/Dockerfile.d/init.tpl @@ -0,0 +1,86 @@ +#!/bin/bash +% + if [[ $REBUILD == "init" ]]; then + echo "## Busting Cache, Forcing Rebuild $(date)" + fi +% +quiet () { + if [[ $VERBOSE ]]; then $@; fi +} +quiet echo -e "\n ************************************************* \n" +quiet echo "****** Initializing Image with build source ******" +cd init +pwd; quiet ls -la +export BUILDING=true +export BUILD_DIR=$PWD +export SHELL=/bin/bash +export BIN_DIR=/opt/bin +mkdir -p $BIN_DIR +echo "export BIN_DIR=${BIN_DIR}" >> /tmp/profile +echo 'export PATH=$BIN_DIR:$PATH' >> /tmp/profile + +echo " ##### creating entrypoint script ###" +cat << EOE >$BIN_DIR/entrypoint +.INCLUDE ./init/entrypoint.tpl +EOE +chmod +x $BIN_DIR/entrypoint +quiet echo '------ default entrypoint -----' +quiet ls -la $BIN_DIR/entrypoint +quiet cat $BIN_DIR/entrypoint +quiet echo "------------" + +echo " ##### creating default start script ###" +cat << "EOS" >$BIN_DIR/start +.INCLUDE ./init/start.sh +EOS +chmod -R +x $BIN_DIR/start +quiet echo "--- DEFAULT START SCRIPT in $BIN_DIR/start ---" +quiet cat $BIN_DIR/start +quiet echo "-----------------------------------" + +echo " ##### creating map host id script ###" +cat << "EOM" >$BIN_DIR/map-host-id +.INCLUDE ./init/map-host-id.sh +EOM +chmod +x $BIN_DIR/map-host-id + +[[ -f image.info ]] && cp image.info /opt + +.INCLUDE ./init/dirs.sh + +if [[ -f build.env ]]; then + echo "-- sourcing /build/build.env --" + quiet ls -la + quiet cat build.env + quiet echo "----------------------" + source build.env +fi + +if [[ -f ./init.sh ]]; then + echo "############## Running Script init.sh of build source #################" + quiet echo "----- build environment ------" + quiet env + quiet echo "----- env ------" + quiet echo "-------------------- init.sh ------------------------------" + quiet cat ./init.sh + quiet echo "-------------------------------------------------------------" + # init.sh must have shebang and be executable + if ! $SHELL ./init.sh; then return 1; fi + echo "############## Finished running init.sh build script #########################" +fi + +.INCLUDE ./init/profile.sh + +echo "****** creating user and group 'host' with ids 1000 *****" +groupadd -g 1000 host +useradd -r -g host -u 1000 host +# map host id now based on build environment +if [[ $VOLUME_DIRS ]]; then + echo "*** creating and configuring volume directories ***" + echo $VOLUME_DIRS + mkdir -p $VOLUME_DIRS + $BIN_DIR/map-host-id + chmod -R g+rw $VOLUME_DIRS +fi + +echo -e "\n ************* End Initialzation ************************" \ No newline at end of file diff --git a/Dockerfile.d/init/dirs.sh b/Dockerfile.d/init/dirs.sh new file mode 100644 index 0000000..d5f2860 --- /dev/null +++ b/Dockerfile.d/init/dirs.sh @@ -0,0 +1,25 @@ +if [[ -d env/ ]]; then + export ENV_DIR=/opt/env + echo "############## Adding Environment Directroy $ENV_DIR #################" + echo "export ENV_DIR=${ENV_DIR}" >> /tmp/profile + quiet echo "copying env/ to $ENV_DIR" + /bin/cp -R -p env/. $ENV_DIR + quiet ls -la $ENV_DIR +fi +if [[ -d bin/ ]]; then + echo "############## Copying to Binary Directroy $BIN_DIR #################" + quiet echo "copying bin/ to $BIN_DIR" + /bin/cp -R -p bin/. $BIN_DIR + # chmod -R +x $BIN_DIR + quiet ls -la $BIN_DIR +fi + +if [[ -d lib/ ]]; then + export LIB_DIR=/opt/lib + echo "############## Adding Library Directroy $LIB_DIR #################" + echo "export LIB_DIR=${LIB_DIR}" >> /tmp/profile + quiet echo "copying lib/ to $LIB_DIR" + /bin/cp -R -p lib/. $LIB_DIR + chmod -R +x $LIB_DIR + quiet ls -la $LIB_DIR +fi \ No newline at end of file diff --git a/Dockerfile.d/init/entrypoint.tpl b/Dockerfile.d/init/entrypoint.tpl new file mode 100644 index 0000000..0dbbb57 --- /dev/null +++ b/Dockerfile.d/init/entrypoint.tpl @@ -0,0 +1,29 @@ +#!/bin/bash +# to maintain variable $ in container script espcape with \$ +# otherwise subtitution will happen during build +case "\$1" in +maphostid) +shift 1 +/bin/bash -l -c '\$BIN_DIR/map-host-id \$@' \$0 "\$@" +;; +shell) +/bin/bash -c "cd \${INITIAL_DIR:-/opt}; exec bash -l" +;; +help) +.INCLUDE ./init/help.sh +;; +image) +.INCLUDE ./init/image-info.sh +;; +script) +shift 1 +cat | /bin/bash -l +;; +${ENTRYPOINT_CMD:-start}) +shift 1 +/bin/bash -l -c '${ENTRYPOINT_CMD_PATH:-$BIN_DIR/start} \$@' \$0 "\$@" +;; +*) +/bin/bash -l -c '"\$@"' \$0 "\$@" +;; +esac \ No newline at end of file diff --git a/Dockerfile.d/init/help.sh b/Dockerfile.d/init/help.sh new file mode 100644 index 0000000..b3d52d9 --- /dev/null +++ b/Dockerfile.d/init/help.sh @@ -0,0 +1,14 @@ +cat < +otherwise you can pass any shell command such as 'ls -la' +the current container custom command is > ${ENTRYPOINT_CMD:-start} +and the script for that command is in ${ENTRYPOINT_CMD_PATH:-$BIN_DIR/start} +----- +you can replace this start script with your own +your own script in $BIN_DIR/start in your build source directory +or set the \$ENTRYPOINT_CMD and \$ENTRYPOINT_CMD_PATH variables +It is possible to override the container entrypoint with your own +but is not recommmended as then a login shell will not be used +and critical environment variables will not be set +HELP \ No newline at end of file diff --git a/Dockerfile.d/init/image-info.sh b/Dockerfile.d/init/image-info.sh new file mode 100644 index 0000000..0fce780 --- /dev/null +++ b/Dockerfile.d/init/image-info.sh @@ -0,0 +1,5 @@ +if [[ -f /opt/image.info ]]; then +echo -e "\n--------- image info found at /opt/image.info----------" +cat /opt/image.info +echo -e "\n****************************" +fi diff --git a/Dockerfile.d/init/map-host-id.sh b/Dockerfile.d/init/map-host-id.sh new file mode 100644 index 0000000..7814226 --- /dev/null +++ b/Dockerfile.d/init/map-host-id.sh @@ -0,0 +1,9 @@ +#!/bin/bash +if [[ $VOLUME_DIRS ]]; then + echo changing ownership of directories $VOLUME_DIRS + echo to ${HOST_MAP:-"host:host"} + declare usesudo + [[ ! $EUID -eq 0 ]] && usesudo=sudo + $usesudo chown -R ${HOST_MAP:-"host:host"} $VOLUME_DIRS + ls -la $VOLUME_DIRS +fi \ No newline at end of file diff --git a/Dockerfile.d/init/profile.sh b/Dockerfile.d/init/profile.sh new file mode 100644 index 0000000..2ab4521 --- /dev/null +++ b/Dockerfile.d/init/profile.sh @@ -0,0 +1,12 @@ +[[ -f $ENV_DIR/run.env ]] && echo 'source $ENV_DIR/run.env' >> /tmp/profile + +while read line; do +if ! grep -q "$line" /etc/profile; then + quiet echo added $line to /etc/profile + echo $line >> /etc/profile +fi +done < /tmp/profile +# echo "echo /etc/profile has been sourced" >> /etc/profile +quiet echo "&&&&&&& last 10 of /etc/profile &&&&&" +quiet tail /etc/profile +quiet echo "%%%%%%%%%%%%%%%%%%%%%%%%%%%" \ No newline at end of file diff --git a/Dockerfile.d/init/start.sh b/Dockerfile.d/init/start.sh new file mode 100644 index 0000000..2628408 --- /dev/null +++ b/Dockerfile.d/init/start.sh @@ -0,0 +1,17 @@ +#!/bin/bash +#***** CONTAINER DEFAULT CUSTOM SCRIPT ******************" +case "$1" in +sub1) +echo this would be a subcommand #1 +echo with arguments $@ +;; +sub2) +shift 1 +echo this would be a subcommand #1 +echo with arguments $@ +;; +*) +echo "running this command $*" +echo within login shell +/bin/bash -c '"$@"' $0 "$@" +esac \ No newline at end of file diff --git a/Dockerfile.d/packages.tpl b/Dockerfile.d/packages.tpl new file mode 100644 index 0000000..fd02466 --- /dev/null +++ b/Dockerfile.d/packages.tpl @@ -0,0 +1,20 @@ +% + if [[ $REBUILD == "packages" ]]; then + echo "## Busting Cache, Forcing Rebuild $(date)" + fi +% +echo -e "\n ************************************************* \n" +echo "Building Image from Base: $BASE_IMAGE" +echo "Distro: $LINUX_DISTRO" +echo " ---- running packages install script ---" +if [[ $LINUX_DISTRO == "alpine" ]]; then + echo "-------------------------------" + echo "adding shadow bash and bash completion coreutils for alpine" + echo "to be compatible with other distros" + apk add --no-cache shadow bash bash-completion coreutils + echo "-------------------------------" +fi +cd packages +/bin/sh ./packages.sh +cd .. +echo -e "\n********************************************************" \ No newline at end of file diff --git a/build b/build index 4213537..4d2cb12 100755 --- a/build +++ b/build @@ -1,46 +1,56 @@ #!/bin/bash -docker_image_build () { +udbuild () { -local targets=(dev arm64 amd64 deploy private multi) -local verbose; local log_dir; local no_prompt -local efile +local targets=(dev arm64 amd64 publish multi default) +local log_dir; local no_prompt +local append_efile declare OPTION; declare OPTARG; declare OPTIND BDIR=$(dirname "$(realpath "$BASH_SOURCE")") export BDIR # load script library source $BDIR/lib/load.sh +BUILD_EFILE="" # check for subcommands first case "$1" in try) - shift 1 - # type try_container - try_container "$@" + shift 1; try_container "$@"; return $? ;; + load_env_file) + echo -e "@@@@@@ loading build environment file for external use @@@@@@" + BUILD_EFILE=$(echo -- "$@" | grep -oP -- '(?<=-e )[^ ]*') + source_env_file "$BUILD_EFILE" + echo -e "@@@@@@@@@@@@@@@@@ returning to calling script @@@@@@@@@@@@@@@" return $? ;; - image_name) + build_src) shift 1; get_build_src "$@"; return $? ;; + help) + ;& + --help) + ;& + -help) shift 1; usage "$@"; return $? ;; + source) type udbuild; return $? ;; + image) shift 1 - image_name "$@" - return $? - ;; - tag) - shift 1 - image_tag "$@" - return $? - ;; - push) - shift 1 - image_push "$@" - return $? - ;; - info) - shift 1 - [[ $1 == "arch" ]] && { shift 1; image_arch "$@"; return $?; } - [[ $1 == "exists" ]] && { shift 1; image_exists "$@"; return $?; } - [[ $1 == "id" ]] && { shift 1; image_id "$@"; return $?; } - image_info "$@"; return $? + case "$1" in + name) shift 1; image_name "$@" ;; + tag) shift 1; image_tag "$@" ;; + push) shift 1; image_push "$@" ;; + delete) shift 1; image_delete "$@" ;; + info) + shift 1 + case "$1" in + arch) shift 1; image_arch "$@" ;; + exists) shift 1; image_exists "$@" ;; + tags) shift 1; image_tags "$@" ;; + id) shift 1; image_id "$@" ;; + * ) image_info "$@" + esac + ;; + *) echo no image subcommand $1 ;; + esac + return $? ;; esac @@ -52,20 +62,30 @@ exit_abnormal() { # Function: Exit with error. [[ -z "$PS1" ]] || no_prompt=true overwrite=true -while getopts 'g:e:b:d:t:ncr:u:plhs:avo' OPTION; do +while getopts 'fg:e:b:d:t:nc:r:u:lhs:a:voi:p' OPTION; do # echo processing: option:$OPTION argument:$OPTARG index:$OPTIND remaining:${@:$OPTIND} case "$OPTION" in + i) + IMAGE_INFO=$OPTARG + ;; e) - if source_env_file $OPTARG; then efile=true; else return 2; fi + BUILD_EFILE=$OPTARG + if ! source_env_file $BUILD_EFILE; then return 2; fi ;; o) unset overwrite ;; v) - verbose=true + VERBOSE=true ;; a) - # automated - script is to be run without prompt (non-interactive) + append_efile=$OPTARG + ;; + f) + REBUILD=init + ;; + p) + echo "build script will be run WITHOUT user prompts (i.e. non-interactive)" no_prompt=true ;; b) @@ -82,7 +102,7 @@ while getopts 'g:e:b:d:t:ncr:u:plhs:avo' OPTION; do ;; l) # append distro name to image name - append_distro=true + APPEND_DISTRO=true ;; t) TARGET=$OPTARG @@ -94,10 +114,7 @@ while getopts 'g:e:b:d:t:ncr:u:plhs:avo' OPTION; do RUSER=$OPTARG ;; c) - try=true - ;; - p) - push=true + TRY_CMD=$OPTARG ;; n) nocache="--no-cache" @@ -118,70 +135,27 @@ done shift $((OPTIND - 1)) -[[ ! $efile ]] && source_env_file +[[ ! $BUILD_EFILE ]] && source_env_file -# processing the build source directory -if [[ ! $BUILD_SRC ]]; then - echo no BUILD_SRC directory specified - echo using present directory $PWD - BUILD_SRC=$PWD +if ! get_build_src; then + if [[ $no_prompt ]] ; then + echo aborting the build... + echo -e "\e[1;31mNOTE: use '_default_' to explicitly use build source in uci-docker-build repo\e[1;37m" + return 2 + else + echo "Do you want to use the uci-docker-build default build source" + echo "at $BDIR/src " + read -n 1 -p "instead? [y]=>" REPLY + [[ $REPLY != "y" ]] && echo -e "\n" && return 2 + BUILD_SRC=$BDIR/src + echo -e "\n\e[1;31mNOTE: use '_default_' to explicitly use build source in uci-docker-build repo\e[1;37m" + fi fi -if [[ ! $(isAbsPath $BUILD_SRC) ]] ; then - if [[ ${BUILD_SRC} == "_base_" ]]; then - BUILD_SRC=${BDIR}/src - else - BUILD_SRC=$(realpath ${BUILD_SRC}) - fi -fi - -if [[ ! ( -d $BUILD_SRC/packages && -d $BUILD_SRC/init ) ]]; then -echo -e "\e[1;31minvalid build source directory" - echo $BUILD_SRC - echo -e "it does not contain packages and init subirectories\e[1;37m" - if [[ ! $(basename $BUILD_SRC) == "src" ]]; then - echo checking in src/ subdirectory for source - if [[ ( -d $BUILD_SRC/src/packages && -d $BUILD_SRC/src/init ) ]]; then - BUILD_SRC=$BUILD_SRC/src - echo found source in src/ subdirectory changing to source directory to - echo $BUILD_SRC - else - echo -e "\e[1;31mERROR: no build source directory at $BUILD_SRC" - echo -e "with init/ and packages/ subdirectores was found\e[1;37m" - if [[ $no_prompt ]] ; then - echo aborting the build... - echo -e "\e[1;31mNOTE: use '_base_' to explicitly use build source in uci-docker-build repo\e[1;37m" - return 2 - else - echo "Do you want to use the uci-docker-build repo source scripts" - echo "at $BDIR/src " - read -n 1 -p "instead? [y]=>" REPLY - [[ $REPLY != "y" ]] && echo -e "\n" && return 2 - BUILD_SRC=$BDIR/src - echo -e "\n\e[1;31mNOTE: use '_base_' to explicitly use build source in uci-docker-build repo\e[1;37m" - fi - fi - fi -fi -# done processing build source directory - -log_dir=$PWD/logs -mkdir -p $log_dir - - pushd "$BDIR" > /dev/null || return 3 - +TARGET=${TARGET:-default} [[ ! "${targets[@]}" =~ $TARGET ]] && echo $TARGET is not a valid target && echo valid targets are: ${targets[@]} && exit 4 -LINUX_DISTRO=${LINUX_DISTRO:-alpine} - -if [[ $BASE_IMAGE ]]; then -echo determining DISTRO of base image: $BASE_IMAGE -LINUX_DISTRO=$(docker_image_distro $BASE_IMAGE) - [[ ! $LINUX_DISTRO ]] && echo "unable to get base image OS for: $BASE_IMAGE, aborting build" && return 5 - echo $BASE_IMAGE is built from distro $LINUX_DISTRO - else - BASE_IMAGE=$LINUX_DISTRO -fi +get_distro IMAGE_NAME=$(make_image_name $@) @@ -199,90 +173,83 @@ if [[ $(image_exists $IMAGE_NAME) ]]; then fi ARCH=$(get_arch) +log_dir=$PWD/logs +mkdir -p $log_dir +[[ $TARGET == "dev" ]] && VERBOSE=true export BASE_IMAGE export TAG export IMAGE_NAME export LINUX_DISTRO export BUILD_SRC -export KEEP -export SYSADMIN_PW export ARCH +export VERBOSE -echo -e "\e[1;37m********************" -echo "Using scripts source directory at $BUILD_SRC" -echo "Building with base image: $BASE_IMAGE" -#todo based on target form image names -echo "Outputing to image name => $IMAGE_NAME<-arch>:${TAG:-latest}" -[[ $push || $TARGET == "private" ]] && echo "Will push image to ${REPO:-hub.docker.com}" -[[ $TARGET == "deploy" ]] && echo "Will build and push both amd64 and arm64 images to hub.docker.com" -[[ $TARGET == "dev" || ! $TARGET ]] && echo "Building image for local machine with architecture $ARCH" -echo "Linux Distro: $LINUX_DISTRO" -echo "Using build target: ${TARGET:-default}" -echo "Build Command: docker buildx --builder ${builder} bake ${nocache} ${TARGET}" -if [[ $verbose ]]; then - echo -e "\n---------------------------------" - docker buildx bake --print $TARGET - echo -e "\n---------------------------------" - echo "build scripts at $BUILD_SRC to be copied to ${BUILD_DIR:-/build} in container ***** " - ls -la $BUILD_SRC - echo -e "\n----- base init script init.sh ------" - cat $BUILD_SRC/init.sh - echo -e "\n---------------------------------" -fi -echo -e "u********************\e[0;37m" +build_info if [[ ! $no_prompt ]]; then read -n 1 -p "do you want to continue [y]=>" REPLY [[ $REPLY != "y" ]] && echo -e "\n" && return 4 fi -builder=default -if [[ $TARGET == "deploy" ]]; then - builder=deploy - if ! docker buildx ls | grep -q deploy ; then - echo multiarch deploy builder does not exist, creating with docker-container driver - docker buildx create --name deploy --driver docker-container >/dev/null - docker buildx ls | grep deploy - fi +if ! source $BDIR/Dockerfile.d/create; then +echo unable to create Dockerfile from template, aborting build +return 3 fi -[[ $TARGET == "private" && ! $REPO ]] && echo "must use '-r ' if building to private repo" && exit 3 +builder=default +if [[ $TARGET == "publish" ]]; then + builder=publish + pushd "$BDIR" > /dev/null || return 3 + if ! docker buildx ls | grep -q publish ; then + echo publish builder does not exist, creating with docker-container driver + docker buildx create --name publish --driver docker-container >/dev/null + docker buildx ls | grep publish + fi + popd > /dev/null || return 4 +fi -# copy source directory to temporary .src/ subdirectory -# MUST either be readable by all or group readable by docker group -rm -rf $BDIR/.src +# copy or bind build source directory to temporary .src/ subdirectory in build repo +[[ -d $BDIR/.src ]] && rm -rf $BDIR/.src +if [[ $(which rsync 2> /dev/null ) ]]; then rsync -aAru ${BUILD_SRC:-src}/ $BDIR/.src -ls -la $BDIR/.src +else +echo no rsync copying with cp +/bin/cp -a ${BUILD_SRC:-src}/. $BDIR/.src > /dev/null 2>&1 +fi + +if [[ -f $append_efile ]]; then +/bin/cp "$append_efile" "$BDIR/.src/init/env/_build.env_" +echo 'source $ENV_DIR/_build.env_' >> $BDIR/.src/init/build.env +fi + +pushd "$BDIR" > /dev/null || return 3 + +######### RUNNING THE DOCKER BUILD COMMAND ###################### echo running build command: docker buildx --builder ${builder} bake ${nocache} ${TARGET} docker buildx --builder ${builder} bake ${nocache} ${TARGET} 2>&1 | tee "$log_dir/${IMAGE_NAME//\//-}build.log" [[ $? == 0 ]] && echo succcess building image $IMAGE_NAME || exit_abnormal 5 +popd > /dev/null || return 4 rm -rf $BDIR/.src -popd > /dev/null - -if [[ ($try || $TARGET == "dev") ]] && [[ ! $no_prompt ]]; then +if [[ ($TRY_CMD || $TARGET == "dev") ]]; then echo trying newly built image in a container - try_container -m opt $([[ $TARGET == "deploy" ]] && echo -p) $IMAGE_NAME + echo name before try $IMAGE_NAME + try_container build -m opt $([[ $TARGET == "publish" ]] && echo -p) ${TRY_CMD:-shell} fi if [[ $TARGET == "private" ]]; then - # echo pushing arm64 image $IMAGE_NAME to ${REPO:-docker hub} + echo pushing arm64 image $IMAGE_NAME to ${REPO:-docker hub} image_push -a -r $REPO $IMAGE_NAME - # echo pushing amd image $IMAGE_NAME to ${REPO:-docker hub} + echo pushing amd image $IMAGE_NAME to ${REPO:-docker hub} image_push -r $REPO $IMAGE_NAME - else - if [[ $push && (! $TARGET == "dev") ]];then - # echo pushing $IMAGE_NAME to ${REPO:-docker hub} - image_push $([[ $TARGET == "arm" ]] && echo -a) -r $REPO $IMAGE_NAME - fi fi } # if script was executed then call the function -(return 0 2>/dev/null) || docker_image_build $@ +(return 0 2>/dev/null) || udbuild "$@" diff --git a/docker-bake.hcl b/docker-bake.hcl index c5bd5af..94ab768 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -14,12 +14,15 @@ variable "BASE_IMAGE" { variable "SYSADMIN_PW" { default = "" } +variable "VERBOSE" { + default = "" +} variable "ARCH" { default = "" } function "tag" { params = [suffix] - result = [format("${IMAGE_NAME}%s:${TAG}", notequal("", suffix) ? "-${suffix}" : "")] + result = [format("${IMAGE_NAME}%s:${TAG}", notequal("${ARCH}", suffix) ? "-${suffix}" : "")] } # groups group "dev" { @@ -28,10 +31,7 @@ group "dev" { group "default" { targets = ["${ARCH}"] } -group "deploy" { - targets = ["multi"] -} -group "private" { +group "multi" { targets = [ "amd64", "arm64" @@ -39,7 +39,7 @@ group "private" { } # intended for use with default local docker builder # uses 'dev' group in docker-bake.hcl -# assume dev machine is amd64 machine +# assume dev and default build for architecture of local machine target "amd64" { context = "." dockerfile = "Dockerfile" @@ -47,9 +47,10 @@ target "amd64" { LINUX_DISTRO = "${LINUX_DISTRO}" BASE_IMAGE = "${BASE_IMAGE}" TAG = "${TAG}" + VERBOSE = "${VERBOSE}" SYSADMIN_PW = "${SYSADMIN_PW}" } - tags = tag("") + tags = tag("amd64") platforms = ["linux/amd64"] } @@ -61,28 +62,11 @@ target "arm64" { platforms = ["linux/arm64"] } -# must use with docker-container driver for multiarch image deployment to registry -# uses 'deploy' group in docker-bake.hcl -target "multi" { +# must use with docker-container driver for multiarch image publishment to registry +# uses 'publish' group in docker-bake.hcl +target "publish" { inherits = ["amd64"] - tags = tag("") + tags = ["${IMAGE_NAME}:${TAG}"] platforms = ["linux/amd64", "linux/arm64"] output = ["type=registry"] -} - - -// variable "RUSER" { -// default = "" -// } -// function "user" { -// params = [] -// result = [notequal("", RUSER) ? "${RUSER}/" : ""] -// } - -// function "tagamd" { -// params = [] -// result = [ -// format("%s${LINUX_DISTRO}:${TAG}", notequal("", RUSER) ? "${RUSER}/" : ""), -// format("%s${LINUX_DISTRO}-amd64:${TAG}", notequal("", RUSER) ? "${RUSER}/" : "") -// ] -// } \ No newline at end of file +} \ No newline at end of file diff --git a/examples/.env b/examples/.env deleted file mode 100644 index c169afd..0000000 --- a/examples/.env +++ /dev/null @@ -1,6 +0,0 @@ -SYSADMIN_PW=ucommandit -# default is alpine -# LINUX_DISTRO=alpine -RUSER=ucommandit -# TARGET=deploy -BUILD_SRC=../src diff --git a/examples/aliases b/examples/aliases deleted file mode 100755 index 770cfdc..0000000 --- a/examples/aliases +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -# three ways to invoke with --no-cache - -# inline -# NO_CACHE=true ./build "$@" - -# with -n option (prefered) -./build -n "$@" -alias rebuild="build -nfunction_list" - -# as export -#export NO_CACHE=true -#./build "$@" diff --git a/examples/build b/examples/build new file mode 100755 index 0000000..b02c7ad --- /dev/null +++ b/examples/build @@ -0,0 +1,3 @@ +# invokes try with the example environment file +# assumes image already built +udbuild -e example.env "$@" diff --git a/examples/example.build b/examples/example.build deleted file mode 100644 index f6ef0ba..0000000 --- a/examples/example.build +++ /dev/null @@ -1 +0,0 @@ -dbuild -e example.env "$@" diff --git a/examples/example.env b/examples/example.env index 7db779a..8ad4e01 100644 --- a/examples/example.env +++ b/examples/example.env @@ -1,17 +1,20 @@ + +# for easy use copy this file to .env and it will be sourced +# otherwise invoke `udbuild -e example.env` # using a filename of just .env will load it by default # LINUX_DISTRO ignored if BASE_IMAGE is set -LINUX_DISTRO=alpine +# LINUX_DISTRO=alpine # BASE_IMAGE="dockerhubuser/mybase" # tag is 'latest' by default # TAG=1.0.0 -# will be prepended to image name with /, used mostly for deploying -RUSER=dockerhubuser +# will be prepended to image name with /, used mostly for publishing +RUSER=testing # default is hub.docker.com # REPO=my.priviate.repo.net # if using base source this will set the pw for the sysadmin user in the image SYSADMIN_PW=ucommandit -# default target is dev -# TARGET=deploy +# default target is "default" +# TARGET=publish BUILD_SRC=../src # looks for /init and /packages in present directory by default # also looks in src/ subdirectory @@ -19,9 +22,6 @@ BUILD_SRC=../src # use '_base_' to force using the uci-docker-build build source # BUILD_SRC=._base_ # in the image where the build scripts are put /build by default -# BUILD_DIR=/opt/build -# keep the build scripts in the image. default is to remove them after build -# KEEP=true diff --git a/examples/private.env b/examples/private.env new file mode 100644 index 0000000..220dd00 --- /dev/null +++ b/examples/private.env @@ -0,0 +1,6 @@ +# this would push build image to a custome git server that supports packages like gitea +# LINUX_DISTRO=alpine +RUSER=ucommandit +TARGET=private +REPO=git.mygitserver.net +BUILD_SRC=../src diff --git a/examples/publish.env b/examples/publish.env new file mode 100644 index 0000000..1f3201f --- /dev/null +++ b/examples/publish.env @@ -0,0 +1,5 @@ +# LINUX_DISTRO=alpine +# this will publish both arm and amd version to docker hub +RUSER=ucommandit +TARGET=publish +BUILD_SRC=../src diff --git a/examples/try b/examples/try new file mode 100755 index 0000000..57e389b --- /dev/null +++ b/examples/try @@ -0,0 +1,2 @@ +# invokes build with the example environment file +udbuild try -e example.env ${@:-shell} diff --git a/install b/install index ce07ebe..0108f3c 100755 --- a/install +++ b/install @@ -11,19 +11,26 @@ declare -a a="(${PATH//:/ })" for i in ${a[*]}; do [[ $i == $parent ]] && found=true; done if [[ $found ]]; then echo creating a link \'$cmd\' in \'$parent\' to \'$builder\' - if ln -ns $builder/build $target; then + if [[ -f $target ]]; then + echo "$target already exists do you want to overwrite? (y/n) " + read -e ans + [[ ! $ans == "y" ]] && exit 1 + fi + if ln -fns $builder/build $target; then [[ ! $(command -v $cmd) ]] && echo FATAL: link failed $cmd not found in path \ || echo install success: try \'$cmd -h\' now - else + else echo Error creating link echo if \': Permission denied\' 'then' run \'sudo ./install\' fi - else - echo $parent not in current path - echo $PATH - echo link to script not created - echo "add the following export somewhere in your shell (e.g. ~/.bashrc)" - echo "export UDBUILD=$builder/build" - echo 'and then use $UDBUILD to invoke the build script ( e.g $UDBUILD -e mybuild.env)' - echo "or rerun this script using a directory in the system path (e.g ./install /usr/bin build)" - fi \ No newline at end of file + else + echo "Install failed: $parent not in current path" + echo $PATH + echo "link to script not created. your install options are:" + echo "1. add $parent to your PATH" + echo "2. rerun this script using a directory in the system path (e.g ./install /usr/bin build)" + echo "3. add the following export somewhere in your shell (e.g. ~/.bashrc)" + echo " export UDBUILD=$builder/build" + echo ' and then use $UDBUILD to invoke the build script' + echo ' ( e.g $UDBUILD -e Smybuild.env)' +fi \ No newline at end of file diff --git a/lib/bash-tpl b/lib/bash-tpl new file mode 100755 index 0000000..fcb1271 --- /dev/null +++ b/lib/bash-tpl @@ -0,0 +1,1296 @@ +#!/usr/bin/env bash +####################################################################### +# SPDX-License-Identifier: MIT +# Copyright (c) 2021 TekWizely & co-authors +# +# Use of this source code is governed by the MIT license. +# See the accompanying LICENSE file, if present, or visit: +# https://opensource.org/licenses/MIT +####################################################################### +VERSION="v0.7.0" +####################################################################### +# Bash-TPL: A Smart, Lightweight shell script templating engine +# +# Lets you mark up textual files with shell commands and variable +# replacements, while minimally impacting your original file layout. +# +# Templates are compiled into shell scripts that you can invoke +# (along with variables, arguments, etc.) to generate complete and +# well-formatted output text files. +# +# Smart +# +# Encourages you to use extra indentation to write clean, well- +# formatted templates, and smartly removes the indentations from the +# generated template scripts. +# +# This results in both templates that are easily readable and +# maintainable, and generated text files that look as good as if they +# were written by hand. +# +# NOTE: Consistent Formatting +# +# The key to success with Bash-TPL indentation fix-up logic is +# Consistent Formatting; using consistent indentation throughout your +# template will yield best results. +# +# Learn More: +# https://github.com/TekWizely/bash-tpl +####################################################################### + +function usage() { + cat << USAGE +Bash-TPL is a smart, lightweight shell script templating engine + +usage: bash-tpl [flags] [--] file + bash-tpl [flags] - + cat file | bash-tpl [flags] + +options: + -h, --help + show help screen + --version + show version + -o, --output-file + write to specified file (default: stdout) + -- treat remaining options as positional arguments + - read from stdin + +customize delimiters: + --tag-delims 'xx xx' + set tag delimiters (default: '<% %>') + --tag-stmt-delim 'x' + set tag statement delimiter (default: '%') + --stmt-delim 'x+' + set statement delimiter (default: '%') + --stmt-block-delims 'x+ x+' + set statement block delimiters + defaults to statement delimiter if not explicitly set + --txt-delim + --text-delim 'x+[ ]?' (single trailing space allowed) + set text delimiter (default: '% ') + --dir-delim + --directive-delim 'x+' + set directive delimiter (default: '.') + --cmt-delim + --comment-delim 'x+' + set template comment delimiter + defaults to directive delimiter + '#' if not explicitly set + --reset-delims + reset all delimiters to defaults + delim options provided after this option are honored + +supported environment variables: + BASH_TPL_TAG_DELIMS + BASH_TPL_TAG_STMT_DELIM + BASH_TPL_STMT_DELIM + BASH_TPL_STMT_BLOCK_DELIMS + BASH_TPL_TEXT_DELIM + BASH_TPL_DIR_DELIM + BASH_TPL_CMT_DELIM + +example: + $ echo 'Hello <% \$NAME %>' > test.tpl + $ NAME="Chuck Norris" source <( bash-tpl test.tpl ) + + Hello Chuck Norris + +learn more: https://github.com/TekWizely/bash-tpl + +USAGE +} + +####################################################################### +# Delim Functions +####################################################################### + +TAG_DELIM_REGEX='^([^[:blank:]])([^[:blank:]]) ([^[:blank:]])([^[:blank:]])$' +TAG_STMT_DELIM_REGEX='^([^[:blank:]])$' +STMT_DELIM_REGEX='^([^[:blank:]]+)$' +STMT_BLOCK_DELIM_REGEX='^([^[:blank:]]+) ([^[:blank:]]+)$' +STMT_BLOCK_TEXT_REGEX='^([^[:blank:]]+\ ?)$' # Optional trailing ' ' + +## +# reset_delims +# +function reset_delims() { + TAG_START_DELIM1='<' + TAG_START_DELIM2='%' + TAG_STOP_DELIM1='%' + TAG_STOP_DELIM2='>' + + TAG_STMT_DELIM='%' + + STMT_DELIM='%' + + TEXT_DELIM_UNDEFINED=1 + TEXT_DELIM='' + + STMT_BLOCK_DELIM_UNDEFINED=1 + STMT_BLOCK_START_DELIM='' + STMT_BLOCK_STOP_DELIM='' + + DIRECTIVE_DELIM='.' + + COMMENT_DELIM_UNDEFINED=1 + COMMENT_DELIM='' +} + +## +# parse_tag_delims +# $1 = delims +# $2 = src (for error msg) +# +function parse_tag_delims() { + if [[ "${1}" =~ $TAG_DELIM_REGEX ]]; then + TAG_START_DELIM1="${BASH_REMATCH[1]}" + TAG_START_DELIM2="${BASH_REMATCH[2]}" + TAG_STOP_DELIM1="${BASH_REMATCH[3]}" + TAG_STOP_DELIM2="${BASH_REMATCH[4]}" + else + echo "Error: Invalid or missing tag delimiter values for ${2-tag delims}: '${1}'" >&2 + exit 1 + fi +} + +## +# parse_tag_stmt_delims +# $1 = delim +# $2 = src (for error msg) +# +function parse_tag_stmt_delim() { + if [[ "${1}" =~ $TAG_STMT_DELIM_REGEX ]]; then + TAG_STMT_DELIM="${BASH_REMATCH[1]}" + else + echo "Error: Invalid or missing tag stmt delimiter value for ${2-tag stmt delim}: '${1}'" >&2 + exit 1 + fi +} + +## +# parse_stmt_delim +# $1 = delim +# $2 = src (for error msg) +# +function parse_stmt_delim() { + if [[ "${1}" =~ $STMT_DELIM_REGEX ]]; then + STMT_DELIM="${BASH_REMATCH[1]}" + else + echo "Error: Invalid or missing stmt delimiter value for ${2:-stmt delim}: '${1}'" >&2 + exit 1 + fi +} + +## +# parse_stmt_block_delims +# $1 = delims +# $2 = src (for error msg) +# +function parse_stmt_block_delims() { + if [[ "${1}" =~ $STMT_BLOCK_DELIM_REGEX ]]; then + STMT_BLOCK_START_DELIM="${BASH_REMATCH[1]}" + STMT_BLOCK_STOP_DELIM="${BASH_REMATCH[2]}" + STMT_BLOCK_DELIM_UNDEFINED='' + else + echo "Error: Invalid or missing stmt-block delimiter values for ${2:-stmt-block delims}: '${1}'" >&2 + exit 1 + fi +} + +## +# parse_text_delim - Uses STMT delim regex +# $1 = delim +# $2 = src (for error msg) +# +function parse_text_delim() { + if [[ "${1}" =~ $STMT_BLOCK_TEXT_REGEX ]]; then + TEXT_DELIM="${1}" + TEXT_DELIM_UNDEFINED='' + else + echo "Error: Invalid or missing text delimiter value for ${2:-txt delim}: '${1}'" >&2 + exit 1 + fi +} + +## +# parse_directive_delim - Uses STMT delim regex +# $1 = delim +# $2 = src (for error msg) +# +function parse_directive_delim() { + if [[ "${1}" =~ $STMT_DELIM_REGEX ]]; then + DIRECTIVE_DELIM="${1}" + else + echo "Error: Invalid or missing directive delimiter value for ${2:-dir delim}: '${1}'" >&2 + exit 1 + fi +} + +## +# parse_comment_delim - Uses STMT delim regex +# $1 = delim +# $2 = src (for error msg) +# +function parse_comment_delim() { + if [[ "${1}" =~ $STMT_DELIM_REGEX ]]; then + COMMENT_DELIM="${1}" + COMMENT_DELIM_UNDEFINED='' + else + echo "Error: Invalid or missing comment delimiter value for ${2:-cmt delim}: '${1}'" >&2 + exit 1 + fi +} + +## +# reset_template_regexes +# +function reset_template_regexes() { + # Fixup STMT_BLOCK delims - Default to STMT_DELIM if not set + # + if [[ -n "${STMT_BLOCK_DELIM_UNDEFINED}" ]]; then + STMT_BLOCK_START_DELIM="${STMT_DELIM}" + STMT_BLOCK_STOP_DELIM="${STMT_DELIM}" + fi + + # Fixup TEXT delim - Default to STMT_DELIM followed by ' ' if not set + # + if [[ -n "${TEXT_DELIM_UNDEFINED}" ]]; then + TEXT_DELIM="${STMT_DELIM} " # Note trailing space (' ') + fi + + # Fixup COMMENT delim - Default to STMT_DELIM followed by '#' if not set + # + if [[ -n "${COMMENT_DELIM_UNDEFINED}" ]]; then + COMMENT_DELIM="${STMT_DELIM}#" + fi + + # + # Create regexes + # + + local d ds d1 d2 d3 d4 + + d="${DIRECTIVE_DELIM}" + escape_regex d + DIRECTIVE_REGEX="^([[:blank:]]*)${d}([a-zA-Z_-]+)(.*)\$" + + d="${COMMENT_DELIM}" + escape_regex d + COMMENT_REGEX="^([[:blank:]]*)${d}" + + d="${STMT_DELIM}" + escape_regex d + STATEMENT_REGEX="^([[:blank:]]*)${d}[[:blank:]]+(.+)\$" + + d="${STMT_BLOCK_START_DELIM}" + escape_regex d + STATEMENT_BLOCK_START_REGEX="^([[:blank:]]*)${d}[[:blank:]]*\$" + + d="${STMT_BLOCK_STOP_DELIM}" + escape_regex d + STATEMENT_BLOCK_STOP_REGEX="^([[:blank:]]*)${d}[[:blank:]]*\$" + + d="${TEXT_DELIM}" + escape_regex d + STATEMENT_BLOCK_TEXT_REGEX="^([[:blank:]]*)${d}([[:blank:]]*[^[:blank:]](.*))\$" + + TEXT_REGEX='^([[:blank:]]*)([^[:blank:]](.*))?$' + + d1="${TAG_START_DELIM1}" + escape_regex d1 + + d2="${TAG_START_DELIM2}" + escape_regex d2 + + d3="${TAG_STOP_DELIM1}" + escape_regex d3 + + d4="${TAG_STOP_DELIM2}" + escape_regex d4 + + ds="${TAG_STMT_DELIM}" + escape_regex ds + + TAG_TEXT_REGEX="^([^${d1}]+|${d1}$|${d1}[^${d1}${d2}]+)(.*)" + + TAG_STD_REGEX="^${d1}${d2}((([^${d3}])|(${d3}[^${d4}]))*)${d3}${d4}(.*)" + + TAG_QUOTE_REGEX="^${d1}${d2}\"((([^\"])|(\"[^${d3}])|(\"${d3}[^${d4}]))*)\"${d3}${d4}(.*)" + + TAG_STATEMENT_REGEX="^${d1}${d2}${ds}((([^${d3}])|(${d3}[^${d4}]))*)${d3}${d4}(.*)" + + # printf "# ---> delim regexes:" + # printf "# STATEMENT_BLOCK_START_REGEX: '%s'\n" "${STATEMENT_BLOCK_START_REGEX}" + # printf "# STATEMENT_BLOCK_STOP_REGEX: '%s'\n" "${STATEMENT_BLOCK_STOP_REGEX}" + # printf "# COMMENT_REGEX: '%s'\n" "${COMMENT_REGEX}" + # printf "# DIRECTIVE_REGEX: '%s'\n" "${DIRECTIVE_REGEX}" + # printf "# STATEMENT_REGEX: '%s'\n" "${STATEMENT_REGEX}" + # printf "# TAG_TEXT_REGEX: '%s'\n" "${TAG_TEXT_REGEX}" + # printf "# TAG_STD_REGEX: '%s'\n" "${TAG_STD_REGEX}" + # printf "# TAG_QUOTE_REGEX: '%s'\n" "${TAG_QUOTE_REGEX}" + # printf "# TAG_STATEMENT_REGEX: '%s'\n" "${TAG_STATEMENT_REGEX}" +} + +####################################################################### +# Misc Functions +####################################################################### + +## +# trim +# usage: trim varname +# NOTE: Expects value to NOT contain '\n' +# +function trim() { + read -r "$1" <<< "${!1}"$'\n' +} + +## +# escape_regex +# usage: escape_regex varname +# +function escape_regex() { + local result + # shellcheck disable=SC2001 # Too complex for ${variable//search/replace} + # shellcheck disable=SC2016 # Not using expansion, prefer single quotes + # shellcheck disable=SC2034 # ref is used + result=$(sed 's/[][\.|$(){}?+*^]/\\&/g' <<< "${!1}") + printf -v "${1}" "%s" "${result}" +} + +## +# normalize_directive +# usage: normalize_directive varname +# +function normalize_directive() { + local result + # shellcheck disable=SC2034 # ref is used + result=$(tr 'a-z_' 'A-Z-' <<< "${!1}") + printf -v "${1}" "%s" "${result}" +} + +####################################################################### +# STATES +####################################################################### + +STATES=() # empty => DEFAULT +STATE="DEFAULT" # [ DEFAULT, MAYBE_TXT_BLOCK, TXT_BLOCK, START_STMT_BLOCK, STMT_BLOCK ] + +## +# push_state +# +function push_state() { + STATES+=("${STATE}") + STATE="${1}" +} + +## +# pop_state +# +function pop_state() { + if [[ ${#STATES[@]} -gt 0 ]]; then + STATE="${STATES[${#STATES[@]} - 1]}" + unset "STATES[${#STATES[@]}-1]" + else + STATE="DEFAULT" + fi +} + +####################################################################### +# TEXT_INDENTS +####################################################################### + +TEXT_INDENTS=() # empty => "" +TEXT_INDENT="" + +## +# push_text_indent +# +function push_text_indent() { + TEXT_INDENTS+=("${TEXT_INDENT}") + TEXT_INDENT="${1}" +} + +## +# pop_text_indent +# +function pop_text_indent() { + if [[ ${#TEXT_INDENTS[@]} -gt 0 ]]; then + TEXT_INDENT="${TEXT_INDENTS[${#TEXT_INDENTS[@]} - 1]}" + unset "TEXT_INDENTS[${#TEXT_INDENTS[@]} - 1]" + else + TEXT_INDENT="" + fi +} + +####################################################################### +# STATEMENT_INDENTS +####################################################################### + +STATEMENT_INDENTS=() # empty => "" +STATEMENT_INDENT="" + +## +# push_statement_indent +# +function push_statement_indent() { + STATEMENT_INDENTS+=("${STATEMENT_INDENT}") + STATEMENT_INDENT="${1}" +} + +## +# pop_statement_indent +# +function pop_statement_indent() { + if [[ ${#STATEMENT_INDENTS[@]} -gt 0 ]]; then + STATEMENT_INDENT="${STATEMENT_INDENTS[${#STATEMENT_INDENTS[@]} - 1]}" + unset "STATEMENT_INDENTS[${#STATEMENT_INDENTS[@]} - 1]" + else + STATEMENT_INDENT="" + fi +} + +####################################################################### +# BLOCK_INDENTS +####################################################################### + +BLOCK_INDENTS=() # empty => "" +BLOCK_INDENT="" + +## +# push_block_indent +# +function push_block_indent() { + BLOCK_INDENTS+=("${BLOCK_INDENT}") + BLOCK_INDENT="${1}" +} + +## +# pop_block_indent +# +function pop_block_indent() { + if [[ ${#BLOCK_INDENTS[@]} -gt 0 ]]; then + BLOCK_INDENT="${BLOCK_INDENTS[${#BLOCK_INDENTS[@]} - 1]}" + unset "BLOCK_INDENTS[${#BLOCK_INDENTS[@]} - 1]" + else + BLOCK_INDENT="" + fi +} + +####################################################################### +# Print Functions +####################################################################### + +## +# print_statement +# $1 = leading indentation +# $2 = statement +# +function print_statement() { + local indent="${1}" + if [[ "${indent}" == "${BLOCK_INDENT}"* ]]; then + indent="${indent/#$BLOCK_INDENT/}" + fi + printf "%s\n" "${BASE_STMT_INDENT}${STATEMENT_INDENT}${indent}${2}" +} + +## +# print_text - generates a printf statement for template text +# $1 = leading text indentation +# $2 = text +# $3 = leading stmt indentation [OPTIONAL] +# +function print_text() { + local indent="${1}" + if [[ "${indent}" == "${BLOCK_INDENT}"* ]]; then + indent="${indent/#$BLOCK_INDENT/}" + fi + process_tags "${3-${BLOCK_INDENT}}" "${BASE_TEXT_INDENT}${TEXT_INDENT}${indent}${2}" +} + +####################################################################### +# Process Functions +####################################################################### + +## +# process_tags +# $1 = statement indentation +# $2 = full line of text to process +# +function process_tags() { + local stmt_indent line args arg quoted + stmt_indent="${1}" + line="${2}" + args="" + while [ -n "${line}" ]; do + # echo "# LINE @ START: $(declare -p line)" >&2 + if [[ "${line}" =~ $TAG_TEXT_REGEX ]]; then + # echo "# TEXT TAG MATCH: $(declare -p BASH_REMATCH)" >&2 + printf -v quoted "%q" "${BASH_REMATCH[1]}" + args="${args}${quoted}" + line="${BASH_REMATCH[2]}" + elif [[ "${line}" =~ $TAG_QUOTE_REGEX ]]; then + # echo "# QUOTE TAG MATCH: $(declare -p BASH_REMATCH)" >&2 + args="${args}\"${BASH_REMATCH[1]}\"" + line="${BASH_REMATCH[6]}" + elif [[ "${line}" =~ $TAG_STATEMENT_REGEX ]]; then + # echo "# STMT TAG MATCH: $(declare -p BASH_REMATCH)" >&2 + arg="${BASH_REMATCH[1]}" + trim arg + args="${args}\"\$(${arg})\"" + line="${BASH_REMATCH[5]}" + # Check standard regex last as it's a super-set of quote and stmt regex + # + elif [[ "${line}" =~ $TAG_STD_REGEX ]]; then + # echo "# STD TAG MATCH: $(declare -p BASH_REMATCH)" >&2 + arg="${BASH_REMATCH[1]}" + trim arg + args="${args}\"${arg}\"" + line="${BASH_REMATCH[5]}" + # Assume next character is TEXT - extract and process remainder + # + elif [[ "${line}" =~ (.)(.*) ]]; then + # echo "# DEFAULT: Assuming first char is TEXT: $(declare -p line)" + printf -v quoted "%q" "${BASH_REMATCH[1]}" + args="${args}${quoted}" + line="${BASH_REMATCH[2]}" + fi + # echo "# LINE @ END: $(declare -p line)" >&2 + done + local stmt + if [ -n "${args}" ]; then + printf -v stmt "printf \"%%s\\\\n\" %s" "${args}" + else + printf -v stmt "printf \"\\\\n\"" + fi + print_statement "${stmt_indent}" "${stmt}" +} + +DELIM_DIR_TAG_REGEX='[Tt][Aa][Gg]\s*=\s*"([^"]*)"' +DELIM_DIR_TAG_STMT_REGEX='[Tt][Aa][Gg][_-]?[Ss][Tt][Mm][Tt]\s*=\s*"([^"]*)"' +DELIM_DIR_STMT_REGEX='[Ss][Tt][Mm][Tt]\s*=\s*"([^"]*)"' +DELIM_DIR_STMT_BLOCK_REGEX='[Ss][Tt][Mm][Tt][_-]?[Bb][Ll][Oo][Cc][Kk]\s*=\s*"([^"]*)"' +DELIM_DIR_TXT_REGEX='[Tt][Xx][Tt]\s*=\s*"([^"]*)"' +DELIM_DIR_TEXT_REGEX='[Tt][Ee][Xx][Tt]\s*=\s*"([^"]*)"' +DELIM_DIR_DIR_REGEX='[Dd][Ii][Rr]\s*=\s*"([^"]*)"' +DELIM_DIR_DIRECTIVE_REGEX='[Dd][Ii][Rr][Ee][Cc][Tt][Ii][Vv][Ee]\s*=\s*"([^"]*)"' +DELIM_DIR_CMT_REGEX='[Cc][Mm][Tt]\s*=\s*"([^"]*)"' +DELIM_DIR_COMMENT_REGEX='[Cc][Oo][Mm][Mm][Ee][Nn][Tt]\s*=\s*"([^"]*)"' + +## +# process_directive +# $1 = leading_indent +# $2 = directive +# $3 = directive arg(s) +# +function process_directive() { + local directive + directive="${2}" + normalize_directive directive + case "${directive}" in + INCLUDE) + local indent="${1}" + if [[ "${indent}" == "${BLOCK_INDENT}"* ]]; then + indent="${indent/#$BLOCK_INDENT/}" + fi + local args args_arr + args="${3}" + trim args + declare -a args_arr="(${args})" + # shellcheck disable=SC2128 # We choose BASH_SOURCE vs BASH_SOURCE[0] for compatability + "${BASH_SOURCE}" \ + --text-indent "${BASE_TEXT_INDENT}${TEXT_INDENT}${indent}" \ + --stmt-indent "${BASE_STMT_INDENT}${STATEMENT_INDENT}${indent}" \ + --block-indent "${BASE_BLOCK_INDENT}${BLOCK_INDENT}" \ + --tag-delims "${TAG_START_DELIM1}${TAG_START_DELIM2} ${TAG_STOP_DELIM1}${TAG_STOP_DELIM2}" \ + --tag-stmt-delim "${TAG_STMT_DELIM}" \ + --stmt-delim "${STMT_DELIM}" \ + --stmt-block-delims "${STMT_BLOCK_START_DELIM} ${STMT_BLOCK_STOP_DELIM}" \ + --txt-delim "${TEXT_DELIM}" \ + --dir-delim "${DIRECTIVE_DELIM}" \ + --cmt-delim "${COMMENT_DELIM}" \ + "${args_arr[@]}" + ;; + DELIMS) + # TAG + # + if [[ "${3}" =~ $DELIM_DIR_TAG_REGEX ]]; then + parse_tag_delims "${BASH_REMATCH[1]}" 'DELIMS TAG directive' + fi + # TAG-STMT + # + if [[ "${3}" =~ $DELIM_DIR_TAG_STMT_REGEX ]]; then + parse_tag_stmt_delim "${BASH_REMATCH[1]}" 'DELIMS TAG-STMT directive' + fi + # STMT + # + if [[ "${3}" =~ $DELIM_DIR_STMT_REGEX ]]; then + parse_stmt_delim "${BASH_REMATCH[1]}" 'DELIMS STMT directive' + fi + # STMT-BLOCK + # + if [[ "${3}" =~ $DELIM_DIR_STMT_BLOCK_REGEX ]]; then + parse_stmt_block_delims "${BASH_REMATCH[1]}" '"DELIMS STMT-BLOCK directive' + fi + # TEXT + # + if [[ "${3}" =~ $DELIM_DIR_TXT_REGEX || "${3}" =~ $DELIM_DIR_TEXT_REGEX ]]; then + parse_text_delim "${BASH_REMATCH[1]}" 'DELIMS TEXT directive' + fi + # DIRECTIVE + # + if [[ "${3}" =~ $DELIM_DIR_DIR_REGEX || "${3}" =~ $DELIM_DIR_DIRECTIVE_REGEX ]]; then + parse_directive_delim "${BASH_REMATCH[1]}" 'DELIMS DIR directive' + fi + # COMMENT + # + if [[ "${3}" =~ $DELIM_DIR_CMT_REGEX || "${3}" =~ $DELIM_DIR_COMMENT_REGEX ]]; then + parse_comment_delim "${BASH_REMATCH[1]}" 'DELIMS CMT directive' + fi + # Apply changes + # + reset_template_regexes + ;; + RESET-DELIMS) + reset_delims + reset_template_regexes + ;; + *) # unsupported directive + echo "Error: Unknown directive: '${directive}' - Skipping" >&2 + ;; + esac +} + +function debug_array() { + printf "[" + local need_comma="" + while [[ ${#@} -gt 0 ]]; do + if [ -n "${need_comma:-}" ]; then + printf ", " + fi + if [ -n "${1:-}" ]; then + printf "'%q'" "${1}" + else + printf "''" + fi + need_comma=1 + shift + done + printf "]" +} +## +# debug_state logs the state of the global variables. +# To use this set the BASH_TPL_DEBUG variable to a non-empty value +# when invoking the script. +# TODO Track source input file+line numbers +# $1 = template line | EOF +# +function debug_state() { + printf "#<< ---------------\n" + printf "#LINE TEXT : '%s'\n" "${1-}" + printf "#STATE : %s\n" "${STATE:-}" + printf "#STATES : %s\n" "$(debug_array "${STATES[@]}")" + printf "#TEXT_INDENT : %q\n" "${TEXT_INDENT:-}" + printf "#TEXT_INDENTS : %s\n" "$(debug_array "${TEXT_INDENTS[@]}")" + printf "#STATEMENT_INDENT : %q\n" "${STATEMENT_INDENT:-}" + printf "#STATEMENT_INDENTS: %s\n" "$(debug_array "${STATEMENT_INDENTS[@]}")" + printf "#BLOCK_INDENT : %q\n" "${BLOCK_INDENT:-}" + printf "#BLOCK_INDENTS : %s\n" "$(debug_array "${BLOCK_INDENTS[@]}")" + printf "#TEXT_BLOCK_LINES_INDENT : %q\n" "${TEXT_BLOCK_LINES_INDENT}" + printf "#TEXT_BLOCK_LINES_INDENT_SET : %q\n" "${TEXT_BLOCK_LINES_INDENT_SET}" + printf "#STATEMENT_BLOCK_LINES_INDENT : %q\n" "${STATEMENT_BLOCK_LINES_INDENT}" + printf "#STATEMENT_BLOCK_LINES_INDENT_STATE : %q\n" "${STATEMENT_BLOCK_LINES_INDENT_STATE}" + printf "#>> ---------------\n" +} + +function process_line() { + [ -n "${BASH_TPL_DEBUG:-}" ] && debug_state ${@+"$@"} + state_"${STATE}" ${@+"$@"} +} + +function process_stdin() { + local line + while IFS="" read -r line || [ -n "${line}" ]; do + process_line "${line}" + done + # EOF - Notify states + # Call with no args + # + while [[ "${STATE}" != "DEFAULT" ]]; do + process_line + done + process_line # DEFAULT +} + +####################################################################### +# State Handler Functions +####################################################################### + +## +# state_DEFAULT +# Not inside any blocks +# Assumes *_INDENT and STATE arrays are empty +# +function state_DEFAULT() { + [[ ${#@} -gt 0 ]] || return # Exit early on EOF + # Line is a statement + # + if [[ "${1}" =~ $STATEMENT_REGEX ]]; then + print_statement "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" + push_statement_indent "${BASH_REMATCH[1]}" + push_state "MAYBE_TXT_BLOCK" + # Line is a statement block start + # + elif [[ "${1}" =~ $STATEMENT_BLOCK_START_REGEX ]]; then + push_statement_indent "${BASH_REMATCH[1]}" + push_state "START_STMT_BLOCK" + # Line is a directive + # + elif [[ "${1}" =~ $DIRECTIVE_REGEX ]]; then + process_directive "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" + # Line is a comment + # + elif [[ "${1}" =~ $COMMENT_REGEX ]]; then + : # Comments do not generate output + # Line is text + # NOTE : Check LAST because regex always matches + # + elif [[ "${1}" =~ $TEXT_REGEX ]]; then + print_text "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" + fi +} + +TEXT_BLOCK_LINES=() +TEXT_BLOCK_LINES_INDENT="" +TEXT_BLOCK_LINES_INDENT_SET="" + +## +# state_MAYBE_TXT_BLOCK +# Previous line was a statement +# We might be starting a text block +# NOTE: Assumes +# push_state "MAYBE_TXT_BLOCK" +# push_statement_indent +# +function state_MAYBE_TXT_BLOCK() { + # If there's a line to process (i.e. not EOF) + # + if [[ ${#@} -gt 0 ]]; then + # Current line is empty + # Considered text block content, + # but doesn't contribute to indentation tracking + # + if [[ "${1}" == "" ]]; then + TEXT_BLOCK_LINES+=("${1}") # Save line + return + # Current line is a block-end statement, + # i.e. it's a statement at the same indentation as the start statement + # + elif [[ "${1}" =~ $STATEMENT_REGEX && "${BASH_REMATCH[1]}" == "${STATEMENT_INDENT}" ]]; then + # We've saved a FULL text block ! + # Use computed indentation + # + push_text_indent "${TEXT_INDENT}${STATEMENT_INDENT/#$BLOCK_INDENT/}" # Additive + push_statement_indent "${TEXT_BLOCK_LINES_INDENT}" + push_block_indent "${TEXT_BLOCK_LINES_INDENT}" + local state_marker=${#STATES[@]} # Save for cleanup + push_state "TXT_BLOCK" + # Text blocks can be nested, so save lines and cleanup *before* processing + # + local lines=("${TEXT_BLOCK_LINES[@]}") + TEXT_BLOCK_LINES=() + TEXT_BLOCK_LINES_INDENT="" + TEXT_BLOCK_LINES_INDENT_SET="" + # Process saved lines now in new state + # + local line + for line in "${lines[@]}"; do + process_line "${line}" + done + # Clean up our TXT_BLOCK state and any other danglers + # + while [[ ${#STATES[@]} -gt $state_marker ]]; do + process_line # EOF + done + # Clean up our MAYBE_TXT_BLOCK state + # + pop_statement_indent + pop_state + # Process close block in parent context + # + process_line "${1}" + return + # Capture line indentation for tracking + # TEXT_REGEX is perfect for this, so just re-use it + # NOTE: Regex always matches + # + elif [[ "${1}" =~ $TEXT_REGEX ]]; then + TEXT_BLOCK_LINES+=("${1}") # Save line + # If current line is indented + # + if [[ "${BASH_REMATCH[1]}" != "${STATEMENT_INDENT}" && "${BASH_REMATCH[1]}" == "${STATEMENT_INDENT}"* ]]; then + # If first time through + # + if [[ "${TEXT_BLOCK_LINES_INDENT_SET}" == "" ]]; then + # Track current indentation + # + TEXT_BLOCK_LINES_INDENT="${BASH_REMATCH[1]}" + TEXT_BLOCK_LINES_INDENT_SET="1" + return + # If current line is indented SAME OR LESS than tracked + # + elif [[ "${TEXT_BLOCK_LINES_INDENT}" == "${BASH_REMATCH[1]}"* ]]; then + # Update tracked indentation (may set to same value) + # + TEXT_BLOCK_LINES_INDENT="${BASH_REMATCH[1]}" + return + # If current line is indented MORE than tracked + # + elif [[ "${BASH_REMATCH[1]}" == "${TEXT_BLOCK_LINES_INDENT}"* ]]; then + # No change + # + return + # Neither line is a subset of the other + # + else + : # Here for completeness + fi + # Current line is NOT indented + # + else + : # Here for completeness + fi + fi + # EOF + # + else + : # Fall through + fi + # If we haven't returned by now, then we're not in a text block + # Discard saved state and process saved lines + # + pop_statement_indent + pop_state + # Text blocks can be nested, so save lines and cleanup *before* processing + # + local lines=("${TEXT_BLOCK_LINES[@]}") + # Clean up + # + TEXT_BLOCK_LINES=() + TEXT_BLOCK_LINES_INDENT="" + TEXT_BLOCK_LINES_INDENT_SET="" + # Process saved lines now in parent context + # TODO "push" these back onto primary line-processing stream? + # + local line + for line in "${lines[@]}"; do + process_line "${line}" + done +} + +## +# state_TXT_BLOCK +# NOTE: Assumes +# Called within MAYBE_TXT_BLOCK with a complete block to process +# Will NOT be called with TXT Block Close +# Every line has a minimum indentation of BLOCK_INDENT +# push_state TXT_BLOCK +# push_text_indent +# push_statement_indent +# push_block_indent +# +function state_TXT_BLOCK() { + # EOF + # + if [[ ${#@} -eq 0 ]]; then + # End of text block + # Discard saved state + # + pop_text_indent + pop_statement_indent + pop_block_indent + pop_state + # Current line is empty + # + elif [[ "${1}" == "" ]]; then + process_tags "${BLOCK_INDENT}" "" + # Current line is a statement + # + elif [[ "${1}" =~ $STATEMENT_REGEX ]]; then + print_statement "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" + push_statement_indent "${BASH_REMATCH[1]}" + push_state "MAYBE_TXT_BLOCK" + # Current line is a statement Start Block + # + elif [[ "${1}" =~ $STATEMENT_BLOCK_START_REGEX ]]; then + push_statement_indent "${BASH_REMATCH[1]}" + push_state "START_STMT_BLOCK" + # Current line is a directive + # + elif [[ "${1}" =~ $DIRECTIVE_REGEX ]]; then + process_directive "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" + # Line is a comment + # + elif [[ "${1}" =~ $COMMENT_REGEX ]]; then + : # Comments do not generate output + # Line is text + # NOTE: Regex always matches + # + elif [[ "${1}" =~ $TEXT_REGEX ]]; then + print_text "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" + fi +} + +STATEMENT_BLOCK_LINES=() +STATEMENT_BLOCK_LINES_INDENT="" +STATEMENT_BLOCK_LINES_INDENT_STATE="" + +## +# state_START_STMT_BLOCK +# NOTE: Assumes +# push_state "START_STMT_BLOCK" +# push_statement_indent +# +function state_START_STMT_BLOCK() { + # If there's a line to process (i.e. not EOF) + # + if [[ ${#@} -gt 0 ]]; then + # Current line is empty + # Considered statement block content, + # but doesn't contribute to indentation tracking + # + if [[ "${1}" == "" ]]; then + STATEMENT_BLOCK_LINES+=("${1}") # Save line + return + # Current line is a statement block end + # + elif [[ "${1}" =~ $STATEMENT_BLOCK_STOP_REGEX ]]; then + # If indentation does not match block-open, then error + # TODO Track line numbers for better reporting + # + if [[ "${BASH_REMATCH[1]}" != "${STATEMENT_INDENT}" ]]; then + echo "Error: stmt-block close indentation does not match open" >&2 + exit 1 + fi + # We've saved a FULL statement block ! + # Is it fully indented? + # + if [[ "${STATEMENT_BLOCK_LINES_INDENT_STATE}" == "1" ]]; then + # Use computed indentation + # + push_text_indent "${TEXT_INDENT}${STATEMENT_INDENT/#$BLOCK_INDENT/}" # Additive + push_block_indent "${STATEMENT_BLOCK_LINES_INDENT}" + else + # If not consistently indented, default to no indent + # TODO Print warning? + # + push_text_indent "" + pop_statement_indent + push_statement_indent "" + push_block_indent "" + fi + # Process the saved lines + # NOTE: Statement block end (+ cleanup) will be processed by STMT_BLOCK handler + # + pop_state + push_state "STMT_BLOCK" + # Process saved lines now in new state + # Statement blocks do not nest, so we use global and cleanup *after* + # + local line + for line in "${STATEMENT_BLOCK_LINES[@]}"; do + process_line "${line}" + done + # Clean up our STMT_BLOCK state + # + process_line # EOF + # Clean up + # + STATEMENT_BLOCK_LINES=() + STATEMENT_BLOCK_LINES_INDENT="" + STATEMENT_BLOCK_LINES_INDENT_STATE="" + # Capture line indentation for tracking + # TEXT_REGEX is perfect for this, so just re-use it + # NOTE: Regex always matches + # + elif [[ "${1}" =~ $TEXT_REGEX ]]; then + STATEMENT_BLOCK_LINES+=("${1}") # Save line + # If current line is indented (or even) + # + if [[ "${BASH_REMATCH[1]}" == "${STATEMENT_INDENT}"* ]]; then + # If first time through + # + if [[ "${STATEMENT_BLOCK_LINES_INDENT_STATE}" == "" ]]; then + # Track current indentation + # + STATEMENT_BLOCK_LINES_INDENT="${BASH_REMATCH[1]}" + STATEMENT_BLOCK_LINES_INDENT_STATE="1" + # If still working with fully indented block + # + elif [[ "${STATEMENT_BLOCK_LINES_INDENT_STATE}" == "1" ]]; then + # If current line is indented SAME OR LESS than tracked + # + if [[ "${STATEMENT_BLOCK_LINES_INDENT}" == "${BASH_REMATCH[1]}"* ]]; then + # Update tracked indentation (may set to same value) + # + STATEMENT_BLOCK_LINES_INDENT="${BASH_REMATCH[1]}" + # If current line is indented MORE than tracked + # + elif [[ "${BASH_REMATCH[1]}" == "${STATEMENT_BLOCK_LINES_INDENT}"* ]]; then + # No change + # + : + # Neither line is a subset of the other + # + else + STATEMENT_BLOCK_LINES_INDENT_STATE="2" + fi + fi + # Current line is NOT indented (or even) + # + else + STATEMENT_BLOCK_LINES_INDENT_STATE="3" + fi + fi + # EOF + # + else + # EOF before close block reached is an error + # TODO Track line numbers for better reporting + # + echo "Error: Missing stmt-block close ('${STMT_BLOCK_STOP_DELIM}')" >&2 + exit 1 + fi +} + +## +# state_STMT_BLOCK +# NOTE: Assumes +# Called within START_STMT_BLOCK with a complete block to process +# Will NOT be called with STMT Block Close +# push_state STMT_BLOCK +# push_text_indent +# push_statement_indent +# push_block_indent +# +function state_STMT_BLOCK() { + # EOF + # + if [[ ${#@} -eq 0 ]]; then + # End of statement block + # Discard saved state + # + pop_text_indent + pop_statement_indent + pop_block_indent + pop_state + # Current line is empty + # + elif [[ "${1}" == "" ]]; then + # TODO Do we need a flag to print BASE_STMT_INDENT when included? + # + printf "\n" + # Line is text + # + elif [[ "${1}" =~ $STATEMENT_BLOCK_TEXT_REGEX ]]; then + print_text "${BLOCK_INDENT}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[1]}" + # Line is assumed to be a statement + # TEXT_REGEX is perfect for this, so just re-use it + # NOTE: Regex always matches + # + elif [[ "${1}" =~ $TEXT_REGEX ]]; then + print_statement "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" + fi +} + +####################################################################### +# Main +####################################################################### + +function version() { + printf "%s\n" "${VERSION}" +} + +## +# parse_env_delims - Set Delims from Environment Vars +# +function parse_env_delims() { + if [ -n "${BASH_TPL_TAG_DELIMS}" ]; then + parse_tag_delims "${BASH_TPL_TAG_DELIMS}" "BASH_TPL_TAG_DELIMS" + fi + + if [ -n "${BASH_TPL_TAG_STMT_DELIM}" ]; then + parse_tag_stmt_delim "${BASH_TPL_TAG_STMT_DELIM}" "BASH_TPL_TAG_STMT_DELIM" + fi + + if [ -n "${BASH_TPL_STMT_DELIM}" ]; then + parse_stmt_delim "${BASH_TPL_STMT_DELIM}" "BASH_TPL_STMT_DELIM" + fi + + if [ -n "${BASH_TPL_STMT_BLOCK_DELIMS}" ]; then + parse_stmt_block_delims "${BASH_TPL_STMT_BLOCK_DELIMS}" "BASH_TPL_STMT_BLOCK_DELIMS" + fi + + if [ -n "${BASH_TPL_TEXT_DELIM}" ]; then + parse_text_delim "${BASH_TPL_TEXT_DELIM}" "BASH_TPL_TEXT_DELIM" + fi + + if [ -n "${BASH_TPL_DIR_DELIM}" ]; then + parse_directive_delim "${BASH_TPL_DIR_DELIM}" "BASH_TPL_DIR_DELIM" + fi + + if [ -n "${BASH_TPL_CMT_DELIM}" ]; then + parse_comment_delim "${BASH_TPL_CMT_DELIM}" "BASH_TPL_CMT_DELIM" + fi +} + +## +# parse_args +# $@ args to parse +# +# Stores positional args in global array __ARGS[@] +# +function parse_args() { + __ARGS=() # Global + while (($#)); do + case "$1" in + -h | --help) + usage + exit 0 + ;; + --version) + version + exit 0 + ;; + -o | --output-file) + if [ -n "${2}" ]; then + OUTPUT_FILE="${2}" + else + echo "Error: Invalid or missing value for --output-file: '${2}'" >&2 + exit 1 + fi + shift 2 + ;; + --reset-delims) + # Reset delims immediately - Any delim flags after this will be honored + # + reset_delims + shift + ;; + --tag-delims) + parse_tag_delims "$2" "$1" + shift 2 + ;; + --tag-stmt-delim) + parse_tag_stmt_delim "$2" "$1" + shift 2 + ;; + --stmt-delim) + parse_stmt_delim "$2" "$1" + shift 2 + ;; + --stmt-block-delims) + parse_stmt_block_delims "$2" "$1" + shift 2 + ;; + --txt-delim | --text-delim) + parse_text_delim "$2" "$1" + shift 2 + ;; + --dir-delim | --directive-delim) + parse_directive_delim "$2" "$1" + shift 2 + ;; + --cmt-delim | --comment-delim) + parse_comment_delim "$2" "$1" + shift 2 + ;; + --text-indent) + BASE_TEXT_INDENT="${2}" + shift 2 + ;; + --stmt-indent) + BASE_STMT_INDENT="${2}" + shift 2 + ;; + --block-indent) + BASE_BLOCK_INDENT="${2}" + shift 2 + ;; + -) + __ARGS+=("$1") + shift + ;; + --) + shift + while (($#)); do + __ARGS+=("$1") + shift + done + ;; + --* | -*) # unsupported flags + echo "Error: unknown flag: '$1'; use -h for help" >&2 + exit 1 + ;; + *) # preserve positional arguments + __ARGS+=("$1") + shift + ;; + esac + done +} + +function main() { + + OUTPUT_FILE="" + + BASE_TEXT_INDENT="" + BASE_STMT_INDENT="" + BASE_BLOCK_INDENT="" + + reset_delims + parse_env_delims + + parse_args "$@" + set -- "${__ARGS[@]}" + unset __ARGS + + # No file argument + # + if [[ -z "${1}" ]]; then + # Nothing waiting on stdin + # + if [[ -t 0 ]]; then + usage + exit 1 + fi + else + # File argument is explicitly stdin + # + if [[ "${1}" == '-' ]]; then + shift + else + # File argument points to non-existing/readable file + # + if [[ ! -r "${1}" ]]; then + echo "File not found: '${1}'" >&2 + exit 1 + fi + # File argument is good, re-route it to stdin + # + exec < "${1}" + shift + fi + fi + + reset_template_regexes + + if [[ -n "${OUTPUT_FILE}" ]]; then + exec > "${OUTPUT_FILE}" + fi + + process_stdin +} + +# Only process main logic if not being sourced (ie tested) +# +(return 0 2> /dev/null) || main "$@" \ No newline at end of file diff --git a/lib/cmds/01-image-name b/lib/cmds/01-image-name deleted file mode 100755 index 8352383..0000000 --- a/lib/cmds/01-image-name +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/bash - -image_name () { - -local tag; local efile - -# generate a full image name with tag -# $1 name, $2 user(or repo), $3 repo - -# [[ $# -lt 1 ]] && echo "image base name required" && exit - -declare OPTION; declare OPTARG; declare OPTIND -while getopts 'e:ag:r:u:' OPTION; do -# echo processing: option:$OPTION argument:$OPTARG index:$OPTIND remaining:${@:$OPTIND} -case "$OPTION" in -e) - efile=$OPTARG - ;; -r) - REPO=$OPTARG - ;; -u) - RUSER=$OPTARG -;; -g) - TAG=$OPTARG - ;; -a) # add -arm64 to image - arm=arm64 - ;; -*) echo unknown run option -$OPTARG - echo "USAGE: tag " - echo "available options: -a add -arm64 to tag, -d delete tag " -;; -esac -done - -shift $((OPTIND - 1)) -echo $efile -if [[ $efile ]]; then - [[ ! -f $efile ]] && efile=$SDIR/$efile - if [[ -f $efile ]]; then - source $efile - [[ ! $? -eq 0 ]] && echo source of $efile failed, exiting && return 2 - else - echo no environment file at $efile, exiting - return 2 - fi - echo "----------" - echo loaded environment filen $efile - cat $efile - echo "----------" -fi - -tag=$( echo $1 | cut -s -d ":" -f2) -tag=${tag:-$TAG} -name=${1%:*} -user=${2:-$RUSER} -repo=${3:-$REPO} - -tag=$([[ $repo ]] && echo ${repo}/)$([[ $user ]] && echo ${user}/)$name$([[ $arm ]] && echo -arm64):${TAG:-latest} - -echo $tag - -} - -# if script was executed then call the function -(return 0 2>/dev/null) || image_name $@ \ No newline at end of file diff --git a/lib/cmds/01-image-name.sh b/lib/cmds/01-image-name.sh new file mode 100755 index 0000000..d7e90b1 --- /dev/null +++ b/lib/cmds/01-image-name.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +image_name () { + +local tag; local efile; local suffix + +# generate a full image name with tag +# $1 name, $2 user(or repo), $3 repo + +# [[ $# -lt 1 ]] && echo "image base name required" && exit + +declare OPTION; declare OPTARG; declare OPTIND +while getopts 'e:s:g:r:u:' OPTION; do +# echo processing: option:$OPTION argument:$OPTARG index:$OPTIND remaining:${@:$OPTIND} +case "$OPTION" in +e) + efile=$OPTARG + ;; +u) + RUSER=$OPTARG +;; +g) + TAG=$OPTARG + ;; +s) # add -arm64 to image + suffix=$OPTARG + ;; +*) echo unknown image-name option -$OPTARG + echo "USAGE: image_name " + echo "available options: -s : add - , -g: tag, -u: repo user, -e: env file" +;; +esac +done + +shift $((OPTIND - 1)) + + +source_env_file $efile + +tag=$( echo $1 | cut -s -d ":" -f2) +TAG=${tag:-$TAG} +name=${1%:*} +shift + +get_distro +echo $(make_image_name $name $@)$([[ $suffix ]] && echo -$suffix):${TAG:-latest} +} + +# if script was executed then call the function +(return 0 2>/dev/null) || image_name $@ \ No newline at end of file diff --git a/lib/cmds/help.md b/lib/cmds/help.md new file mode 100644 index 0000000..2e56677 --- /dev/null +++ b/lib/cmds/help.md @@ -0,0 +1,89 @@ + +# UCOMMANDIT DOCKER BUILD SCRIPT + + Image Build Script: Creates one or more images using a target per the docker-bake.hcl file + +## USAGE + + `udbuild