#!/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 [] | ]" 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 #################################################################