Skip to content

Instantly share code, notes, and snippets.

@smoser
Last active March 27, 2017 19:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save smoser/dd45b6324be937b67b80bac7aa1a6c47 to your computer and use it in GitHub Desktop.
Save smoser/dd45b6324be937b67b80bac7aa1a6c47 to your computer and use it in GitHub Desktop.
cloud-init-debug: grab debug information for cloud-init

cloud-init-debug

cloud-init-debug collect debug information for cloud-init.

This program will collect information to aid in cloud-init debugging.

To run:

$ wget https://gist.githubusercontent.com/smoser/dd45b6324be937b67b80bac7aa1a6c47/raw/cloud-init-debug 
$ chmod 755 cloud-init-debug

# if this is just a bare instance without any important or sensitive
# information, please run:
$ sudo ./cloud-init-debug --all

## if this contains sensitive information such as passwords, run:
$ sudo ./cloud-init-debug

That will create a file 'debug.tar.gz' that can be attached to a bug.

#!/bin/bash
VERBOSITY=0
TEMP_D=""
DEFAULT_OUT="debug.tar.gz"
error() { echo "$@" 1>&2; }
fail() { local r=$?; [ $r -eq 0 ] && r=1; failrc "$r" "$@"; }
failrc() { local r=$1; shift; [ $# -eq 0 ] || error "$@"; exit $r; }
Usage() {
cat <<EOF
Usage: ${0##*/} [ options ] [output]
collect cloud-init debug information from a system.
output defaults: $DEFAULT_OUT
options:
-h | --help show this ssage
--all collect all information, including possibly sensitive
info in /var/lib/cloud/
EOF
}
sensitive_message() {
cat <<EOF
****************************** Warning **********************************
Passing the --all flag means that the contents of /var/lib/cloud/ have
been collected. If you launched this instance with sensitive information
such as passwords or api keys, then do not post it publicly.
EOF
}
bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; return 1; }
cleanup() {
[ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
}
debug() {
local level=${1}; shift;
[ "${level}" -gt "${VERBOSITY}" ] && return
error "${@}"
}
missing() {
debug 1 "$@"
echo "$@" >> "${TEMP_D}/missing.txt"
}
tar_dir() {
local dir="$1" out="$2"
if [ ! -d "$dir" ]; then
missing "$out: $dir: not a directory"
return
fi
debug 1 "collecting $dir to $out"
local updir="" bdir=""
updir=$(cd "$dir/.." && pwd) ||
{ error "failed cd $dir/.."; return 1; }
bdir=$(basename "$dir")
tar -C "$updir" -cf "${TEMP_D}/$out" "$bdir/" ||
{ error "failed to collect $dir"; return 1; }
}
collect_sys() {
local root="$1" dir="$2" ofile="$3"
debug 1 "collecting sys dir $dir"
if [ "$root" != "/" ]; then
missing "$ofile: root not /"
return
fi
if [ ! -d "$dir" ]; then
missing "$ofile: $dir not a dir."
fi
( cd "$dir" && grep -r . * ) 2>/dev/null > "${TEMP_D}/$ofile"
return 0
}
collect_files() {
local root="$1" file="" ofile="" fofile=""
shift
for file in "$@"; do
ofile="${file#${root}}"
fofile="${TEMP_D}/files/${ofile}"
debug 1 "collecting /$ofile"
if [ ! -f "$file" ]; then
missing "$ofile: not a file."
fi
[ -d "${fofile%/*}" ] || mkdir -p "${fofile%/*}" || return
cp -a "$file" "$fofile" || return
done
}
main() {
local short_opts="hv"
local long_opts="all,help,root:,verbose"
local getopt_out=""
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
{ bad_Usage; return; }
local cur="" next="" all=false output="${DEFAULT_OUT}" root="/"
while [ $# -ne 0 ]; do
cur="$1"; next="$2";
case "$cur" in
--all) all=true;;
-h|--help) Usage ; exit 0;;
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
-r|--root) root="$1"; shift;;
--) shift; break;;
esac
shift;
done
[ $# -eq 0 -o $# -eq 1 ] ||
{ bad_Usage "expected 0 or 1 arguments, got $#: $*"; return; }
if [ $# -eq 1 ]; then
output="$1"
fi
if [ ! -d "$root" ]; then
fail "target '$target' not a directory"
fi
root=$(cd "$root" && pwd) || fail "failed to get full path to root";
[ "$root" = "/" ] || root="$root/"
if [ "$(id -u)" != "0" ]; then
fail "Sorry, this program must be run as root: sudo $0 $*"
fi
TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") ||
fail "failed to make tempdir"
trap cleanup EXIT
if $all; then
tar_dir "${root}var/lib/cloud" "var-lib-cloud.tar" || return
fi
tar_dir "${root}etc/cloud" "etc-cloud.tar" || return
collect_sys "$root" "/sys/class/dmi/id" sys-class-dmi-id || return
collect_sys "$root" "/sys/hypervisor" sys-hypervisor || return
ofile="run-cloud-init.tar"
if [ "$root" = "/" ]; then
tar_dir "${root}run/cloud-init" "$ofile" || return
else
missing "$ofile: root not /"
fi
if [ "$root" = "/" -a -e /run/systemd ]; then
debug 1 "collecting systemd logs"
ofile="${TEMP_D}/journalctl-short-monotonic"
journalctl -o short-monotonic > "$ofile" 2>&1
echo "$?" >> "$ofile"
ofile="${TEMP_D}/journalctl-short-precise"
journalctl -o short-precise > "$ofile" 2>&1
echo "$?" >> "$ofile"
ofile="${TEMP_D}/systemctl-all"
systemctl --all --no-pager > "$ofile" 2>&1
echo "$?" >> "$ofile"
elif [ "$root" != "/" ]; then
missing "journalctl: root not /"
else
missing "journalctl: not systemd."
fi
collect_files "$root" "${root}etc/fstab" "${root}var/log/cloud-init"*
if [ "$root" = "/" ]; then
local version=""
{
echo "rpm: $(rpm -q cloud-init 2>/dev/null)"
echo "dpkg: $(dpkg-query --show cloud-init 2>/dev/null)"
echo "cloud-init: $(cloud-init --version 2>/dev/null)"
} > "${TEMP_D}/version"
else
missing "version: root not /"
fi
if [ "$root" = "/" ]; then
dmesg > "${TEMP_D}/dmesg"
else
missing "dmesg: root not /"
fi
uname -a > "${TEMP_D}/uname-a"
local out=""
if out=$(command -v systemd-detect-virt >/dev/null 2>&1); then
systemd-detect-virt > "${TEMP_D}/systemd-detect-virt"
else
missing "systemd-detect-virt: command not available."
fi
uname -a > "${TEMP_D}/uname-a"
if $all; then
sensitive_message 1>&2
fi
tar -C "${TEMP_D}" -czf "$output" . ||
fail "failed to write to $output"
error "wrote to $output"
}
main "$@"
# vi: ts=4 expandtab
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment