I have written a simple wrapper for docker build
and docker run
that allows me to build and run my image without having to use the docker
command directly.
Here's the layout of my code:
esp8266
├── scripts
│ ├── config.sh <- configuration
│ └── util.sh <- utility functions
├── docker <- docker image root
│ └── Dockerfile
├── build <- script to build image
└── make <- script to run image
And the files themselves:
scripts/config.sh
readonly DOCKER_IMAGE_NAME="jackwilsdon/esp8266"
readonly DOCKER_IMAGE_TAG="0.1.0"
readonly DOCKER_IMAGE_MOUNT="/mnt/data"
readonly DOCKER_LOCAL_SOURCE_DIRECTORY="docker"
readonly DOCKER_LOCAL_MOUNT_DIRECTORY="data"
scripts/util.sh
#!/usr/bin/env bash
#
# Utilities for esp8266 development container scripts
# usage: print [args...]
print() {
echo "${CURRENT_SCRIPT}: $*"
}
# usage: print_raw [args...]
print_raw() {
echo "$*"
}
# usage: error [args...]
error() {
print "error: $*" >&2
}
# usage: error_raw [args...]
error_raw() {
print_raw "$*" >&2
}
# usage: check_requirements
check_requirements() {
if [[ -z "${CURRENT_SCRIPT}" ]]; then
error "CURRENT_SCRIPT not set"
exit 1
fi
if [[ -z "${CURRENT_DIRECTORY}" ]]; then
error "CURRENT_DIRECTORY not set"
exit 1
fi
if [[ -z "${DOCKER_BINARY}" ]]; then
error "unable to find docker (DOCKER_BINARY not set)"
return 1
fi
}
# usage: docker [args...]
docker() {
"${DOCKER_BINARY}" "$@"
local exit_code="$?"
if [[ "${exit_code}" -ne 0 ]]; then
error "${DOCKER_BINARY} exited with code ${exit_code}"
return 1
fi
}
# usage: arg_or_default arg default [new arg value]
arg_or_default() {
if [[ -n "$1" ]]; then
if [[ "$#" -eq 3 ]]; then
echo "$3"
else
echo "$1"
fi
else
echo "$2"
fi
}
build
#!/usr/bin/env bash
#
# Build esp8266 development container
readonly CURRENT_SCRIPT="$(basename -- ${BASH_SOURCE[0]})"
readonly CURRENT_DIRECTORY="$(cd "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly DOCKER_BINARY="$(command -v docker)"
source "${CURRENT_DIRECTORY}/scripts/config.sh"
source "${CURRENT_DIRECTORY}/scripts/util.sh"
# usage: usage [printer]
usage() {
local printer="$(arg_or_default "$1" 'print_raw')"
"${printer}" "usage: ${CURRENT_SCRIPT} [-h] [TAG]"
}
# usage: full_usage [printer]
full_usage() {
local printer="$(arg_or_default "$1" 'print_raw')"
usage "${printer}"
"${printer}"
"${printer}" 'Build tool for esp8266 development container'
"${printer}"
"${printer}" 'arguments:'
"${printer}" ' -h show this help message and exit'
"${printer}" ' TAG the tag of the image to build'
}
# usage: build_image [tag]
build_image() {
# Generate image name
local name="${DOCKER_IMAGE_NAME}:$(arg_or_default "$1" \
"${DOCKER_IMAGE_TAG}")"
print "building image ${name}"
# Run docker with the provided arguments
docker build -t "${name}" \
"${CURRENT_DIRECTORY}/${DOCKER_LOCAL_SOURCE_DIRECTORY}"
}
# usage: main [-h] [-d DATA_DIRECTORY] [-t TAG] [ARGS...]
main() {
check_requirements "$@" || exit 1
while getopts ':h' OPT; do
case "${OPT}" in
h)
full_usage
exit 0
;;
?)
full_usage
print
error "invalid argument: ${OPTARG}"
exit 1
;;
esac
done
shift $((OPTIND - 1))
build_image "$@"
}
if [[ "$0" == "${BASH_SOURCE[0]}" ]]; then
main "$@"
fi
make
#!/usr/bin/env bash
#
# Run esp8266 development container
readonly CURRENT_SCRIPT="$(basename -- ${BASH_SOURCE[0]})"
readonly CURRENT_DIRECTORY="$(cd "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly DOCKER_BINARY="$(command -v docker)"
source "${CURRENT_DIRECTORY}/scripts/config.sh"
source "${CURRENT_DIRECTORY}/scripts/util.sh"
# usage: usage [printer]
usage() {
local printer="$(arg_or_default "$1" 'print_raw')"
"${printer}" "usage: ${CURRENT_SCRIPT} [-h] [-d DATA_DIRECTORY] [-t TAG]" \
"[ARGS...]"
}
# usage: full_usage [printer]
full_usage() {
local printer="$(arg_or_default "$1" 'print_raw')"
usage "${printer}"
"${printer}"
"${printer}" 'Make tool for esp8266 development container'
"${printer}"
"${printer}" 'arguments:'
"${printer}" ' -h show this help message and exit'
"${printer}" ' -d DATA_DIRECTORY mount point for /mnt/data inside the'
"${printer}" ' container'
"${printer}" ' -t TAG the tag of the image to run'
"${printer}" ' ARGS... the command to run in the container'
}
# usage: run_image [data_directory] [tag] [args...]
run_image() {
local arguments=(run --rm --interactive --tty --entrypoint /sbin/my_init)
# Generate volume and image names
local volume="$(arg_or_default "$1" \
"${CURRENT_DIRECTORY}/${DOCKER_LOCAL_MOUNT_DIRECTORY}")"
local name="${DOCKER_IMAGE_NAME}:$(arg_or_default "$2" \
"${DOCKER_IMAGE_TAG}")"
# Add volume and name to arguments
arguments+=(-v "${volume}:/mnt/data" ${name})
# Remove the first two arguments from the argument list
shift 2
# Run docker with the provided arguments
docker "${arguments[@]}" -- "$@"
}
# usage: main [-h] [-d DATA_DIRECTORY] [-t TAG] [ARGS...]
main() {
check_requirements "$@" || exit 1
local docker_volume=''
local docker_image_tag=''
while getopts ':hd:t:' OPT; do
case "${OPT}" in
h)
full_usage
exit 0
;;
d)
docker_volume="${OPTARG}"
;;
t)
docker_image_tag="${OPTARG}"
;;
?)
full_usage error_raw
error_raw
error "invalid argument: ${OPTARG}"
exit 1
;;
esac
done
shift $((OPTIND - 1))
run_image "${docker_volume}" "${docker_image_tag}" "$@"
}
if [[ "$0" == "${BASH_SOURCE[0]}" ]]; then
main "$@"
fi
This image is primarily designed for development and ease of use, which is why I have created these shell scripts.
Here's some examples of how to use the scripts:
$ ./build -h
usage: build [-h] [TAG]
Build tool for esp8266 development container
arguments:
-h show this help message and exit
TAG the tag of the image to build
$ ./build example_tag
build: building image jackwilsdon/esp8266:example_tag
Sending build context to Docker daemon 3.584 kB
Step 1 : FROM phusion/baseimage:latest
---> c39664f3d4e5
Step 2 : MAINTAINER Jack Wilsdon <[email protected]>
---> Using cache
---> 4ec300220c6c
...
Step 7 : VOLUME /mnt/data
---> Using cache
---> f9f8a21a0d16
Successfully built f9f8a21a0d16
$ ./make -h
usage: make [-h] [-d DATA_DIRECTORY] [-t TAG] [ARGS...]
Make tool for esp8266 development container
arguments:
-h show this help message and exit
-d DATA_DIRECTORY mount point for /mnt/data inside the
container
-t TAG the tag of the image to run
ARGS... the command to run in the container
$ ./make whoami
*** Running /etc/my_init.d/00_regen_ssh_host_keys.sh...
*** Running /etc/rc.local...
*** Booting runit daemon...
*** Runit started as PID 9
*** Running whoami...
root
*** whoami exited with status 0.
*** Shutting down runit daemon (PID 9)...
*** Killing all processes...
I'm rather new to shell scripting, however I'd like to ensure my scripts are as robust as possible, is there anything I can do to improve their robustness?
make
might come back and bite you one day. \$\endgroup\$