Add `backups` command.

`backups` provides subcommands for managing backups of the hosts file.
This commit is contained in:
William Melody 2020-03-15 18:42:16 -07:00
parent ada8cb6019
commit 4eed5251da
4 changed files with 405 additions and 4 deletions

View File

@ -46,6 +46,7 @@ A package for Arch users is also [available in the AUR](https://aur.archlinux.or
Usage:
hosts [<search string>]
hosts add <ip> <hostname> [<comment>]
hosts backups [create | [compare | delete | restore | show] <filename>]
hosts block <hostname>...
hosts disable (<ip> | <hostname> | <search string>)
hosts disabled
@ -109,6 +110,31 @@ Description:
Add a given IP address and hostname pair, along with an optional comment.
```
### `hosts backups`
```text
Usage:
hosts backups
hosts backups create
hosts backups compare <name>
hosts backups delete <name>
hosts backups restore <name> --skip-backup
hosts backups show <name>
Subcommands:
backups List available backups.
backups create Create a new backup of the hosts file.
backups compare Compare a backup file with the current hosts file.
The diff tool configured for git will be used if
one is set.
backups delete Delete the specified backup.
backups restore Replace the contents of the hosts file with a
specified backup. The hosts file is automatically
backed up before being overwritten unless the
'--skip-backup' flag is specified.
backups show Show the contents of the specified backup file.
```
### `hosts block`
```text

145
hosts
View File

@ -611,6 +611,7 @@ Version: ${_VERSION}
Usage:
${_ME} [<search string>]
${_ME} add <ip> <hostname> [<comment>]
${_ME} backups [create | [compare | delete | restore | show] <filename>]
${_ME} block <hostname>...
${_ME} disable (<ip> | <hostname> | <search string>)
${_ME} disabled
@ -634,8 +635,6 @@ Options:
Help:
${_ME} help [<command>]
$(commands)
HEREDOC
fi
}
@ -750,6 +749,148 @@ add() {
fi
}
# --------------------------------------------------------------------- backups
desc "backups" <<HEREDOC
Usage:
${_ME} backups
${_ME} backups create
${_ME} backups compare <name>
${_ME} backups delete <name>
${_ME} backups restore <name> --skip-backup
${_ME} backups show <name>
Subcommands:
backups List available backups.
backups create Create a new backup of the hosts file.
backups compare Compare a backup file with the current hosts file.
The diff tool configured for git will be used if
one is set.
backups delete Delete the specified backup.
backups restore Replace the contents of the hosts file with a
specified backup. The hosts file is automatically
backed up before being overwritten unless the
'--skip-backup' flag is specified.
backups show Show the contents of the specified backup file.
HEREDOC
backups() {
local _subcommand="${1:-}"
shift
local _hosts_dirname
_hosts_dirname="$(dirname "${HOSTS_PATH}")"
case "${_subcommand}" in
create)
_verify_write_permissions "$@"
local _timestamp
_timestamp="$(date +"%Y%m%d%H%M%S")"
cp "${HOSTS_PATH}" "${HOSTS_PATH}--backup-${_timestamp}" && \
printf "Backed up to %s--backup-%s\n" "${HOSTS_PATH}" "${_timestamp}"
;;
compare)
if [[ -z "${1:-}" ]]
then
"$_ME" help backups
exit 1
elif [[ ! -e "${_hosts_dirname}/${1}" ]]
then
printf "Backup not found: %s\n" "${1:-}"
exit 1
fi
local _difftool="diff"
if command -v git &>/dev/null
then
_difftool="$(git config --get merge.tool)"
fi
"${_difftool}" "${HOSTS_PATH}" "${_hosts_dirname}/${1}"
;;
delete)
if [[ -z "${1:-}" ]]
then
"$_ME" help backups
exit 1
fi
_verify_write_permissions "$@"
if [[ "${HOSTS_PATH}" != "${_hosts_dirname}/${1:-}" ]] &&
[[ -e "${_hosts_dirname}/${1:-}" ]]
then
rm "${_hosts_dirname}/${1:-}" &&
printf "Backup deleted: %s\n" "${_hosts_dirname}/${1:-}"
else
printf "Backup not found: %s\n" "${1:-}"
exit 1
fi
;;
restore)
if [[ -z "${1:-}" ]]
then
"$_ME" help backups
exit 1
elif [[ ! -e "${_hosts_dirname}/${1}" ]]
then
printf "Backup not found: %s\n" "${1:-}"
exit 1
fi
_verify_write_permissions "$@"
if [[ "${2:-}" != "--skip-backup" ]]
then
"${_ME}" backups create
fi
cat "${_hosts_dirname}/${1}" > "${HOSTS_PATH}" &&
printf "Restored from backup: %s\n" "${1}"
;;
show)
if [[ -z "${1:-}" ]]
then
"$_ME" help backups
exit 1
elif [[ ! -e "${_hosts_dirname}/${1}" ]]
then
printf "Backup not found: %s\n" "${1:-}"
exit 1
fi
less "${_hosts_dirname}/${1:-}"
;;
*)
local _filenames=()
set +f
for __filename in $(cd "${_hosts_dirname}" && ls -1 hosts* 2> /dev/null)
do
if [[ "${__filename:-}" != "hosts" ]] && \
[[ ! "${__filename:-}" =~ ^hosts_test..{6}$ ]]
then
_filenames+=("${__filename:-}")
fi
done
set -f
if ((${#_filenames[@]}))
then
for __filename in "${_filenames[@]:-}"
do
printf "%s\n" "${__filename}"
done
else
printf \
"No backups found. Create a new backup:\n %s backups create\n" \
"${_ME}"
fi
;;
esac
}
# ----------------------------------------------------------------------- block
desc "block" <<HEREDOC

233
test/backups.bats Executable file
View File

@ -0,0 +1,233 @@
#!/usr/bin/env bats
load test_helper
# `hosts backups` #############################################################
@test "\`backups\` with no backups and no arguments exits with status 0." {
run "${_HOSTS}" backups
printf "\${status}: %s\\n" "${status}"
printf "\${output}: '%s'\\n" "${output}"
[[ ${status} -eq 0 ]]
}
@test "\`backups\` with backups and no arguments exits with status 0." {
{
run "${_HOSTS}" backups create
}
run "${_HOSTS}" backups
printf "\${status}: %s\\n" "${status}"
printf "\${output}: '%s'\\n" "${output}"
[[ ${status} -eq 0 ]]
}
@test "\`backups\` with no backups and no arguments prints message." {
run "${_HOSTS}" backups
printf "\${status}: %s\\n" "${status}"
printf "\${output}: '%s'\\n" "${output}"
_expected="\
No backups found. Create a new backup:
hosts backups create"
[[ "${output}" == "${_expected}" ]]
}
@test "\`backups\` with backups and no arguments prints list of backups." {
{
run "${_HOSTS}" backups create
sleep 1
run "${_HOSTS}" backups create
}
run "${_HOSTS}" backups
printf "\${status}: %s\\n" "${status}"
printf "\${output}: '%s'\\n" "${output}"
[[ "${lines[0]}" =~ hosts_test ]]
[[ "${lines[0]}" =~ '--backup-' ]]
[[ "${lines[1]}" =~ hosts_test ]]
[[ "${lines[1]}" =~ '--backup-' ]]
}
# `hosts backups create` ######################################################
@test "\`backups create\` exits with status 0." {
run "${_HOSTS}" backups create
printf "\${status}: %s\\n" "${status}"
printf "\${output}: '%s'\\n" "${output}"
[[ ${status} -eq 0 ]]
}
@test "\`backups create\` creates backup." {
run "${_HOSTS}" backups create
printf "\${status}: %s\\n" "${status}"
printf "\${output}: '%s'\\n" "${output}"
_backup_path="$(echo "${output}" | sed -e 's/.*\(\/tmp.*\)/\1/')"
printf "\${_backup_path}: '%s'\\n" "${_backup_path}"
[[ -e "${_backup_path}" ]]
}
@test "\`backups create\` prints message." {
run "${_HOSTS}" backups create
printf "\${status}: %s\\n" "${status}"
printf "\${output}: '%s'\\n" "${output}"
[[ ${output} =~ 'Backed up to' ]]
}
# `hosts backups compare` #####################################################
@test "\`backups compare\` with valid backup exits with status 0 and prints." {
{
run "${_HOSTS}" backups create
_backup_path="$(echo "${output}" | sed -e 's/.*\(\/tmp.*\)/\1/')"
_backup_basename="$(basename "${_backup_path}")"
}
run "${_HOSTS}" backups show "${_backup_basename}"
printf "\${output}: '%s'\\n" "${output}"
printf "\${lines[6]}: '%s'\\n" "${lines[6]}"
[[ ${status} -eq 0 ]]
[[ "${lines[6]}" == '127.0.0.1 localhost' ]]
}
@test "\`backups compare\` with invalid backup exits with status 1" {
{
run "${_HOSTS}" backups create
_backup_path="$(echo "${output}" | sed -e 's/.*\(\/tmp.*\)/\1/')"
_backup_basename="$(basename "${_backup_path}")"
}
run "${_HOSTS}" backups compare "invalid-backup-name"
printf "\${status}: %s\\n" "${status}"
printf "\${output}: '%s'\\n" "${output}"
[[ ${status} -eq 1 ]]
[[ ${output} =~ 'Backup not found' ]]
}
# `hosts backups delete` ######################################################
@test "\`backups delete\` with valid backup exits with status 0 and deletes backup" {
{
run "${_HOSTS}" backups create
_backup_path="$(echo "${output}" | sed -e 's/.*\(\/tmp.*\)/\1/')"
_backup_basename="$(basename "${_backup_path}")"
}
run "${_HOSTS}" backups delete "${_backup_basename}"
printf "\${status}: %s\\n" "${status}"
printf "\${output}: '%s'\\n" "${output}"
[[ ${status} -eq 0 ]]
[[ ! -e "${_backup_path}" ]]
[[ ${output} =~ 'Backup deleted' ]]
}
@test "\`backups delete\` with invalid backup exits with status 1" {
{
run "${_HOSTS}" backups create
_backup_path="$(echo "${output}" | sed -e 's/.*\(\/tmp.*\)/\1/')"
_backup_basename="$(basename "${_backup_path}")"
}
run "${_HOSTS}" backups delete "invalid-backup-name"
printf "\${status}: %s\\n" "${status}"
printf "\${output}: '%s'\\n" "${output}"
[[ ${status} -eq 1 ]]
[[ -e "${_backup_path}" ]]
[[ ${output} =~ 'Backup not found' ]]
}
# `hosts backups restore` #####################################################
@test "\`backups restore\` with valid backup exits with status 0 and restores" {
{
run "${_HOSTS}" backups create
_backup_path="$(echo "${output}" | sed -e 's/.*\(\/tmp.*\)/\1/')"
_backup_basename="$(basename "${_backup_path}")"
run "${_HOSTS}" add 0.0.0.0 example.com
sleep 1
}
run "${_HOSTS}" backups restore "${_backup_basename}"
printf "\${status}: %s\\n" "${status}"
printf "\${output}: '%s'\\n" "${output}"
printf "\${lines[0]}: '%s'\\n" "${lines[0]}"
[[ ${status} -eq 0 ]]
[[ "${lines[0]}" =~ 'Backed up to' ]]
[[ "${lines[1]}" =~ 'Restored from backup' ]]
_new_backup_path="$(echo "${lines[0]}" | sed -e 's/.*\(\/tmp.*\)/\1/')"
_new_backup_content="$(cat "${_new_backup_path}")"
_old_backup_content="$(cat "${_backup_path}")"
_current_content="$(cat "${HOSTS_PATH}")"
[[ "${_new_backup_content}" != "${_current_content}" ]]
[[ "${_old_backup_content}" == "${_current_content}" ]]
}
@test "\`backups restore --skip-backup\` with valid backup exits with status 0 and restores" {
{
run "${_HOSTS}" backups create
_backup_path="$(echo "${output}" | sed -e 's/.*\(\/tmp.*\)/\1/')"
_backup_basename="$(basename "${_backup_path}")"
run "${_HOSTS}" add 0.0.0.0 example.com
_replaced_content="$(cat "${HOSTS_PATH}")"
sleep 1
}
run "${_HOSTS}" backups restore "${_backup_basename}" --skip-backup
printf "\${status}: %s\\n" "${status}"
printf "\${output}: '%s'\\n" "${output}"
printf "\${lines[0]}: '%s'\\n" "${lines[0]}"
[[ ${status} -eq 0 ]]
[[ "${lines[0]}" =~ 'Restored from backup' ]]
_old_backup_content="$(cat "${_backup_path}")"
_current_content="$(cat "${HOSTS_PATH}")"
[[ "${_replaced_content}" != "${_current_content}" ]]
[[ "${_old_backup_content}" == "${_current_content}" ]]
}
@test "\`backups restore\` with invalid backup exits with status 1" {
{
run "${_HOSTS}" backups create
_backup_path="$(echo "${output}" | sed -e 's/.*\(\/tmp.*\)/\1/')"
_backup_basename="$(basename "${_backup_path}")"
}
run "${_HOSTS}" backups restore "invalid-backup-name"
printf "\${status}: %s\\n" "${status}"
printf "\${output}: '%s'\\n" "${output}"
[[ ${status} -eq 1 ]]
[[ ${output} =~ 'Backup not found' ]]
}
# `hosts backups show` ########################################################
@test "\`backups show\` with valid backup exits with status 0 and prints." {
{
run "${_HOSTS}" backups create
_backup_path="$(echo "${output}" | sed -e 's/.*\(\/tmp.*\)/\1/')"
_backup_basename="$(basename "${_backup_path}")"
}
run "${_HOSTS}" backups show "${_backup_basename}"
printf "\${output}: '%s'\\n" "${output}"
printf "\${lines[6]}: '%s'\\n" "${lines[6]}"
[[ ${status} -eq 0 ]]
[[ "${lines[6]}" == '127.0.0.1 localhost' ]]
}
@test "\`backups show\` with invalid backup exits with status 1" {
{
run "${_HOSTS}" backups create
_backup_path="$(echo "${output}" | sed -e 's/.*\(\/tmp.*\)/\1/')"
_backup_basename="$(basename "${_backup_path}")"
}
run "${_HOSTS}" backups show "invalid-backup-name"
printf "\${status}: %s\\n" "${status}"
printf "\${output}: '%s'\\n" "${output}"
[[ ${status} -eq 1 ]]
[[ ${output} =~ 'Backup not found' ]]
}

View File

@ -12,8 +12,9 @@ setup() {
# The location of the `hosts` script being tested.
export _HOSTS="${BATS_TEST_DIRNAME}/../hosts"
export _HOSTS_TEMP_DIR="/tmp"
export _HOSTS_TEMP_PATH
_HOSTS_TEMP_PATH="$(mktemp /tmp/hosts_test.XXXXXX)" || exit 1
_HOSTS_TEMP_PATH="$(mktemp ${_HOSTS_TEMP_DIR}/hosts_test.XXXXXX)" || exit 1
cat "${BATS_TEST_DIRNAME}/fixtures/hosts" > "${_HOSTS_TEMP_PATH}"
export HOSTS_PATH="${_HOSTS_TEMP_PATH}"
@ -24,7 +25,7 @@ teardown() {
[[ -e "${_HOSTS_TEMP_PATH}" ]] &&
[[ "${_HOSTS_TEMP_PATH}" =~ ^/tmp/hosts_test ]]
then
rm "${_HOSTS_TEMP_PATH}"
rm -f "${_HOSTS_TEMP_DIR}"/hosts_test.*
fi
}