You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ngcpcfg/scripts/patch

330 lines
9.1 KiB

#!/bin/bash
# Purpose: *.patchtt functionality for ngcpcfg config/templates
################################################################################
set -e
set -u
# support testsuite
FUNCTIONS="${FUNCTIONS:-/usr/share/ngcp-ngcpcfg/functions/}"
HELPER="${HELPER:-/usr/share/ngcp-ngcpcfg/helper/}"
SCRIPTS="${SCRIPTS:-/usr/share/ngcp-ngcpcfg/scripts/}"
if ! [ -r "${FUNCTIONS}"/main ] ; then
printf "Error: %s/main could not be read. Exiting.\n" "${FUNCTIONS}" >&2
exit 1
fi
# shellcheck disable=SC1090
. "${FUNCTIONS}"/main
cd "${NGCPCTL_MAIN}"
## functions {{{
patch_help() {
export TIME_FORMAT=''
log_info "'ngcpcfg patch' walks through all templates searching for '*.patchtt.tt2' files"
log_info "and generates '*.customtt.tt2' files based on the original template."
log_info "The option '--from-customtt' simplifies migration from customtt to patchtt."
log_info ""
log_info "Sample:"
log_info " ngcpcfg patch [--help | --from-customtt [<customtt file(s)>] | <patchtt file(s)>]"
log_info ""
log_info "Run 'man ngcpcfg' for more information."
}
patch_search() {
local name="$1"
shift # remove the first 'patchtt/customtt'
local files=("$@")
local fileslist
fileslist=$(mktemp -t ngcpcfg-patch-search.XXXXXXXXXX)
local regexp=""
local hosts=()
if [ -n "${HOST_FILE:-}" ]; then
hosts+=("${HOST_FILE}")
fi
if [ -n "${PAIR_FILE:-}" ]; then
hosts+=("${PAIR_FILE}")
fi
if [ -n "${NODE_FILE:-}" ]; then
hosts+=("${NODE_FILE}")
fi
for val in "${hosts[@]}"; do
regexp="${regexp}|\\${val}"
done
local awk_regexp=".*${name}\.tt2$"
if [ -n "${regexp}" ]; then
awk_regexp=".*${name}\.tt2(${regexp#|})?$"
fi
log_debug "Searching for ${name} files (requested '${files[*]}') awk_regexp:${awk_regexp}"
for dir in ${CONFIG_POOL} ; do
[ -n "${dir}" ] || log_error "${dir} doesn't exist"
log_debug "Iterate over all files in ${TEMPLATE_POOL_BASE%/}/${dir#/}"
while read -r file ; do
if [ "${#files[@]}" = "0" ] ; then
log_debug "NO arguments provided via cmdline"
log_debug "Found ${name} '${file}'"
echo "${file}" >> "${fileslist}"
else
# arguments (file list/pattern) provided via cmdline
for arg in "${files[@]}"; do
if echo "${file}" | grep -q -- "${arg}" ; then
log_debug "Processing ${name} '${file}' as requested"
echo "${file}" >> "${fileslist}"
fi
done
fi
done < <(find "${TEMPLATE_POOL_BASE%/}/${dir#/}" -regextype awk -iregex "${awk_regexp}")
done
# output patch list, make sure we provide the file names just once
sort -u "${fileslist}"
if [ -n "${DEBUG:-}" ] ; then
# send to stderr since stdout is used from outside
log_debug "Not removing temporary fileslist files since we are in debug mode:" >&2
log_debug " fileslist = ${fileslist}" >&2
else
rm -f "${fileslist}"
fi
}
patch_validate_patch() {
local patch="$1"
log_debug "Validating patch: '${patch}'"
if [ ! -f "${patch}" ] ; then
log_error "Missing patch file '${patch}'"
bad_files+=("${patch}")
return 1
fi
local template="${patch%%.patchtt*}.tt2"
if [ -f "${template}" ] ; then
log_debug "Found template for the patch: '${template}'"
else
log_error "Missing template '${template}'"
log_error " for patch '${patch}'"
bad_files+=("${patch}")
return 1
fi
local customtt="${patch//.patchtt/.customtt}"
if [ -f "${customtt}" ] ; then
log_debug "Overwriting customtt '${customtt}'"
else
log_debug "Not found customtt for the patch: '${customtt}'"
fi
}
patch_apply() {
local patch="$1"
local apply="${2:-false}"
local template="${patch%%.patchtt*}.tt2"
local customtt="${patch//.patchtt/.customtt}"
local patch_output
patch_output=$(mktemp -t ngcpcfg-patch-out.XXXXXXXXXX)
local patch_opts=()
patch_opts+=("--input=${patch}")
patch_opts+=("--prefix=/dev/null") # do not produce .orig backup files
patch_opts+=("--reject-file=-") # do not produce .rej file
patch_opts+=(-F0 -N) # disable fuzzy logic to be as safe as possible
if "${apply}" ; then
patch_opts+=("--output=${customtt}")
else
patch_opts+=(--dry-run)
patch_opts+=("--output=/dev/null")
fi
log_debug "Generating customtt '${customtt}' from '${patch}' (apply=${apply})"
log_debug "Executing: patch ${patch_opts[*]} ${template}"
if patch "${patch_opts[@]}" "${template}" >"${patch_output}" 2>&1 ; then
if "${apply}" ; then
log_info "Successfully created '${customtt}'"
else
log_debug "Patch '${patch}' can be applied"
fi
good_files+=("${patch}")
else
log_error "The patch '${patch}' cannot be applied:"
cat "${patch_output}" >&2
bad_files+=("${patch}")
fi
if [[ -f "${template}" && -f "${customtt}" ]] ; then
chmod --reference="${template}" "${customtt}"
chown --reference="${template}" "${customtt}"
fi
rm -f "${patch_output}"
}
patch_validate_customtt() {
local file="$1"
log_debug "Validating customtt: '${file}'"
if [ ! -f "${file}" ] ; then
log_error "Missing customtt file '${file}'"
bad_files+=("${file}")
return 1
fi
local template="${file%%.customtt*}.tt2"
if [ -f "${template}" ] ; then
log_debug "Found template for the customtt: '${template}'"
else
log_error "Missing template for customtt '${customtt}'"
bad_files+=("${customtt}")
return 1
fi
local patchtt="${file//.customtt/.patchtt}"
if [ -f "${patchtt}" ] ; then
log_debug "Overwriting patchtt '${patchtt}'"
else
log_debug "Not found patchtt for the customtt: '${patchtt}'"
fi
}
patch_import_customtt() {
local customtt_file="$1"
local apply="${2:-false}"
local template="${customtt_file%%.customtt*}.tt2"
local patchtt="${customtt_file//.customtt/.patchtt}"
local tmp_patchtt
tmp_patchtt=$(mktemp -t ngcpcfg-patch-import.XXXXXXXXXX)
local diff_output
diff_output=$(mktemp -t ngcpcfg-patch-diff.XXXXXXXXXX)
# diff exit status is 0 if inputs are the same, 1 if different, 2 if trouble.
log_debug "Generating temporary patchtt '${tmp_patchtt}' from template '${template}' and customtt '${customtt_file}'"
case "$(diff -u "${template}" "${customtt_file}" > "${tmp_patchtt}" 2>"${diff_output}" && echo $? || echo $?)" in
0)
log_error "No difference between customtt '${customtt_file}' and template '${template}' (patchtt is not necessary here, remove or change customtt file)"
bad_files+=("${customtt_file}")
;;
1)
log_debug "Successfully processed diff for customtt '${customtt_file}'"
good_files+=("${customtt_file}")
;;
2)
log_error "Diff failed between template '${template}' and customtt file '${customtt_file}':"
cat "${diff_output}" >&2
bad_files+=("${customtt_file}")
;;
*)
log_error "We should not be here. Aborting (better safe then sorry)."
exit 1
;;
esac
if "${apply}" ; then
log_info "Creating patchtt file '${patchtt}'"
log_debug "Removing first 2 lines (filename + date) from 'diff -u' output (due to time changes on '--from-customtt')"
tail -n +3 "${tmp_patchtt}" > "${patchtt}"
fi
rm -f "${diff_output}"
}
patch_import() {
mapfile -t files < <(patch_search "customtt" "$@")
log_debug "Validating customtt files"
for customtt in "${files[@]}" ; do
log_info "Validating customtt '${customtt}'"
if patch_validate_customtt "${customtt}" ; then
patch_import_customtt "${customtt}" "false"
fi
done
if [ "${#bad_files[@]}" != "0" ] ; then
log_debug "Aborted here due to failed patch validation above"
return
fi
log_debug "Validating patchtt files from customtt"
for customtt in "${files[@]}" ; do
patch_import_customtt "${customtt}" "true"
done
}
patch_patch() {
mapfile -t files < <(patch_search "patchtt" "$@")
for patch in "${files[@]}" ; do
log_info "Validating patch '${patch}'"
if patch_validate_patch "${patch}" ; then
patch_apply "${patch}" "false"
fi
done
if [ "${#bad_files[@]}" != "0" ] ; then
log_debug "Aborted here due to failed patch validation above"
return
fi
for patch in "${files[@]}" ; do
log_info "Applying patch '${patch}'"
patch_apply "${patch}" "true"
done
}
patch_footer() {
local label="$1"
if [ "${#bad_files[@]}" = "0" ] ; then
if [ "${#good_files[@]}" = "0" ] ; then
log_info "No ${label} files found, nothing to patch."
else
log_info "Requested ${label} operation has finished successfully."
fi
else
log_error "Some operations above finished with an error for the file(s):"
mapfile -t bad_files_unique < <(echo "${bad_files[@]}" | tr ' ' '\n' | sort -u)
printf '\t%s\n' "${bad_files_unique[@]}"
RC=1
fi
}
## }}}
declare -a bad_files=()
declare -a good_files=()
patch_type=patchtt
while [ -n "${1:-}" ] ; do
case "${1:-}" in
--help)
patch_help
exit 0
;;
--from-customtt)
patch_type=customtt
shift
;;
*)
break
;;
esac
done
if [ "${patch_type}" = 'patchtt' ]; then
patch_patch "$@"
else
patch_import "$@"
fi
patch_footer "${patch_type}"
exit "${RC:-0}"
## END OF FILE #################################################################