Add support for template args

Closes 211.
This commit is contained in:
Chris Wells
2020-09-05 20:13:13 -04:00
parent 5b096e82ed
commit 2225f48f05
2 changed files with 161 additions and 21 deletions

View File

@@ -631,8 +631,8 @@ Templates](https://gitlab.com/BastilleBSD-Templates)?
Bastille supports a templating system allowing you to apply files, pkgs and
execute commands inside the container automatically.
Currently supported template hooks are: `LIMITS`, `INCLUDE`, `PRE`, `FSTAB`,
`PKG`, `OVERLAY`, `SYSRC`, `SERVICE`, `CMD`.
Currently supported template hooks are: `ARG`, `LIMITS`, `INCLUDE`, `PRE`,
`FSTAB`, `PKG`, `OVERLAY`, `SYSRC`, `SERVICE`, `CMD`, `RENDER`.
Planned template hooks include: `PF`, `LOG`
Templates are created in `${bastille_prefix}/templates` and can leverage any of
@@ -655,17 +655,19 @@ echo "usr" > /usr/local/bastille/templates/username/base-template/OVERLAY
Template hooks are executed in specific order and require specific syntax to
work as expected. This table outlines that order and those requirements:
| SUPPORTED | format | example |
|-----------|---------------------|------------------------------------------------|
| LIMITS | resource value | memoryuse 1G |
| INCLUDE | template path/URL | http?://TEMPLATE_URL or username/base-template |
| PRE | /bin/sh command | mkdir -p /usr/local/path |
| FSTAB | fstab syntax | /host/path container/path nullfs ro 0 0 |
| PKG | port/pkg name(s) | vim-console zsh git-lite tree htop |
| OVERLAY | paths (one/line) | etc usr |
| SYSRC | sysrc command(s) | nginx_enable=YES |
| SERVICE | service command(s) | nginx restart |
| CMD | /bin/sh command | /usr/bin/chsh -s /usr/local/bin/zsh |
| SUPPORTED | format | example |
|-----------|-----------------------|------------------------------------------------|
| ARG | name=value (one/line) | domain=example.com (omit value for no default) |
| LIMITS | resource value | memoryuse 1G |
| INCLUDE | template path/URL | http?://TEMPLATE_URL or username/base-template |
| PRE | /bin/sh command | mkdir -p /usr/local/path |
| FSTAB | fstab syntax | /host/path container/path nullfs ro 0 0 |
| PKG | port/pkg name(s) | vim-console zsh git-lite tree htop |
| OVERLAY | paths (one/line) | etc usr |
| SYSRC | sysrc command(s) | nginx_enable=YES |
| SERVICE | service command(s) | nginx restart |
| CMD | /bin/sh command | /usr/bin/chsh -s /usr/local/bin/zsh |
| RENDER | paths (one/line) | /usr/local/etc/nginx |
| PLANNED | format | example |
|---------|------------------|----------------------------------------------------------------|
@@ -674,6 +676,12 @@ work as expected. This table outlines that order and those requirements:
Note: SYSRC requires NO quotes or that quotes (`"`) be escaped. ie; `\"`)
Any name provided in the ARG file can be used as a variable in the other hooks.
For example, `name=value` in the ARG file will cause instances of `${name}`
to be replaced with `value`. The `RENDER` hook can be used to specify files or
directories whose contents should have the variables replaced. Values can be specified
either through the command line when applying the template or as a default in the ARG file.
In addition to supporting template hooks, Bastille supports overlaying files
into the container. This is done by placing the files in their full path, using the
template directory as "/".
@@ -705,12 +713,22 @@ create a `Bastillefile` inside the base template directory. Each line in
the file should begin with an uppercase reference to a Bastille command
followed by its arguments (omitting the target, which is deduced from the
`template` arguments). Lines beginning with `#` are treated as comments.
Variables can also be defined using `ARG` with one `name=value` pair per
line. Subsequent references to `${name}` would be replaced by `value`.
Note that argument values are not available for use until after the point
at which they are defined in the file.
Bastillefile example:
```shell
LIMITS memoryuse 1G
# This value can be overridden when the template is applied.
ARG domain=example.com
# Replace all argument variables inside the nginx config.
RENDER /usr/local/etc/nginx
# Install and start nginx.
PKG nginx
SYSRC nginx_enable=YES
@@ -719,6 +737,9 @@ SERVICE nginx restart
# Copy files to nginx.
CP www/ usr/local/www/nginx-dist/
# Use the "domain" arg to create a file on the server containing the domain.
CMD echo "${domain}" > /usr/local/www/nginx-dist/domain.txt
# Create a file on the server containing the jail's hostname.
CMD hostname > /usr/local/www/nginx-dist/hostname.txt
@@ -735,8 +756,12 @@ Bastille includes a `template` sub-command. This sub-command requires a target
and a template name. As covered in the previous section, template names
correspond to directory names in the `bastille/templates` directory.
To provide values for arguments defined by `ARG` in the template, pass the
optional `--arg` parameter as many times as needed. Alternatively, use
`--arg-file <fileName>` with one `name=value` pair per line.
```shell
ishmael ~ # bastille template folsom username/base
ishmael ~ # bastille template folsom username/base --arg domain=example.com
[folsom]:
Copying files...
Copy complete.

