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 Bastille supports a templating system allowing you to apply files, pkgs and
execute commands inside the container automatically. execute commands inside the container automatically.
Currently supported template hooks are: `LIMITS`, `INCLUDE`, `PRE`, `FSTAB`, Currently supported template hooks are: `ARG`, `LIMITS`, `INCLUDE`, `PRE`,
`PKG`, `OVERLAY`, `SYSRC`, `SERVICE`, `CMD`. `FSTAB`, `PKG`, `OVERLAY`, `SYSRC`, `SERVICE`, `CMD`, `RENDER`.
Planned template hooks include: `PF`, `LOG` Planned template hooks include: `PF`, `LOG`
Templates are created in `${bastille_prefix}/templates` and can leverage any of Templates are created in `${bastille_prefix}/templates` and can leverage any of
@@ -656,7 +656,8 @@ Template hooks are executed in specific order and require specific syntax to
work as expected. This table outlines that order and those requirements: work as expected. This table outlines that order and those requirements:
| SUPPORTED | format | example | | SUPPORTED | format | example |
|-----------|---------------------|------------------------------------------------| |-----------|-----------------------|------------------------------------------------|
| ARG | name=value (one/line) | domain=example.com (omit value for no default) |
| LIMITS | resource value | memoryuse 1G | | LIMITS | resource value | memoryuse 1G |
| INCLUDE | template path/URL | http?://TEMPLATE_URL or username/base-template | | INCLUDE | template path/URL | http?://TEMPLATE_URL or username/base-template |
| PRE | /bin/sh command | mkdir -p /usr/local/path | | PRE | /bin/sh command | mkdir -p /usr/local/path |
@@ -666,6 +667,7 @@ work as expected. This table outlines that order and those requirements:
| SYSRC | sysrc command(s) | nginx_enable=YES | | SYSRC | sysrc command(s) | nginx_enable=YES |
| SERVICE | service command(s) | nginx restart | | SERVICE | service command(s) | nginx restart |
| CMD | /bin/sh command | /usr/bin/chsh -s /usr/local/bin/zsh | | CMD | /bin/sh command | /usr/bin/chsh -s /usr/local/bin/zsh |
| RENDER | paths (one/line) | /usr/local/etc/nginx |
| PLANNED | format | example | | 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; `\"`) 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 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 into the container. This is done by placing the files in their full path, using the
template directory as "/". 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 the file should begin with an uppercase reference to a Bastille command
followed by its arguments (omitting the target, which is deduced from the followed by its arguments (omitting the target, which is deduced from the
`template` arguments). Lines beginning with `#` are treated as comments. `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: Bastillefile example:
```shell ```shell
LIMITS memoryuse 1G 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. # Install and start nginx.
PKG nginx PKG nginx
SYSRC nginx_enable=YES SYSRC nginx_enable=YES
@@ -719,6 +737,9 @@ SERVICE nginx restart
# Copy files to nginx. # Copy files to nginx.
CP www/ usr/local/www/nginx-dist/ 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. # Create a file on the server containing the jail's hostname.
CMD hostname > /usr/local/www/nginx-dist/hostname.txt 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 and a template name. As covered in the previous section, template names
correspond to directory names in the `bastille/templates` directory. 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 ```shell
ishmael ~ # bastille template folsom username/base ishmael ~ # bastille template folsom username/base --arg domain=example.com
[folsom]: [folsom]:
Copying files... Copying files...
Copy complete. Copy complete.

View File

@@ -49,6 +49,62 @@ post_command_hook() {
esac 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. # Handle special-case commands first.
case "$1" in case "$1" in
help|-h|--help) help|-h|--help)
@@ -56,7 +112,7 @@ help|-h|--help)
;; ;;
esac esac
if [ $# -ne 1 ]; then if [ $# -lt 1 ]; then
bastille_usage bastille_usage
fi fi
@@ -87,7 +143,27 @@ if [ -z "${JAILS}" ]; then
fi fi
if [ -z "${HOOKS}" ]; then 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 fi
## global variables ## global variables
@@ -113,6 +189,23 @@ for _jail in ${JAILS}; do
fi fi
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 if [ -s "${bastille_template}/Bastillefile" ]; then
# Ignore blank lines and comments. -- cwells # Ignore blank lines and comments. -- cwells
SCRIPT=$(grep -v '^\s*$' "${bastille_template}/Bastillefile" | grep -v '^\s*#') SCRIPT=$(grep -v '^\s*$' "${bastille_template}/Bastillefile" | grep -v '^\s*#')
@@ -121,19 +214,27 @@ for _jail in ${JAILS}; do
' '
set -f set -f
for _line in ${SCRIPT}; do for _line in ${SCRIPT}; do
# First word converted to lowercase is the Bastille command. -- cwells
_cmd=$(echo "${_line}" | awk '{print tolower($1);}') _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 # Apply overrides for commands/aliases and arguments. -- cwells
case $_cmd in 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) cmd)
# Allow redirection within the jail. -- cwells # Allow redirection within the jail. -- cwells
_args="sh -c '${_args}'" _args="sh -c '${_args}'"
;; ;;
overlay)
_cmd='cp'
_args="${bastille_template}/${_args} /"
;;
cp|copy) cp|copy)
_cmd='cp' _cmd='cp'
# Convert relative "from" path into absolute path inside the template directory. -- cwells # Convert relative "from" path into absolute path inside the template directory. -- cwells
@@ -145,8 +246,16 @@ for _jail in ${JAILS}; do
_cmd='mount' ;; _cmd='mount' ;;
include) include)
_cmd='template' ;; _cmd='template' ;;
overlay)
_cmd='cp'
_args="${bastille_template}/${_args} /"
;;
pkg) pkg)
_args="install -y ${_args}" ;; _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 esac
if ! eval "bastille ${_cmd} ${_jail} ${_args}"; then if ! eval "bastille ${_cmd} ${_jail} ${_args}"; then
@@ -185,6 +294,10 @@ for _jail in ${JAILS}; do
continue ;; continue ;;
PRE) PRE)
_cmd='cmd' ;; _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 esac
echo -e "${COLOR_GREEN}[${_jail}]:${_hook} -- START${COLOR_RESET}" echo -e "${COLOR_GREEN}[${_jail}]:${_hook} -- START${COLOR_RESET}"
@@ -198,6 +311,8 @@ for _jail in ${JAILS}; do
if [ -z "${_line}" ]; then if [ -z "${_line}" ]; then
continue continue
fi fi
# Replace "arg" variables in this line with the provided values. -- cwells
_line=$(echo "${_line}" | eval "sed ${ARG_REPLACEMENTS}")
eval "_args=\"${_args_template}\"" eval "_args=\"${_args_template}\""
bastille "${_cmd}" "${_jail}" ${_args} || exit 1 bastille "${_cmd}" "${_jail}" ${_args} || exit 1
done < "${bastille_template}/${_hook}" done < "${bastille_template}/${_hook}"