← Back to Blog
🛠️ AWS CLI Mastery
October 8, 2025

JMESPath: Queries Avanzadas en AWS CLI

📋 Contexto

JMESPath es el lenguaje de query que usa AWS CLI con el parámetro --query. Dominar JMESPath transforma AWS CLI de "herramienta básica" a "súper poder de automatización". En este post, las queries que más uso y patrones avanzados.

🎯 ¿Qué es JMESPath?

JMESPath es un lenguaje de query para JSON, similar a:

  • XPath: para XML
  • SQL SELECT: para bases de datos
  • jq: para JSON en CLI

Ejemplo básico:

# JSON
{
  "Users": [
    {"Name": "Alice", "Age": 30},
    {"Name": "Bob", "Age": 25}
  ]
}

# Query
Users[0].Name

# Output
"Alice"

💡 Sintaxis Básica

1. Acceder a Campos

# Dot notation
aws ec2 describe-instances --query 'Reservations[0].Instances[0].InstanceId'

# Output
"i-1234567890abcdef0"

2. Wildcard (Todas las Instancias)

# Get all instance IDs
aws ec2 describe-instances --query 'Reservations[*].Instances[*].InstanceId'

# Flatten (remove nested arrays)
aws ec2 describe-instances --query 'Reservations[].Instances[].InstanceId'

3. Proyecciones (Seleccionar Múltiples Campos)

# Multi-field projection
aws ec2 describe-instances --query 'Reservations[].Instances[].[InstanceId,InstanceType,State.Name]'

# Output (array of arrays)
[
  ["i-111", "t3.micro", "running"],
  ["i-222", "t3.small", "stopped"]
]

4. Filtros

# Filter running instances
aws ec2 describe-instances --query 'Reservations[].Instances[?State.Name==`running`].InstanceId'

# Multiple conditions (AND)
aws ec2 describe-instances --query 'Reservations[].Instances[?State.Name==`running` && InstanceType==`t3.micro`]'

# OR condition
aws ec2 describe-instances --query 'Reservations[].Instances[?State.Name==`running` || State.Name==`stopped`]'
💡 Nota: En JMESPath, los valores literales van con backticks: `running` no 'running'

🚀 Queries Prácticas (Casos Reales)

EC2: Instancias con Tags

# Get Name tag value
aws ec2 describe-instances --query 'Reservations[].Instances[].[InstanceId,Tags[?Key==`Name`].Value|[0]]'

# Filter by Environment tag
aws ec2 describe-instances --query 'Reservations[].Instances[?Tags[?Key==`Environment` && Value==`production`]]'

# Get all instances WITHOUT a specific tag
aws ec2 describe-instances --query 'Reservations[].Instances[?!Tags[?Key==`Owner`]]'

EBS: Volúmenes No Adjuntos

# Unattached volumes
aws ec2 describe-volumes --query 'Volumes[?State==`available`].[VolumeId,Size,VolumeType,CreateTime]'

# Volumes older than X (requires external date comparison)
aws ec2 describe-volumes --query 'Volumes[?State==`available`]|[?CreateTime<`2024-01-01`]'

S3: Buckets con Tamaño

# List buckets with creation date
aws s3api list-buckets --query 'Buckets[].[Name,CreationDate]' --output table

# Get bucket sizes (requires CloudWatch)
aws cloudwatch get-metric-statistics \
    --namespace AWS/S3 \
    --metric-name BucketSizeBytes \
    --dimensions Name=BucketName,Value=my-bucket Name=StorageType,Value=StandardStorage \
    --start-time 2024-12-01T00:00:00Z \
    --end-time 2024-12-30T23:59:59Z \
    --period 86400 \
    --statistics Average \
    --query 'Datapoints|[0].Average'

IAM: Usuarios sin MFA

# Get users without MFA enabled
aws iam get-credential-report --query 'Content' --output text | base64 -d | \
    awk -F, '$4=="false" {print $1}'

# Using JMESPath (if data is JSON)
aws iam list-users --query 'Users[?!MfaActive].[UserName,CreateDate]'

RDS: Instancias Públicamente Accesibles

# Find public RDS instances
aws rds describe-db-instances --query 'DBInstances[?PubliclyAccessible==`true`].[DBInstanceIdentifier,Engine,DBInstanceClass]'

🔥 Queries Avanzadas

1. sort_by() - Ordenar Resultados

# Sort instances by launch time (newest first)
aws ec2 describe-instances --query 'reverse(sort_by(Reservations[].Instances[], &LaunchTime))[].[InstanceId,LaunchTime]'

# Sort by name tag
aws ec2 describe-instances --query 'sort_by(Reservations[].Instances[], &Tags[?Key==`Name`].Value|[0])[]'

2. length() - Contar Elementos

# Count running instances
aws ec2 describe-instances --query 'length(Reservations[].Instances[?State.Name==`running`][])'

# Count security groups
aws ec2 describe-security-groups --query 'length(SecurityGroups)'

3. contains() - Buscar Strings

# Instances with "prod" in name
aws ec2 describe-instances --query 'Reservations[].Instances[?contains(Tags[?Key==`Name`].Value|[0], `prod`)]'

# Security groups with port 22 open
aws ec2 describe-security-groups --query 'SecurityGroups[?IpPermissions[?contains(ToPort, `22`)]]'

4. max_by() / min_by() - Máximo/Mínimo

# Largest EBS volume
aws ec2 describe-volumes --query 'max_by(Volumes, &Size).[VolumeId,Size]'