View File

@@ -49,6 +49,62 @@ post_command_hook() {
esac
}
get_arg_name() {
echo "${1}" | sed -E 's/=.*//'
}
parse_arg_value() {
# Parses the value after = and then escapes back/forward slashes and single quotes in it. -- cwells
echo "${1}" | sed -E 's/[^=]+=?//' | sed -e 's/\\/\\\\/g' -e 's/\//\\\//g' -e 's/'\''/'\''\\'\'\''/g'
}
get_arg_value() {
_name_value_pair="${1}"
shift
_arg_name="$(get_arg_name "${_name_value_pair}")"
# Remaining arguments in $@ are the script arguments, which take precedence. -- cwells
for _script_arg in "$@"; do
case ${_script_arg} in
--arg)
# Parse whatever is next. -- cwells
_next_arg='true' ;;
*)
if [ "${_next_arg}" = 'true' ]; then # This is the parameter after --arg. -- cwells
_next_arg=''
if [ "$(get_arg_name "${_script_arg}")" = "${_arg_name}" ]; then
parse_arg_value "${_script_arg}"
return
fi
fi
;;
esac
done
# Check the ARG_FILE if one was provided. --cwells
if [ -n "${ARG_FILE}" ]; then
# To prevent a false empty value, only parse the value if this argument exists in the file. -- cwells
if grep "^${_arg_name}=" "${ARG_FILE}" > /dev/null 2>&1; then
parse_arg_value "$(grep "^${_arg_name}=" "${ARG_FILE}")"
return
fi
fi
# Return the default value, which may be empty, from the name=value pair. -- cwells
parse_arg_value "${_name_value_pair}"
}
render() {
_file_path="${1}/${2}"
if [ -d "${_file_path}" ]; then # Recursively render every file in this directory. -- cwells
find "${_file_path}" \( -type d -name .git -prune \) -o -type f -print0 | $(eval "xargs -0 sed -i '' ${ARG_REPLACEMENTS}")
elif [ -f "${_file_path}" ]; then
eval "sed -i '' ${ARG_REPLACEMENTS} '${_file_path}'"
else
echo -e "${COLOR_YELLOW}Path not found for render: ${2}${COLOR_RESET}"
fi
}
# Handle special-case commands first.
case "$1" in
help|-h|--help)
@@ -56,7 +112,7 @@ help|-h|--help)
;;
esac
if [ $# -ne 1 ]; then
if [ $# -lt 1 ]; then
bastille_usage
fi
@@ -87,7 +143,27 @@ if [ -z "${JAILS}" ]; then
fi
if [ -z "${HOOKS}" ]; then
HOOKS='LIMITS INCLUDE PRE FSTAB PF PKG OVERLAY CONFIG SYSRC SERVICE CMD'
HOOKS='LIMITS INCLUDE PRE FSTAB PF PKG OVERLAY CONFIG SYSRC SERVICE CMD RENDER'
fi
# Check for an --arg-file parameter. -- cwells
for _script_arg in "$@"; do
case ${_script_arg} in
--arg-file)
# Parse whatever is next. -- cwells
_next_arg='true' ;;
*)
if [ "${_next_arg}" = 'true' ]; then # This is the parameter after --arg-file. -- cwells
_next_arg=''
ARG_FILE="${_script_arg}"
break
fi
;;
esac
done
if [ -n "${ARG_FILE}" ] && [ ! -f "${ARG_FILE}" ]; then
error_exit "File not found: ${ARG_FILE}"
fi
## global variables
@@ -113,6 +189,23 @@ for _jail in ${JAILS}; do
fi
fi
# This is parsed outside the HOOKS loop so an ARG file can be used with a Bastillefile. -- cwells
ARG_REPLACEMENTS=''
if [ -s "${bastille_template}/ARG" ]; then
while read _line; do
if [ -z "${_line}" ]; then
continue
fi
_arg_name=$(get_arg_name "${_line}")
_arg_value=$(get_arg_value "${_line}" "$@")
if [ -z "${_arg_value}" ]; then
echo -e "${COLOR_YELLOW}No value provided for arg: ${_arg_name}${COLOR_RESET}"
fi
# Build a list of sed commands like this: -e 's/${username}/root/g' -e 's/${domain}/example.com/g'
ARG_REPLACEMENTS="${ARG_REPLACEMENTS} -e 's/\${${_arg_name}}/${_arg_value}/g'"
done < "${bastille_template}/ARG"
fi
if [ -s "${bastille_template}/Bastillefile" ]; then
# Ignore blank lines and comments. -- cwells
SCRIPT=$(grep -v '^\s*$' "${bastille_template}/Bastillefile" | grep -v '^\s*#')
@@ -121,19 +214,27 @@ for _jail in ${JAILS}; do
'
set -f
for _line in ${SCRIPT}; do
# First word converted to lowercase is the Bastille command. -- cwells
_cmd=$(echo "${_line}" | awk '{print tolower($1);}')
_args=$(echo "${_line}" | awk '{$1=""; sub(/^ */, ""); print;}')
# Rest of the line with "arg" variables replaced will be the arguments. -- cwells
_args=$(echo "${_line}" | awk '{$1=""; sub(/^ */, ""); print;}' | eval "sed ${ARG_REPLACEMENTS}")
# Apply overrides for commands/aliases and arguments. -- cwells
case $_cmd in
arg) # This is a template argument definition. -- cwells
_arg_name=$(get_arg_name "${_args}")
_arg_value=$(get_arg_value "${_args}" "$@")
if [ -z "${_arg_value}" ]; then
echo -e "${COLOR_YELLOW}No value provided for arg: ${_arg_name}${COLOR_RESET}"
fi
# Build a list of sed commands like this: -e 's/${username}/root/g' -e 's/${domain}/example.com/g'
ARG_REPLACEMENTS="${ARG_REPLACEMENTS} -e 's/\${${_arg_name}}/${_arg_value}/g'"
continue
;;
cmd)
# Allow redirection within the jail. -- cwells
_args="sh -c '${_args}'"
;;
overlay)
_cmd='cp'
_args="${bastille_template}/${_args} /"
;;
cp|copy)
_cmd='cp'
# Convert relative "from" path into absolute path inside the template directory. -- cwells
@@ -145,8 +246,16 @@ for _jail in ${JAILS}; do
_cmd='mount' ;;
include)
_cmd='template' ;;
overlay)
_cmd='cp'
_args="${bastille_template}/${_args} /"
;;
pkg)
_args="install -y ${_args}" ;;
render) # This is a path to one or more files needing arguments replaced by values. -- cwells
render "${bastille_jail_path}" "${_args}"
continue
;;
esac
if ! eval "bastille ${_cmd} ${_jail} ${_args}"; then
@@ -185,6 +294,10 @@ for _jail in ${JAILS}; do
continue ;;
PRE)
_cmd='cmd' ;;
RENDER) # This is a path to one or more files needing arguments replaced by values. -- cwells
render "${bastille_jail_path}" "${_line}"
continue
;;
esac
echo -e "${COLOR_GREEN}[${_jail}]:${_hook} -- START${COLOR_RESET}"
@@ -198,6 +311,8 @@ for _jail in ${JAILS}; do
if [ -z "${_line}" ]; then
continue
fi
# Replace "arg" variables in this line with the provided values. -- cwells
_line=$(echo "${_line}" | eval "sed ${ARG_REPLACEMENTS}")
eval "_args=\"${_args_template}\""
bastille "${_cmd}" "${_jail}" ${_args} || exit 1
done < "${bastille_template}/${_hook}"