Skip to content

Instantly share code, notes, and snippets.

@l2D
Last active June 19, 2025 15:31
Show Gist options
  • Select an option

  • Save l2D/cac5cc0ecd1cd322c133048929c2d1eb to your computer and use it in GitHub Desktop.

Select an option

Save l2D/cac5cc0ecd1cd322c133048929c2d1eb to your computer and use it in GitHub Desktop.
Bash script to get AWS EC2 Metadata. This script is use for get all or specific keys of AWS EC2 metadata with CLI options: `-h` - for Help menu, `-l` for list of available categories
#!/bin/bash
# --------------------------------- Constants -------------------------------- #
readonly BASE_URL="http://169.254.169.254"
readonly METADATA_PATH="/latest/meta-data"
readonly TOKEN_PATH="/latest/api/token"
readonly TOKEN_TTL=60 # In seconds
readonly HEADER_TOKEN_TTL="X-aws-ec2-metadata-token-ttl-seconds"
readonly HEADER_TOKEN="X-aws-ec2-metadata-token"
# -------------------------- Show usage information -------------------------- #
usage() {
echo "Usage: $0 [-k key1,key2,...] [-a] [-p] [-l] [-c category]"
echo "Options:"
echo " -k KEYS Comma-separated list of metadata keys to retrieve"
echo " -a Get all available metadata keys (overrides -k)"
echo " -p Pretty print JSON output"
echo " -l List available top-level metadata categories"
echo " -c CAT Show description for a specific metadata category"
echo " -h Display this help message"
echo ""
echo "Examples:"
echo " $0 # Get default keys (instance-id, ami-id, instance-type, local-ipv4)"
echo " $0 -k mac,instance-id,system # Get specific keys"
echo " $0 -a # Get all available metadata"
echo " $0 -p -k instance-type # Pretty print specific key"
echo " $0 -l # List available metadata categories"
echo " $0 -c placement # Show info about placement category"
exit 1
}
# --------------- Display metadata categories with descriptions -------------- #
show_categories() {
cat <<EOL
Available EC2 Instance Metadata Categories:
ami-id The AMI ID used to launch the instance
ami-launch-index Launch index for multiple instances launched together
ami-manifest-path Path to the AMI manifest file
ancestor-ami-ids AMI IDs of instances rebundled to create this AMI
autoscaling/target-lifecycle-state Target Auto Scaling lifecycle state
block-device-mapping/* Block device mapping information
events/maintenance/history Completed or canceled maintenance events
events/maintenance/scheduled Active maintenance events
events/recommendations/rebalance EC2 instance rebalance recommendations
hostname Private IPv4 DNS hostname or Resource-based name
iam/info IAM role information if associated with instance
iam/security-credentials/* IAM security credentials for the associated role
identity-credentials/ec2/* Credentials for the instance identity role
instance-action Action pending (reboot/shutdown) for the instance
instance-id The ID of this instance
instance-life-cycle The purchasing option (on-demand, spot, etc.)
instance-type The type of instance
ipv6 The IPv6 address of the instance
kernel-id The ID of the kernel launched with the instance
local-hostname Private IPv4 DNS hostname or Resource-based name
local-ipv4 Private IPv4 address
mac Media access control (MAC) address
network/interfaces/macs/* Network interface information
placement/availability-zone The Availability Zone of the instance
placement/availability-zone-id Static Availability Zone ID
placement/group-name The name of the placement group
placement/host-id The ID of the host (for Dedicated Hosts)
placement/partition-number The partition number
placement/region The AWS Region of the instance
product-codes AWS Marketplace product codes
public-hostname Public DNS hostname (IPv4)
public-ipv4 Public IPv4 address
public-keys/* Public keys associated with the instance
ramdisk-id RAM disk ID specified at launch
reservation-id The ID of the reservation
security-groups Security groups applied to the instance
services/domain Domain for AWS resources in the Region
services/partition AWS partition (aws, aws-cn, aws-us-gov)
spot/instance-action Pending action for Spot Instances
spot/termination-time Approximate termination time for Spot Instances
tags/instance Instance tags (if access is enabled)
Use -c CATEGORY for more detailed information about a specific category.
EOL
}
# --------------- Display information about a specific category -------------- #
show_category_info() {
local category=$1
case $category in
ami-id)
echo "Category: ami-id"
echo "Description: The AMI ID used to launch the instance."
echo "Version: 1.0"
echo "Example usage: $0 -k ami-id"
;;
instance-id)
echo "Category: instance-id"
echo "Description: The ID of this instance."
echo "Version: 1.0"
echo "Example usage: $0 -k instance-id"
;;
instance-type)
echo "Category: instance-type"
echo "Description: The type of instance. For example, t2.micro, m5.large."
echo "Version: 2007-08-29"
echo "Example usage: $0 -k instance-type"
;;
placement*)
echo "Category: placement"
echo "Description: Placement information for the instance."
echo "Sub-categories:"
echo " - placement/availability-zone: The Availability Zone of the instance"
echo " - placement/availability-zone-id: Static Availability Zone ID"
echo " - placement/group-name: The name of the placement group"
echo " - placement/host-id: The ID of the host (for Dedicated Hosts)"
echo " - placement/partition-number: The partition number"
echo " - placement/region: The AWS Region of the instance"
echo "Example usage: $0 -k placement/availability-zone"
;;
network*)
echo "Category: network/interfaces/macs"
echo "Description: Network interface information."
echo "Note: First get the MAC address, then access specific information."
echo "Example workflow:"
echo " 1. Get MAC address: $0 -k mac"
echo " 2. Use MAC to get interface details: $0 -k network/interfaces/macs/YOUR_MAC/public-ipv4s"
echo ""
echo "Available sub-categories (replace YOUR_MAC with actual MAC):"
echo " - network/interfaces/macs/YOUR_MAC/device-number: The device number"
echo " - network/interfaces/macs/YOUR_MAC/interface-id: The ID of the network interface"
echo " - network/interfaces/macs/YOUR_MAC/local-ipv4s: Private IPv4 addresses"
echo " - network/interfaces/macs/YOUR_MAC/public-ipv4s: Public IPv4 addresses"
echo " - network/interfaces/macs/YOUR_MAC/security-groups: Security groups"
echo " - network/interfaces/macs/YOUR_MAC/subnet-id: The ID of the subnet"
echo " - network/interfaces/macs/YOUR_MAC/vpc-id: The ID of the VPC"
;;
*)
echo "No detailed information available for category '$category'."
echo "Use -l to see a list of available categories."
;;
esac
}
# Check if jq is installed
check_jq() {
if ! command -v jq &>/dev/null; then
echo "Error: jq is not installed but is required for this script." >&2
echo "To install jq:" >&2
echo " - Amazon Linux/RHEL: sudo yum install -y jq" >&2
echo " - Ubuntu/Debian: sudo apt-get install -y jq" >&2
echo " - macOS: brew install jq" >&2
exit 1
fi
return 0
}
# -------------------- Default keys if none are specified -------------------- #
DEFAULT_KEYS=("instance-id" "ami-id" "instance-type" "local-ipv4")
KEYS=()
GET_ALL=false
PRETTY_PRINT=false
LIST_CATEGORIES=false
CATEGORY_INFO=""
# ------------------------ Parse command line options ------------------------ #
while getopts ":k:aplc:h" opt; do
case $opt in
k)
# Split comma-separated keys into array
IFS=',' read -ra KEYS <<<"$OPTARG"
;;
a)
GET_ALL=true
;;
p)
PRETTY_PRINT=true
;;
l)
LIST_CATEGORIES=true
;;
c)
CATEGORY_INFO="$OPTARG"
;;
h)
usage
;;
\?)
echo "Invalid option: -$OPTARG" >&2
usage
;;
:)
echo "Option -$OPTARG requires an argument." >&2
usage
;;
esac
done
# Show metadata categories for `-l` option
if [ "$LIST_CATEGORIES" = true ]; then
show_categories
exit 0
fi
# Show category info for `-c <CAT>` option
if [ -n "$CATEGORY_INFO" ]; then
show_category_info "$CATEGORY_INFO"
exit 0
fi
check_jq
# If no keys specified, use defaults
if [ ${#KEYS[@]} -eq 0 ] && [ "$GET_ALL" = false ]; then
KEYS=("${DEFAULT_KEYS[@]}")
fi
make_http_request(){
local url=$BASE_URL
local path=$1
local token=$2
if [ -n "$token" ]; then
curl -s -f -H "$HEADER_TOKEN: $token" "$url$path" 2>/dev/null || echo "N/A"
else
curl -s -f "$url$path" 2>/dev/null || echo "N/A"
fi
}
# Get IMDSv2 token (with fallback to IMDSv1)
TOKEN=$(curl -s -f -X PUT "$BASE_URL$TOKEN_PATH" -H "$HEADER_TOKEN_TTL: $TOKEN_TTL" 2>/dev/null) || TOKEN=""
# Function to get metadata for a specific key
get_metadata() {
local key=$1
local value
if [ -n "$TOKEN" ]; then
# IMDSv2 with token
value=$(make_http_request "$METADATA_PATH/$key" "$TOKEN")
else
# Fallback to IMDSv1
value=$(make_http_request "$METADATA_PATH/$key" "")
fi
# Return the value
echo "$value"
}
# Function to get all available metadata keys
get_all_metadata_keys() {
if [ -n "$TOKEN" ]; then
# IMDSv2 with token
make_http_request "$METADATA_PATH/" "$TOKEN"
else
# Fallback to IMDSv1
make_http_request "$METADATA_PATH/" ""
fi
}
# Get all available keys if -a option is specified
if [ "$GET_ALL" = true ]; then
ALL_KEYS=$(get_all_metadata_keys)
if [ -z "$ALL_KEYS" ]; then
echo "Error: Failed to retrieve metadata keys. Are you running on an EC2 instance?" >&2
exit 1
fi
IFS=$'\n' read -rd '' -a KEYS <<<"$ALL_KEYS"
fi
# Recursive handle metadata with nested values
process_metadata() {
local key=$1
local result
# Get the metadata value
result=$(get_metadata "$key")
# Check if the result is a list of subkeys (containing newlines)
if [[ "$result" == *$'\n'* ]] || [[ "$key" == */ ]]; then
# Create a nested JSON object
local nested_json="{}"
# Process each subkey
while IFS= read -r subkey; do
# Skip empty lines
[ -z "$subkey" ] && continue
# If the subkey ends with /, it's a directory, recurse
if [[ "$subkey" == */ ]]; then
local full_key="${key}${subkey}"
# Recursively process this nested directory
local subvalue=$(process_metadata "$full_key")
nested_json=$(echo "$nested_json" | jq --arg key "$subkey" --argjson val "$subvalue" '. + {($key): $val}')
else
# For direct subkeys, get their values
local full_key="${key}${subkey}"
local subvalue=$(get_metadata "$full_key")
nested_json=$(echo "$nested_json" | jq --arg key "$subkey" --arg val "$subvalue" '. + {($key): $val}')
fi
done <<< "$result"
echo "$nested_json"
else
# Return the result as a simple value (properly quoted for jq)
jq -n --arg value "$result" '$value'
fi
}
# Empty JSON object for storing results
JSON_DATA="{}"
# Use jq to build the JSON object safely with proper escaping
for KEY in "${KEYS[@]}"; do
# Check if key ends with / (indicating nested data)
if [[ "$KEY" == */ ]] || [[ "$(get_metadata "$KEY")" == *$'\n'* ]]; then
# Process the nested metadata
VALUE=$(process_metadata "$KEY")
# Add the nested JSON object to the main JSON
if command -v jq &>/dev/null; then
JSON_DATA=$(echo "$JSON_DATA" | jq --arg key "$KEY" --argjson val "$VALUE" '. + {($key): $val}')
else
echo "Error: jq is required for processing nested metadata" >&2
exit 1
fi
else
# Regular key, just get the value
VALUE=$(get_metadata "$KEY")
# Use jq to add the key-value pair to the JSON object safely
JSON_DATA=$(echo "$JSON_DATA" | jq --arg key "$KEY" --arg val "$VALUE" '. + {($key): $val}')
fi
done
# Output the result (with optional pretty printing)
if [ "$PRETTY_PRINT" = true ] && command -v jq &>/dev/null; then
echo "$JSON_DATA" | jq '.'
else
echo "$JSON_DATA"
fi
@l2D
Copy link
Author

