Skip to content

Instantly share code, notes, and snippets.

@ericboehs
Created April 15, 2025 22:18
Show Gist options
  • Save ericboehs/d6c2857d82d9e0f8d8832f3baece8c8e to your computer and use it in GitHub Desktop.
Save ericboehs/d6c2857d82d9e0f8d8832f3baece8c8e to your computer and use it in GitHub Desktop.
πŸ” ssm-param-history – View Recently Updated AWS SSM Parameters
#!/usr/bin/env zsh
###############################################################################
# ssm-param-history
#
# Description:
# Inspect recently updated AWS SSM parameters under a given base path. Uses a
# local twenty-minute cache to avoid redundant API calls. Parameters can be
# selected interactively via fzf or printed directly in a clean, sorted list.
# (Using --no-fzf won't fetch additional metadata, just the parameter names.)
#
# Features:
# - Queries AWS SSM via `get-parameters-by-path` and caches results for 20 minutes
# - Automatically refreshes if cache is missing, empty, or expired
# - Displays parameters updated within the last N days (--days, default: 7)
# - Supports fuzzy selection with fzf (or use --no-fzf for plain output)
# - Displays metadata (type, version, last modified date & user)
# - Always shows `String` values; optionally shows decrypted `SecureString` values (--decrypt)
# - Does NOT include deleted parameters (only active ones are returned) as
# these are only in CloudTrail
#
# Usage:
# ./ssm-param-history [options] <ssm-path>
#
# Options:
# --no-fzf Disable fzf picker (just prints recent params)
# --no-cache Skip cache (force fresh query)
# --decrypt Show decrypted SecureString values
# --days=N Show values modified in the last N days (default: 7)
# --help Show this help message
#
# Example:
# ./ssm-param-history --decrypt /dsva-vagov/vets-api/prod/
#
#
# Dependencies:
# - aws cli
# - jq
# - fzf (optional)
# - gdate
#
# On macOS: `brew install awscli session-manager-plugin jq fzf coreutils`
#
###############################################################################
set -eo pipefail # Don't treat unset vars as fatal to allow --help fallback
cleanup_spinner() {
tput cnorm
[[ -n "$fetch_pid" ]] && kill "$fetch_pid" 2>/dev/null || true
}
trap cleanup_spinner INT TERM EXIT
spinner() {
local delay=0.1
local spin_chars=("β ‹" "β ™" "β Ή" "β Έ" "β Ό" "β ΄" "β ¦" "β §" "β ‡" "⠏")
local total=${#spin_chars[@]}
local i=0
tput civis
while ps -p $fetch_pid > /dev/null 2>&1; do
local char="${spin_chars[i+1]}"
printf "\r\033[2K%-2s Fetching cache for %s..." "$char" "$base_path"
i=$(( (i + 1) % total ))
sleep $delay
done
wait $fetch_pid 2>/dev/null
local exit_code=$?
tput cnorm
if [[ $exit_code -eq 0 ]]; then
printf "\r\033[2Kβœ… Cache refreshed for %s\n" "$base_path"
else
printf "\r\033[2K❌ Cache failed for %s (exit code $exit_code)\n" "$base_path"
fi
return $exit_code
}
CACHE_DIR="${HOME}/.cache/ssm-path-picker"
mkdir -p "$CACHE_DIR"
# --- Default flag values ---
use_fzf=true
use_cache=true
decrypt=false
days=7
# --- Usage/help ---
if [[ "$1" == "--help" ]]; then
echo "Usage: $(basename $0) [options] <ssm-path>"
echo ""
echo "Options:"
echo " --no-fzf Disable fzf picker (prints list instead)"
echo " --no-cache Skip cache (force fresh fetch)"
echo " --decrypt Show decrypted parameter values"
echo " --days=N Look back N days (default: 7)"
echo " --help Show this help message"
exit 0
fi
# --- Parse flags and path ---
path_arg=""
for arg in "$@"; do
case $arg in
--no-fzf) use_fzf=false ;;
--no-cache) use_cache=false ;;
--decrypt) decrypt=true ;;
--days=*) days=${arg#*=} ;;
--*) echo "Unknown option: $arg"; exit 1 ;;
*)
if [[ -z "$path_arg" ]]; then
path_arg="$arg"
fi
;;
esac
done
if [[ -z "$path_arg" ]]; then
echo "❌ No SSM path provided."
"$0" --help
exit 1
fi
if command -v gdate >/dev/null 2>&1; then
date_cmd="gdate"
elif date --version 2>&1 | grep -q "GNU coreutils"; then
date_cmd="date"
else
echo "❌ Neither 'gdate' nor GNU 'date' found. Please install coreutils."
exit 1
fi
base_path="$path_arg"
# Create a safe cache key (hash or path-safe slug)
cache_key=$(echo "${base_path}--days=${days}" | base64 | tr -d '=' | tr '/+=' '_-~')
cache_file="$CACHE_DIR/$cache_key.json"
# Only refresh cache if older than 20 minutes
if $use_cache; then
if [[ ! -s "$cache_file" || $(($($date_cmd +%s) - $($date_cmd -r "$cache_file" +%s))) -gt 1200 ]]; then
aws ssm get-parameters-by-path \
--path "$base_path" \
--recursive \
--with-decryption \
--output json > "$cache_file" &
fetch_pid=$!
spinner || exit $?
else
echo "βœ… Using cached results for $base_path"
fi
else
echo "🚫 Skipping cache; fetching fresh results"
aws ssm get-parameters-by-path \
--path "$base_path" \
--recursive \
--with-decryption \
--output json > "$cache_file"
fi
cutoff_epoch=$($date_cmd -u -d "$days days ago" +%s)
# Get recent parameters
param_list=$(jq -r --argjson cutoff "$cutoff_epoch" '
.Parameters[]
| select(.Name and .LastModifiedDate)
| .LastModifiedDate as $iso
| .Name as $name
| ($iso
| sub("\\..*"; "")
| sub("([-+]\\d+:\\d+)?$"; "Z")
| fromdateiso8601
) as $date
| select($date >= $cutoff)
| [$date | strftime("%Y-%m-%d %H:%M"), $name]
| @tsv
' < "$cache_file" | sort -r)
if $use_fzf; then
selection=$(echo "$param_list" | fzf --multi --ansi --prompt="Select SSM parameters: " --header=$'LAST MODIFIED\tNAME')
if [[ -z "$selection" ]]; then
echo "No selection made."
exit 0
fi
param_names=(${(@f)$(echo "$selection" | cut -f2-)})
else
echo "$param_list"
exit 0
fi
# Show details for selected params
for name in "${param_names[@]}"; do
echo -e "\n\033[1m=== $name ===\033[0m"
meta_json=$(aws ssm describe-parameters \
--parameter-filters Key=Name,Option=Equals,Values="$name" \
--output json 2>/dev/null)
if [[ -z "$meta_json" || "$meta_json" == "{}" || "$meta_json" == *'"Parameters": []'* ]]; then
echo "Warning: Unable to retrieve metadata for $name"
continue
fi
meta=$(echo "$meta_json" | jq '.Parameters[0] | {Name, Type, Version, LastModifiedDate, LastModifiedUser}')
param_type=$(echo "$meta" | jq -r '.Type')
if [[ "$param_type" == "SecureString" && "$decrypt" == true ]]; then
value=$(aws ssm get-parameter --name "$name" --with-decryption --output json | jq -r '.Parameter.Value')
echo "$meta" | jq --arg val "$value" '. + {Value: $val}'
elif [[ "$param_type" == "String" ]]; then
value=$(aws ssm get-parameter --name "$name" --output json | jq -r '.Parameter.Value')
echo "$meta" | jq --arg val "$value" '. + {Value: $val}'
else
echo "$meta" | jq
fi
done
@ericboehs
Copy link
Author

ericboehs commented Apr 15, 2025

πŸ” ssm-param-history

A Zsh script for viewing recently updated AWS SSM parameters under a given path.
Supports interactive filtering with fzf, local caching, and optional decryption of SecureString values.


✨ Features

  • πŸ“ Caches results for 20 minutes to reduce redundant API calls
  • πŸ“† Filters parameters by last modified date (e.g. past 7 days)
  • πŸ” Fuzzy search with fzf (or plain output with --no-fzf)
  • πŸ” Decrypt SecureString values with --decrypt
  • 🧠 Displays parameter metadata (type, version, modified date & user)
  • ❌ Skips deleted parameters (only shows active ones from SSM)
  • πŸ’» macOS + Linux compatible (gdate fallback handled)

πŸ“¦ Installation

Install globally (e.g. for all users):

# Install ssm-param-history from GitHub Gist
curl -sL https://gist.github.com/ericboehs/d6c2857d82d9e0f8d8832f3baece8c8e/raw/ssm-param-history > /usr/local/bin/ssm-param-history
chmod +x /usr/local/bin/ssm-param-history

Or install to your local bin:

mkdir -p ~/.local/bin
curl -sL https://gist.github.com/ericboehs/d6c2857d82d9e0f8d8832f3baece8c8e/raw/ssm-param-history > ~/.local/bin/ssm-param-history
chmod +x ~/.local/bin/ssm-param-history

And add this to your ~/.zshrc (if not already set):

export PATH="$HOME/.local/bin:$PATH"

πŸ”§ Dependencies

  • aws CLI
  • jq
  • fzf (optional, only for interactive mode)
  • gdate (part of GNU coreutils)

πŸ’» macOS users:
Install all required tools via Homebrew:

brew install awscli session-manager-plugin jq fzf coreutils

πŸ§ͺ Example Usage

# Interactive picker for recent sandbox parameters, with decryption
ssm-param-history --decrypt /dsva-vagov/vets-api/sandbox/

# Non-interactive, print recent changes from staging
ssm-param-history --no-fzf /dsva-vagov/vets-api/staging

# Skip cache and look back 30 days
ssm-param-history --no-cache --days=30 /my-app/env/dev

🧠 Notes

  • Cache is stored in ~/.cache/ssm-path-picker/ and keyed by path and --days
  • Deleted parameters are not returned β€” CloudTrail is needed for those
  • The spinner is fancy. Don’t blink.

πŸ“œ License

MIT

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment