Created
April 15, 2025 22:18
-
-
Save ericboehs/d6c2857d82d9e0f8d8832f3baece8c8e to your computer and use it in GitHub Desktop.
π ssm-param-history β View Recently Updated AWS SSM Parameters
This file contains hidden or 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 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
π 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 ofSecureString
values.β¨ Features
fzf
(or plain output with--no-fzf
)SecureString
values with--decrypt
gdate
fallback handled)π¦ Installation
Install globally (e.g. for all users):
Or install to your local bin:
And add this to your
~/.zshrc
(if not already set):π§ Dependencies
aws
CLIjq
fzf
(optional, only for interactive mode)gdate
(part of GNU coreutils)π§ͺ Example Usage
π§ Notes
~/.cache/ssm-path-picker/
and keyed by path and--days
π License
MIT