l2D commented Jun 2, 2025

Overview & Example

# sh get_aws_ec2_metadata.sh -h

Usage: get_aws_ec2_metadata.sh [-k key1,key2,...] [-a] [-p] [-l] [-c category]
Options:
  -k KEYS    Comma-separated list of metadata keys to retrieve
  -a         Get all available metadata keys (overrides -k)
  -p         Pretty print JSON output
  -l         List available top-level metadata categories
  -c CAT     Show description for a specific metadata category
  -h         Display this help message

Examples:
  get_aws_ec2_metadata.sh                               # Get default keys (instance-id, ami-id, instance-type, local-ipv4)
  get_aws_ec2_metadata.sh -k mac,instance-id,system    # Get specific keys
  get_aws_ec2_metadata.sh -a                           # Get all available metadata
  get_aws_ec2_metadata.sh -p -k instance-type          # Pretty print specific key
  get_aws_ec2_metadata.sh -l                           # List available metadata categories
  get_aws_ec2_metadata.sh -c placement                 # Show info about placement category

# sh get_aws_ec2_metadata.sh

{
  "instance-id": "i-0c6c1a2ac93f71ddd",
  "ami-id": "ami-031d1a7e5de4eabbb",
  "instance-type": "t3.micro",
  "local-ipv4": "10.0.101.244"
}
# sh get_aws_ec2_metadata.sh -l

