Last active
June 19, 2025 15:31
-
-
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
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
| #!/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 |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Overview & Example
References: