Skip to content

Instantly share code, notes, and snippets.

@fnichol
Last active September 26, 2018 18:28
Show Gist options
  • Save fnichol/2698730da0707ab49c9239dbd9e4957e to your computer and use it in GitHub Desktop.
Save fnichol/2698730da0707ab49c9239dbd9e4957e to your computer and use it in GitHub Desktop.
A script that ensures a pre-existing Habitat Studio is safely cleaned up and captures a report on failure
#!/usr/bin/env bash
#
# Copyright (c) 2018 Chef Software, Inc. and/or applicable contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
main() {
set -euo pipefail
if [ -n "${DEBUG:-}" ]; then set -x; fi
program="$(basename "$0")"
author="The Habitat Maintainers <[email protected]>"
declare -rg program author
# The exit code if after a successful unmount, the filesystem is still
# mounted
ERR_MOUNT_PERSISTS=81
# The exit code if an assumed empty directory or regular file could not be
# deleted
ERR_DELETE=83
declare -rg ERR_MOUNT_PERSISTS ERR_DELETE
need_cmd basename
need_cmd date
need_cmd find
need_cmd grep
need_cmd hostname
need_cmd ls
need_cmd mount
need_cmd ps
need_cmd pwd
need_cmd readlink
need_cmd rm
need_cmd rmdir
need_cmd sed
need_cmd umount
need_cmd uptime
parse_cli_args "$@"
cleanup_studio
}
print_help() {
echo "$program
Authors: $author
Ensures a pre-existing Habitat Studio is safely cleaned up and captures a
report on failure.
USAGE:
$program [FLAGS] [OPTIONS]
FLAGS:
-h Prints help information
OPTIONS:
-r <HAB_STUDIO_ROOT> Sets a Studio root [default: /hab/studios/<DIR_NAME>]
"
}
parse_cli_args() {
# Implementation for directory name taken from Studio source code
local dir_name
dir_name="$(pwd |
sed -e 's,^/$,root,' -e 's,^/,,' -e 's,/,--,g' -e 's, ,-,g')"
# Set default studio root
hab_studio_root="/hab/studios/$dir_name"
OPTIND=1
# Parse command line flags and options
while getopts ":hr:" opt; do
case $opt in
h)
print_help
exit 0
;;
r)
hab_studio_root="$OPTARG"
;;
\?)
print_help
exit_with "Invalid option: -$OPTARG" 1
;;
esac
done
# Shift off all parsed token in `$*` so that the subcommand is now `$1`.
shift "$((OPTIND - 1))"
# Properly canonicalize the root path of the Studio by following all
# symlinks.
if [[ -d "$hab_studio_root" ]]; then
hab_studio_root="$(readlink -f "$hab_studio_root")"
fi
declare -rg hab_studio_root
}
cleanup_studio() {
local report
report="$HOME/studio-teardown-forensics-$(date -u +%Y-%m-%d-%H:%M:%SZ).md"
if [[ ! -d "$hab_studio_root" ]]; then
banner "No Studio exists at: $hab_studio_root, skipping"
return 0
fi
banner "Removing Studio at $hab_studio_root"
banner "Unmounting any remaining filesystems"
if is_fs_mounted "$hab_studio_root/src"; then
capture_forensics "$report"
umount -v -l "$hab_studio_root/src"
fi
if is_fs_mounted "$hab_studio_root/hab/cache/artifacts"; then
capture_forensics "$report"
umount -v -l "$hab_studio_root/hab/cache/artifacts"
fi
if is_fs_mounted "$hab_studio_root/run"; then
capture_forensics "$report"
umount -v "$hab_studio_root/run"
fi
if is_fs_mounted "$hab_studio_root/sys"; then
capture_forensics "$report"
umount -v -l "$hab_studio_root/sys"
fi
if is_fs_mounted "$hab_studio_root/proc"; then
capture_forensics "$report"
umount -v "$hab_studio_root/proc"
fi
if is_fs_mounted "$hab_studio_root/dev/pts"; then
capture_forensics "$report"
umount -v "$hab_studio_root/dev/pts"
fi
if is_fs_mounted "$hab_studio_root/dev"; then
capture_forensics "$report"
umount -v -l "$hab_studio_root/dev"
fi
if is_fs_mounted "$hab_studio_root/var/run/docker.sock"; then
capture_forensics "$report"
umount -v -l "$hab_studio_root/var/run/docker.sock"
fi
banner "Verifying filesystems are removed and mount points are empty"
if is_fs_mounted "$hab_studio_root/src"; then
capture_forensics "$report"
exit_with "Mount of $hab_studio_root/src persists" "$ERR_MOUNT_PERSISTS"
fi
if is_not_empty "$hab_studio_root/src"; then
capture_forensics "$report"
exit_with "Directory $hab_studio_root/src is not empty" \
"$ERR_MOUNT_PERSISTS"
fi
if is_fs_mounted "$hab_studio_root/hab/cache/artifacts"; then
capture_forensics "$report"
exit_with "Mount of $hab_studio_root/hab/cache/artifacts persists" \
"$ERR_MOUNT_PERSISTS"
fi
if is_not_empty "$hab_studio_root/hab/cache/artifacts"; then
capture_forensics "$report"
exit_with "Directory $hab_studio_root/hab/cache/artifacts is not empty" \
"$ERR_MOUNT_PERSISTS"
fi
if is_fs_mounted "$hab_studio_root/run"; then
capture_forensics "$report"
exit_with "Mount of $hab_studio_root/run persists" "$ERR_MOUNT_PERSISTS"
fi
if is_not_empty "$hab_studio_root/run"; then
capture_forensics "$report"
exit_with "Directory $hab_studio_root/run is not empty" \
"$ERR_MOUNT_PERSISTS"
fi
if is_fs_mounted "$hab_studio_root/sys"; then
capture_forensics "$report"
exit_with "Mount of $hab_studio_root/sys persists" "$ERR_MOUNT_PERSISTS"
fi
if is_not_empty "$hab_studio_root/sys"; then
capture_forensics "$report"
exit_with "Directory $hab_studio_root/sys is not empty" \
"$ERR_MOUNT_PERSISTS"
fi
if is_fs_mounted "$hab_studio_root/proc"; then
capture_forensics "$report"
exit_with "Mount of $hab_studio_root/proc persists" "$ERR_MOUNT_PERSISTS"
fi
if is_not_empty "$hab_studio_root/proc"; then
capture_forensics "$report"
exit_with "Directory $hab_studio_root/proc is not empty" \
"$ERR_MOUNT_PERSISTS"
fi
if is_fs_mounted "$hab_studio_root/dev/pts"; then
capture_forensics "$report"
exit_with "Mount of $hab_studio_root/dev/pts persists" \
"$ERR_MOUNT_PERSISTS"
fi
# An unmounted `dev/pts` will restore the underlying `dev` bind-mounted fs
if is_fs_mounted "$hab_studio_root/dev"; then
capture_forensics "$report"
exit_with "Mount of $hab_studio_root/dev persists" "$ERR_MOUNT_PERSISTS"
fi
if is_not_empty "$hab_studio_root/dev"; then
capture_forensics "$report"
exit_with "Directory $hab_studio_root/dev is not empty" \
"$ERR_MOUNT_PERSISTS"
fi
if is_fs_mounted "$hab_studio_root/var/run/docker.sock"; then
capture_forensics "$report"
exit_with "Mount of $hab_studio_root/var/run/docker.sock persists" \
"$ERR_MOUNT_PERSISTS"
fi
if is_not_empty "$hab_studio_root/var/run/docker.sock"; then
capture_forensics "$report"
exit_with "Directory $hab_studio_root/var/run/docker.sock is not empty" \
"$ERR_MOUNT_PERSISTS"
fi
banner "Removing mount points"
if [[ -d "$hab_studio_root/src" ]]; then
if ! rmdir -v "$hab_studio_root/src"; then
capture_forensics "$report"
exit_with "Error removing $hab_studio_root/src" "$ERR_DELETE"
fi
fi
if [[ -d "$hab_studio_root/hab/cache/artifacts" ]]; then
if ! rmdir -v "$hab_studio_root/hab/cache/artifacts"; then
capture_forensics "$report"
exit_with "Error removing $hab_studio_root/hab/cache/artifacts" \
"$ERR_DELETE"
fi
fi
if [[ -d "$hab_studio_root/run" ]]; then
if ! rmdir -v "$hab_studio_root/run"; then
capture_forensics "$report"
exit_with "Error removing $hab_studio_root/run" "$ERR_DELETE"
fi
fi
if [[ -d "$hab_studio_root/sys" ]]; then
if ! rmdir -v "$hab_studio_root/sys"; then
capture_forensics "$report"
exit_with "Error removing $hab_studio_root/sys" "$ERR_DELETE"
fi
fi
if [[ -d "$hab_studio_root/proc" ]]; then
if ! rmdir -v "$hab_studio_root/proc"; then
capture_forensics "$report"
exit_with "Error removing $hab_studio_root/proc" "$ERR_DELETE"
fi
fi
# There should be no `dev/pts` to check or delete
if [[ -d "$hab_studio_root/dev" ]]; then
if ! rmdir -v "$hab_studio_root/dev"; then
capture_forensics "$report"
exit_with "Error removing $hab_studio_root/dev" "$ERR_DELETE"
fi
fi
if [[ -f "$hab_studio_root/var/run/docker.sock" ]]; then
if ! rm -v "$hab_studio_root/var/run/docker.sock"; then
capture_forensics "$report"
exit_with "Error removing $hab_studio_root/var/run/docker.sock" \
"$ERR_DELETE"
fi
fi
banner "Studio $hab_studio_root is properly cleaned up."
}
is_fs_mounted() {
if mount | grep -q "on $1 type"; then
warn "Filesystem '$1' IS mounted"
return 0
else
info "Filesystem '$1' is not currently mounted"
return 1
fi
}
is_not_empty() {
if [[ ! -d "$1" ]]; then
info "Directory '$1' does not exist"
return 1
elif find "$1" -mindepth 1 -print -quit 2>/dev/null | grep -q .; then
warn "Directory '$1' is NOT empty"
return 0
else
info "Directory '$1' is empty"
return 1
fi
}
need_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
exit_with "Required command '$1' not found on PATH" 127
fi
}
banner() {
echo "--> ${1:-}"
}
info() {
echo " ${1:-}"
}
warn() {
echo "xxx ${1:-}" >&2
}
exit_with() {
warn "$1"
exit "${2:-10}"
}
capture_forensics() {
local report="$1"
# If forensics has already been captured, early return
if [[ -f "$report" ]]; then
return 0
fi
banner "Capturing forensics report"
echo "\
# $(basename "$report")
## Host Information
### \`hostname\`:
\`\`\`
$(hostname)
\`\`\`
### \`uptime\`:
\`\`\`
$(uptime)
\`\`\`
## Studio Recursive Directory Listing
### \`ls -laR $hab_studio_root\`:
\`\`\`
$(ls -laR "$hab_studio_root")
\`\`\`
## Process Listing
### \`ps -ef\`:
\`\`\`
$(ps -ef)
\`\`\`
## System Mount Table State
### \`mount\`:
\`\`\`
$(mount)
\`\`\`
" >"$report"
if command -v hab >/dev/null 2>&1; then
if hab pkg path core/hab-studio >/dev/null 2>&1; then
# If a Busybox binary is found in a Studio package, capture its `mount`
# state
if [[ -x "$(hab pkg path core/hab-studio)/libexec/busybox" ]]; then
echo "
## Busybox Mount Table State
### \`$(hab pkg path core/hab-studio)/libexec/busybox mount\`:
\`\`\`
$("$(hab pkg path core/hab-studio)/libexec/busybox" mount)
\`\`\`
" >>"$report"
fi
fi
# If core/hab is installed, capture all installed releases
if hab pkg path core/hab >/dev/null 2>&1; then
echo "
## Installed Releases of core/hab
### \`ls -1d /hab/pkgs/core/hab/*/*\`:
\`\`\`
$(ls -1d /hab/pkgs/core/hab/*/*)
\`\`\`
" >>"$report"
fi
# If core/hab-studio is installed, capture all installed releases
if hab pkg path core/hab-studio >/dev/null 2>&1; then
echo "
## Installed Releases of core/hab-studio
### \`ls -1d /hab/pkgs/core/hab-studio/*/*\`:
\`\`\`
$(ls -1d /hab/pkgs/core/hab-studio/*/*)
\`\`\`
" >>"$report"
fi
fi
# If a Docker engine is running, capture its state
if command -v docker >/dev/null 2>&1; then
if docker info >/dev/null 2>&1; then
echo "
## Docker Status
### \`docker info\`:
\`\`\`
$(docker info)
\`\`\`
### \`docker ps -a\`:
\`\`\`
$(docker ps -a)
\`\`\`
" >>"$report"
fi
fi
info "Report captured and written to $report"
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment