Last active
September 26, 2018 18:28
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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