# Most recently launched instance
aws ec2 describe-instances --query 'max_by(Reservations[].Instances[], &LaunchTime).[InstanceId,LaunchTime]'

5. Nested Projections

# Complex nested query
aws ec2 describe-instances --query 'Reservations[].{
    InstanceId: Instances[0].InstanceId,
    Name: Instances[0].Tags[?Key==`Name`].Value|[0],
    Type: Instances[0].InstanceType,
    State: Instances[0].State.Name,
    SecurityGroups: Instances[0].SecurityGroups[].GroupName,
    PrivateIP: Instances[0].PrivateIpAddress
}'

6. Pipe Expressions (Procesamiento en Cadena)

# Filter, then sort, then project
aws ec2 describe-instances \
    --query 'Reservations[].Instances[?State.Name==`running`] | [].{
        ID: InstanceId, 
        Type: InstanceType
    } | sort_by(@, &Type)'

📊 Formato de Output

JSON (default)

aws ec2 describe-instances --query 'Reservations[].Instances[].[InstanceId,State.Name]' --output json

Table (legible)

aws ec2 describe-instances --query 'Reservations[].Instances[].[InstanceId,InstanceType,State.Name]' --output table

# Output:
---------------------------------------------------------
|              DescribeInstances                       |
+-------------------+--------------+--------------------+
|  i-1234567890     |  t3.micro    |  running           |
|  i-0987654321     |  t3.small    |  stopped           |
+-------------------+--------------+--------------------+

Text (para scripts)

aws ec2 describe-instances --query 'Reservations[].Instances[].InstanceId' --output text

# Output (tab-separated):
i-1234567890    i-0987654321

🛠️ Testing JMESPath Queries

jpterm (Interactive)

# Install
pip install jmespath-terminal

# Use
aws ec2 describe-instances | jpterm

# Interactively test queries with autocomplete

jmespath.org (Online)

Visita jmespath.org para testear queries con tu propio JSON.

Python (Scripting)

import json
import jmespath

data = json.loads(aws_output)
result = jmespath.search('Reservations[].Instances[].InstanceId', data)
print(result)

💡 Tips y Trucos

1. Flatten Arrays con []

# Con [*] (nested arrays)
[
  ["i-111"],
  ["i-222"]
]

# Con [] (flattened)
[
  "i-111",
  "i-222"
]

2. Default Values con ||

# Si Name tag no existe, usa "N/A"
aws ec2 describe-instances --query 'Reservations[].Instances[].[InstanceId, Tags[?Key==`Name`].Value|[0] || `N/A`]'

3. First Element con [0]

# Get first running instance
aws ec2 describe-instances --query 'Reservations[].Instances[?State.Name==`running`][0].InstanceId'

4. Combining Filters

# Running t3.micro instances in production
aws ec2 describe-instances --query 'Reservations[].Instances[?
    State.Name==`running` && 
    InstanceType==`t3.micro` && 
    Tags[?Key==`Environment` && Value==`production`]
]'

📚 Cheat Sheet

Operation Syntax Example
Field access . Users[0].Name
Array index [0] Users[0]
Wildcard [*] Users[*].Name
Flatten [] Users[].Name
Filter [?expr] Users[?Age>`25`]
Projection {key:value} {Name:Name,Age:Age}
Pipe | Users[] | [0]
Sort sort_by() sort_by(Users, &Age)
Length length() length(Users)
Max/Min max_by() max_by(Users, &Age)

🎯 Caso Real: Script Completo

#!/bin/bash
# ec2-report.sh - Comprehensive EC2 report using JMESPath

set -euo pipefail

echo "=== EC2 Instances Report ==="
echo ""

# Running instances count
running=$(aws ec2 describe-instances \
    --query 'length(Reservations[].Instances[?State.Name==`running`][])' \
    --output text)
echo "Running instances: $running"

# Stopped instances count
stopped=$(aws ec2 describe-instances \
    --query 'length(Reservations[].Instances[?State.Name==`stopped`][])' \
    --output text)
echo "Stopped instances: $stopped"

echo ""
echo "=== By Instance Type ==="
aws ec2 describe-instances \
    --query 'Reservations[].Instances[].[InstanceType] | [*] | group_by(@, &[0])' \
    --output table

echo ""
echo "=== Production Instances ==="
aws ec2 describe-instances \
    --query 'Reservations[].Instances[?Tags[?Key==`Environment` && Value==`production`]].[
        InstanceId,
        InstanceType,
        State.Name,
        Tags[?Key==`Name`].Value|[0],
        PrivateIpAddress
    ]' \
    --output table

echo ""
echo "=== Instances Without Name Tag ==="
aws ec2 describe-instances \
    --query 'Reservations[].Instances[?!Tags[?Key==`Name`]].[InstanceId,InstanceType]' \
    --output table
✅ Resultado: Un reporte completo de EC2 con una sola herramienta (AWS CLI + JMESPath), sin necesidad de jq, awk o Python.

📚 Recursos

💭 Conclusión

JMESPath transforma AWS CLI de herramienta de consulta a lenguaje de automatización. Invertir tiempo en aprender JMESPath paga dividendos inmediatos: scripts más simples, menos dependencias (adiós jq/awk), y outputs exactamente como los necesitas.

Mi consejo: practica con jpterm hasta que las queries complejas se vuelvan segunda naturaleza. Es una habilidad que usarás todos los días.