Available EC2 Instance Metadata Categories:

ami-id                          The AMI ID used to launch the instance
ami-launch-index                Launch index for multiple instances launched together
ami-manifest-path               Path to the AMI manifest file
ancestor-ami-ids                AMI IDs of instances rebundled to create this AMI
autoscaling/target-lifecycle-state    Target Auto Scaling lifecycle state
block-device-mapping/*          Block device mapping information
events/maintenance/history      Completed or canceled maintenance events
events/maintenance/scheduled    Active maintenance events
events/recommendations/rebalance    EC2 instance rebalance recommendations
hostname                        Private IPv4 DNS hostname or Resource-based name
iam/info                        IAM role information if associated with instance
iam/security-credentials/*      IAM security credentials for the associated role
identity-credentials/ec2/*      Credentials for the instance identity role
instance-action                 Action pending (reboot/shutdown) for the instance
instance-id                     The ID of this instance
instance-life-cycle             The purchasing option (on-demand, spot, etc.)
instance-type                   The type of instance
ipv6                            The IPv6 address of the instance
kernel-id                       The ID of the kernel launched with the instance
local-hostname                  Private IPv4 DNS hostname or Resource-based name
local-ipv4                      Private IPv4 address
mac                             Media access control (MAC) address
network/interfaces/macs/*       Network interface information
placement/availability-zone     The Availability Zone of the instance
placement/availability-zone-id  Static Availability Zone ID
placement/group-name            The name of the placement group
placement/host-id               The ID of the host (for Dedicated Hosts)
placement/partition-number      The partition number
placement/region                The AWS Region of the instance
product-codes                   AWS Marketplace product codes
public-hostname                 Public DNS hostname (IPv4)
public-ipv4                     Public IPv4 address
public-keys/*                   Public keys associated with the instance
ramdisk-id                      RAM disk ID specified at launch
reservation-id                  The ID of the reservation
security-groups                 Security groups applied to the instance
services/domain                 Domain for AWS resources in the Region
services/partition              AWS partition (aws, aws-cn, aws-us-gov)
spot/instance-action            Pending action for Spot Instances
spot/termination-time           Approximate termination time for Spot Instances
tags/instance                   Instance tags (if access is enabled)

Use -c CATEGORY for more detailed information about a specific category.
# sh get_aws_ec2_metadata.sh -pk instance-id,ami-id,mac

{
  "instance-id": "i-0c6c1a2ac####",
  "ami-id": "ami-031d1a7e5de####",
  "mac": "06:1e:######"
}

References:

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