En la lección anterior recorrimos la consola y creamos nuestro primer bucket con el ratón. Funcionó, pero dejamos una pregunta abierta: ¿cómo repites eso mismo cien veces, o cómo demuestras dentro de seis meses qué hiciste exactamente? La respuesta es dejar de hacer clic y empezar a escribir comandos y código.

Esta lección es la puerta de entrada a todo el trabajo serio en AWS. La AWS CLI es la herramienta que usarás a diario para consultar, crear y diagnosticar; los SDK son las bibliotecas con las que tu aplicación habla con AWS desde dentro. Ambos usan las mismas APIs que la consola, así que todo lo que aprendas aquí te sirve para cualquier servicio del resto del curso.

Contenido

  1. Por qué automatizar: el problema de la consola
  2. Instalar AWS CLI v2 en Linux, macOS y Windows
  3. Anatomía de un comando
  4. Configurar credenciales: aws configure, ficheros y perfiles
  5. Orden de precedencia de las credenciales
  6. Seguridad de las claves de acceso
  7. Formatos de salida y filtrado con --query
  8. Paginación, --dry-run y otras opciones útiles
  9. Comandos de exploración imprescindibles
  10. SDKs: qué son y qué lenguajes hay
  11. boto3: el SDK de Python paso a paso
  12. Un vistazo equivalente en JavaScript
  13. CloudShell como alternativa sin instalación

Por qué automatizar: el problema de la consola

Marta creó el bucket de pruebas en la consola. Ahora Luis necesita crear tres buckets iguales (uno por entorno) con las mismas etiquetas y la misma configuración. Con la consola tendría que repetir unos quince clics tres veces, y cualquier despiste produciría una diferencia silenciosa entre entornos.

Los cuatro problemas estructurales de la consola:

Problema Consecuencia real en MercadoFresco
No es reproducible El entorno de desarrollo nunca acaba siendo idéntico al de producción, y los errores "solo pasan en producción"
No es documentable Nadie puede revisar en Git lo que se hizo con el ratón, ni saber por qué
No es componible No puedes encadenar "lista todas las instancias paradas y etiquétalas"
No escala Etiquetar 200 recursos a mano es inviable

La CLI resuelve los cuatro: un comando es texto, y el texto se guarda, se versiona, se revisa, se repite y se encadena. Y es el paso previo natural hacia la infraestructura como código del módulo 9, donde ya ni siquiera escribiremos comandos sino declaraciones del estado deseado.

Instalar AWS CLI v2 en Linux, macOS y Windows

Usa siempre la versión 2. La v1 está en mantenimiento, se instalaba con pip (lo que causaba conflictos de dependencias) y le faltan funciones importantes como el inicio de sesión con IAM Identity Center.

Linux (x86_64)

# 1. Descarga el instalador oficial en un fichero zip
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"

# 2. Descomprime el paquete en el directorio actual
unzip awscliv2.zip

# 3. Ejecuta el instalador con permisos de administrador
sudo ./aws/install

# 4. Limpia los ficheros temporales
rm -rf awscliv2.zip aws/

Qué hace cada línea:

  • curl ... -o "awscliv2.zip": descarga el paquete y lo guarda con ese nombre. La URL es la oficial de AWS; desconfía de instaladores de terceros.
  • unzip: extrae una carpeta aws/ con el instalador.
  • sudo ./aws/install: copia los binarios a /usr/local/aws-cli y crea el enlace /usr/local/bin/aws. Necesita sudo porque escribe fuera de tu directorio personal.
  • rm -rf: borra lo descargado, que ya no hace falta.

Si tu máquina es ARM (por ejemplo, una instancia Graviton), cambia x86_64 por aarch64.

Para actualizar una instalación existente, añade los parámetros de actualización:

sudo ./aws/install --bin-dir /usr/local/bin --install-dir /usr/local/aws-cli --update

macOS

# Descarga el paquete .pkg oficial
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"

# Instala para todos los usuarios del equipo
sudo installer -pkg AWSCLIV2.pkg -target /

Alternativa con Homebrew, si ya lo usas: brew install awscli.

Windows

Descarga y ejecuta el instalador MSI oficial:

https://awscli.amazonaws.com/AWSCLIV2.msi

O desde PowerShell como administrador:

msiexec.exe /i https://awscli.amazonaws.com/AWSCLIV2.msi

Después cierra y abre de nuevo la terminal para que se recargue la variable PATH.

Verificación

En cualquier sistema:

aws --version

Salida esperada (los números variarán):

aws-cli/2.15.40 Python/3.11.8 Linux/6.5.0 exe/x86_64.ubuntu.22

Se lee así: versión de la CLI, versión de Python embebida (no usa tu Python, va incluida), sistema operativo y tipo de compilación. Si ves aws-cli/1.x, tienes la v1 y deberías desinstalarla antes de continuar.

Anatomía de un comando

Todos los comandos siguen la misma estructura:

aws <servicio> <operación> [--parámetro valor] [--opciones-globales]

Un ejemplo real, línea a línea:

aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --region eu-west-1 \
  --output table
Parte Qué es Detalle
aws El programa
ec2 Servicio Suele coincidir con el nombre del servicio: s3, iam, lambda, rds
describe-instances Operación La API se llama DescribeInstances; la CLI usa minúsculas y guiones
--filters "..." Parámetro específico Filtra en el servidor: solo instancias en ejecución
--region Opción global Sobrescribe la región del perfil para este comando
--output table Opción global Formato de salida

Los verbos siguen convenciones muy predecibles, y reconocerlas acelera mucho el aprendizaje:

Prefijo Qué hace Ejemplos
describe- Devuelve información detallada describe-instances, describe-vpcs
list- Devuelve una lista de identificadores list-buckets, list-functions
get- Obtiene un elemento concreto get-caller-identity, get-bucket-tagging
create- Crea un recurso create-bucket, create-tags
delete- / terminate- Elimina delete-bucket, terminate-instances
put- Escribe o sobrescribe una configuración put-bucket-tagging, put-metric-alarm

La ayuda integrada

Es la mejor documentación que tienes y funciona sin conexión:

aws help                        # lista todos los servicios disponibles
aws s3api help                  # lista todas las operaciones de s3api
aws s3api create-bucket help    # todos los parámetros de esa operación, con ejemplos

Se abre en un paginador: navega con las flechas y sal con q.

Configurar credenciales: aws configure, ficheros y perfiles

La CLI necesita saber quién eres. Vamos a crear unas claves de acceso para el usuario mercadofresco-admin que creamos en la lección 01-02.

Paso 1: crear las claves de acceso

En la consola: IAM → Users → mercadofresco-admin → Security credentials → Create access key. Elige el caso de uso Command Line Interface (CLI), confirma el aviso y crea.

Obtendrás dos valores:

  • Access key ID: algo como AKIAIOSFODNN7EXAMPLE. Es público-ish, identifica la clave.
  • Secret access key: algo como wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY. Solo se muestra una vez. Si la pierdes, hay que crear una clave nueva.

Advertencia: nunca crees claves de acceso para el usuario root (lección 01-02). Y si no quieres manejar claves permanentes en absoluto, la alternativa moderna es aws configure sso con IAM Identity Center, que emite credenciales temporales.

Paso 2: aws configure

aws configure

Te hará cuatro preguntas:

AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Default region name [None]: eu-west-1
Default output format [None]: json
  • Región por defecto: eu-west-1, la que decidimos en la lección 01-03. Te evita escribir --region en cada comando.
  • Formato de salida: json es un buen valor por defecto porque es el que consumen otras herramientas.

Los ficheros de configuración

aws configure no hace magia: escribe dos ficheros de texto en tu directorio personal (~/.aws/ en Linux y macOS, %USERPROFILE%\.aws\ en Windows).

~/.aws/credentials — contiene los secretos:

[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

~/.aws/config — contiene los ajustes no secretos:

[default]
region = eu-west-1
output = json

Puedes editarlos a mano perfectamente. Comprueba además que los permisos sean restrictivos:

chmod 600 ~/.aws/credentials

Esto deja el fichero legible y escribible solo por tu usuario. En un equipo compartido es imprescindible.

Perfiles nombrados

Casi nunca trabajarás con una sola identidad. MercadoFresco tendrá pronto un entorno de desarrollo y otro de producción, y conviene que no se puedan confundir.

Crea un perfil nuevo:

aws configure --profile mercadofresco-dev

Los ficheros quedan así:

# ~/.aws/credentials
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

[mercadofresco-dev]
aws_access_key_id = AKIAI44QH8DHBEXAMPLE
aws_secret_access_key = je7MtGbClwBF/2Zp9Utk/h3yCoEXAMPLEKEY
# ~/.aws/config
[default]
region = eu-west-1
output = json

[profile mercadofresco-dev]
region = eu-west-1
output = json

Atención al detalle: en credentials la sección es [mercadofresco-dev], pero en config es [profile mercadofresco-dev], con la palabra profile delante. Es una inconsistencia histórica que provoca errores constantes. Si un perfil "no se encuentra", revisa esto primero.

Para usar un perfil:

# Opción 1: por parámetro, en un comando concreto
aws s3 ls --profile mercadofresco-dev

# Opción 2: por variable de entorno, para toda la sesión de terminal
export AWS_PROFILE=mercadofresco-dev
aws s3 ls

Y para saber siempre con quién estás actuando:

aws sts get-caller-identity --profile mercadofresco-dev

Adquiere el hábito de ejecutar este comando antes de cualquier operación destructiva. Es la diferencia entre borrar la base de datos de desarrollo y borrar la de producción.

Orden de precedencia de las credenciales

Cuando ejecutas un comando, la CLI busca credenciales en un orden fijo y se queda con la primera fuente que encuentre. Conocer este orden explica el 90 % de los "pero si yo configuré eso":

# Fuente Cómo se indica Cuándo se usa
1 Opciones de línea de comandos --profile, --region Sobrescribe todo lo demás
2 Variables de entorno AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_PROFILE, AWS_DEFAULT_REGION CI/CD, contenedores, sesiones temporales
3 Fichero ~/.aws/credentials Perfil [default] o el indicado Trabajo diario en tu portátil
4 Fichero ~/.aws/config Sección [profile ...] Configuración y roles
5 Credenciales de contenedor Variable inyectada por ECS/EKS Contenedores en AWS (módulo 10)
6 Rol de instancia (IMDS) Metadatos de la propia EC2 La forma correcta dentro de AWS

Los dos últimos son los importantes conceptualmente. Cuando tu código se ejecuta dentro de AWS —en una instancia EC2, en una función Lambda, en un contenedor— no necesita claves en absoluto: se le asigna un rol IAM y el SDK obtiene automáticamente credenciales temporales que se rotan solas cada pocas horas.

flowchart TB
    A["Comando o SDK necesita credenciales"] --> B{"--profile en la linea?"}
    B -->|si| USA1["Usa ese perfil"]
    B -->|no| C{"Variables de entorno?"}
    C -->|si| USA2["Usa las variables"]
    C -->|no| D{"Fichero ~/.aws/credentials?"}
    D -->|si| USA3["Usa el perfil default"]
    D -->|no| E{"Corriendo dentro de AWS?"}
    E -->|si| USA4["Rol de la instancia - credenciales temporales - LO IDEAL"]
    E -->|no| F["Error: Unable to locate credentials"]

Los roles IAM se estudian a fondo en la lección 04-01. Quédate con la idea: claves permanentes solo en tu portátil; dentro de AWS, siempre roles.

Seguridad de las claves de acceso

Una clave de acceso de mercadofresco-admin es, literalmente, el control total de la cuenta. Estas reglas no son opcionales.

Nunca en el código ni en Git

Este es el error que arruina cuentas todos los días. Hay bots que escanean GitHub de forma continua buscando cadenas que empiecen por AKIA; una clave publicada se explota en minutos, típicamente lanzando decenas de instancias grandes para minar criptomonedas. La factura resultante puede ser de miles de euros.

Protección mínima en cualquier repositorio:

# .gitignore
.env
.aws/
*.pem
credentials

Y una barrera automática, muy recomendable:

# git-secrets escanea cada commit buscando patrones de credenciales de AWS
git secrets --install
git secrets --register-aws

Si alguna vez publicas una clave por error: desactívala y bórrala inmediatamente en IAM, revisa CloudTrail (lección 05-03) para ver qué se hizo con ella, y crea una nueva. Borrar el commit no sirve de nada: ya está indexado.

Otras reglas

Regla Por qué
Rotar cada 90 días Reduce la ventana de explotación de una clave filtrada
Una clave por persona y uso Permite revocar una sin afectar a las demás
Eliminar las claves inactivas IAM muestra la fecha de último uso; lo que no se usa, se borra
Nunca claves en el root Ya visto en 01-02
Dentro de AWS, roles en vez de claves Credenciales temporales, rotadas automáticamente
MFA para operaciones sensibles Se puede exigir en las políticas IAM (lección 04-01)

El procedimiento de rotación sin cortes es sencillo: crea una segunda clave, actualiza donde se use, comprueba que todo funciona, desactiva la antigua, espera unos días y bórrala. IAM permite dos claves activas por usuario precisamente para esto.

Formatos de salida y filtrado con --query

Los comandos de AWS devuelven mucha información. Aprender a recortarla es lo que convierte la CLI en una herramienta cómoda.

Formatos de salida

Formato Cuándo usarlo
json Por defecto. Ideal para encadenar con jq o para procesar en scripts
table Para leer con los ojos: bordes ASCII y columnas
text Para procesar con grep, awk o cut; separado por tabuladores
yaml Más legible que JSON para configuraciones largas
aws ec2 describe-regions --output table

--query con JMESPath

--query aplica una expresión JMESPath sobre la respuesta, en el cliente. Vamos de menos a más.

Nivel 1 — extraer una lista:

# Devuelve solo el array Regions completo
aws ec2 describe-regions --query 'Regions'

Nivel 2 — un campo de cada elemento:

# De cada región, solo su nombre. Los corchetes [] recorren la lista
aws ec2 describe-regions --query 'Regions[].RegionName' --output text

Salida:

eu-west-1	eu-west-2	eu-west-3	us-east-1	...

Nivel 3 — renombrar campos en un objeto:

aws ec2 describe-regions \
  --query 'Regions[].{Region:RegionName, Endpoint:Endpoint}' \
  --output table

Las llaves {...} construyen un objeto nuevo; a la izquierda del : va el nombre que quieres ver, a la derecha el campo original.

Nivel 4 — filtrar con una condición:

# Solo las regiones europeas: ? introduce el filtro, contains() es una función JMESPath
aws ec2 describe-regions \
  --query 'Regions[?contains(RegionName, `eu-`)].RegionName' \
  --output table

Fíjate en las comillas invertidas alrededor de eu-: en JMESPath, los literales van entre backticks, no entre comillas normales. Es una fuente frecuente de errores.

Nivel 5 — un caso real de MercadoFresco:

aws ec2 describe-instances \
  --query 'Reservations[].Instances[].{
      Id:InstanceId,
      Tipo:InstanceType,
      Estado:State.Name,
      IP:PrivateIpAddress,
      Zona:Placement.AvailabilityZone,
      Nombre:Tags[?Key==`Name`]|[0].Value
    }' \
  --output table

Desglose:

  • Reservations[].Instances[]: aplana la estructura anidada de EC2 (las instancias vienen agrupadas por reserva) en una sola lista.
  • State.Name: navega dentro de un objeto anidado con el punto.
  • Tags[?Key==\Name`]|[0].Value: filtra las etiquetas quedándose con la de clave Name, y el |[0]` toma el primer resultado de esa lista para extraer su valor.

--query frente a --filters

--filters --query
Dónde se aplica En el servidor de AWS En tu equipo, tras recibir la respuesta
Efecto Reduce lo que se transfiere Solo reduce lo que se muestra
Disponibilidad Solo en algunos servicios y campos En cualquier comando
Recomendación Úsalo siempre que exista Para dar forma al resultado

Lo ideal es combinarlos: --filters para traer poco, --query para presentarlo bien.

Paginación, --dry-run y otras opciones útiles

Paginación

Cuando hay muchos resultados, la CLI v2 los pagina automáticamente y abre un paginador. Opciones para controlarlo:

# Devuelve como máximo 5 elementos
aws s3api list-buckets --max-items 5

# Desactiva el paginador interactivo (útil en scripts)
aws ec2 describe-instances --no-cli-pager

# Desactivarlo de forma permanente
export AWS_PAGER=""

Si limitas con --max-items y hay más resultados, la respuesta incluye un NextToken que puedes pasar con --starting-token para pedir la página siguiente.

--dry-run

Muchas operaciones de EC2 aceptan --dry-run: comprueban si tendrías permisos para hacer la operación, pero no la ejecutan.

aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 \
  --instance-type t3.micro \
  --dry-run

Si tienes permiso, verás:

An error occurred (DryRunOperation) when calling the RunInstances operation:
Request would have succeeded, but DryRun flag is set

Ese mensaje, aunque diga "error", es la confirmación de éxito. Si no tuvieras permiso, el error sería UnauthorizedOperation. Es una forma segura de verificar permisos sin crear nada ni gastar nada.

Otras opciones que se usan mucho

Opción Para qué
--no-cli-pager Salida directa sin paginador
--debug Traza completa de la petición HTTP; imprescindible para diagnosticar
--cli-input-json file://params.json Pasar todos los parámetros desde un fichero
--generate-cli-skeleton Generar la plantilla JSON de parámetros de una operación
--endpoint-url Apuntar a un endpoint alternativo (p. ej. un emulador local)

Comandos de exploración imprescindibles

Estos son los comandos que ejecutarás cientos de veces. Guárdalos.

# 1. ¿Quién soy? Devuelve cuenta, ID de usuario y ARN
aws sts get-caller-identity
# 2. ¿Qué regiones existen y están habilitadas en mi cuenta?
aws ec2 describe-regions --query 'Regions[].RegionName' --output text
# 3. ¿Qué zonas de disponibilidad tiene mi región, con su ID físico?
aws ec2 describe-availability-zones \
  --query 'AvailabilityZones[].{AZ:ZoneName, Id:ZoneId, Estado:State}' \
  --output table
# 4. ¿Qué buckets tengo?
aws s3 ls
# 5. ¿Qué instancias EC2 tengo y en qué estado?
aws ec2 describe-instances \
  --query 'Reservations[].Instances[].{Id:InstanceId, Estado:State.Name}' \
  --output table
# 6. ¿Qué configuración tiene la CLI y de dónde sale cada valor?
aws configure list

La salida del último es especialmente útil porque indica el origen de cada valor:

      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile     mercadofresco-dev           env    AWS_PROFILE
access_key     ****************MPLE   shared-credentials-file
    region                eu-west-1      config-file    ~/.aws/config

Cuando algo no funcione como esperas, este comando te dirá exactamente qué credencial y qué región está usando la CLI y por qué.

SDKs: qué son y qué lenguajes hay

Un SDK (Software Development Kit) es una biblioteca que permite a tu aplicación llamar a las APIs de AWS desde el lenguaje en el que está escrita, sin construir peticiones HTTP firmadas a mano.

La diferencia con la CLI:

CLI SDK
Quién lo usa Una persona en una terminal, o un script Tu aplicación, en tiempo de ejecución
Caso típico "Lista los buckets", "para esa instancia" "Cuando el cliente sube una foto, guárdala en S3"
Formato Comandos de texto Código en tu lenguaje

Lenguajes con SDK oficial:

Lenguaje Nombre del SDK Nota
Python boto3 El más usado en scripting y automatización
JavaScript / TypeScript AWS SDK for JavaScript v3 Modular: instalas solo el cliente que usas
Java AWS SDK for Java 2.x Muy común en empresa
.NET (C#) AWS SDK for .NET
Go AWS SDK for Go v2 Habitual en herramientas de infraestructura
PHP AWS SDK for PHP Relevante para el monolito actual de MercadoFresco
Rust, Ruby, C++, Kotlin, Swift Disponibles

Todos comparten la misma lógica: cadena de proveedores de credenciales (el mismo orden de precedencia que vimos), reintentos automáticos con retroceso exponencial, y nombres de operación equivalentes a los de la API.

boto3: el SDK de Python paso a paso

Luis va a usar Python para los scripts de MercadoFresco, así que veamos boto3 en detalle.

Instalación

# Crear un entorno virtual aislado (buena práctica: no instalar en el Python del sistema)
python3 -m venv venv

# Activarlo (en Windows: venv\Scripts\activate)
source venv/bin/activate

# Instalar boto3
pip install boto3

Cliente frente a recurso

boto3 ofrece dos interfaces distintas para lo mismo:

Client Resource
Nivel Bajo: refleja la API 1:1 Alto: orientado a objetos
Cobertura Todos los servicios y operaciones Solo algunos servicios; en mantenimiento
Estilo s3.list_buckets() devuelve un diccionario for b in s3.buckets.all()
Recomendación Úsalo por defecto Solo si te lo encuentras en código heredado

Ejemplo del mismo objetivo con ambos:

import boto3

# --- Interfaz Client (recomendada) ---
s3_client = boto3.client("s3")
respuesta = s3_client.list_buckets()          # devuelve un diccionario tal cual lo da la API
for bucket in respuesta["Buckets"]:
    print(bucket["Name"], bucket["CreationDate"])

# --- Interfaz Resource (más antigua) ---
s3_recurso = boto3.resource("s3")
for bucket in s3_recurso.buckets.all():       # objetos con atributos
    print(bucket.name, bucket.creation_date)

Credenciales y sesiones

boto3 usa exactamente el mismo orden de precedencia que la CLI, así que si aws s3 ls funciona, tu código Python funcionará sin configurar nada.

Para elegir perfil o región explícitamente se usa una sesión:

import boto3

# Una sesión encapsula credenciales + región
sesion = boto3.Session(profile_name="mercadofresco-dev", region_name="eu-west-1")
s3 = sesion.client("s3")

print(sesion.client("sts").get_caller_identity()["Arn"])

Manejo de errores con ClientError

Todo error devuelto por un servicio de AWS llega como botocore.exceptions.ClientError. Ignorarlo es el error más común de los scripts de principiante.

import boto3
from botocore.exceptions import ClientError

s3 = boto3.client("s3", region_name="eu-west-1")

def existe_bucket(nombre: str) -> bool:
    """Comprueba si un bucket existe y es accesible para nuestras credenciales."""
    try:
        s3.head_bucket(Bucket=nombre)          # petición ligera: solo cabeceras
        return True
    except ClientError as error:
        # El código concreto está siempre en error.response["Error"]["Code"]
        codigo = error.response["Error"]["Code"]
        if codigo == "404":
            print(f"El bucket '{nombre}' no existe.")
        elif codigo == "403":
            print(f"El bucket '{nombre}' existe pero no tienes permiso para verlo.")
        else:
            raise                              # cualquier otro error, que se propague
        return False

print(existe_bucket("mercadofresco-fotos-producto"))

Puntos clave del fragmento:

  • head_bucket es la forma barata de comprobar existencia: no descarga contenido.
  • error.response["Error"]["Code"] es la ruta estándar para leer el código de error. Memorízala.
  • Distinguir 404 de 403 importa: "no existe" y "existe pero no puedes verlo" exigen acciones distintas.
  • El raise final evita el antipatrón de tragarse errores desconocidos, que oculta problemas reales.

Paginación en boto3

Las APIs devuelven como mucho unos cientos de elementos por llamada. Si iteras solo la primera respuesta, perderás datos en silencio. Los paginadores lo resuelven:

import boto3

s3 = boto3.client("s3", region_name="eu-west-1")

paginador = s3.get_paginator("list_objects_v2")
paginas = paginador.paginate(Bucket="mercadofresco-fotos-producto", Prefix="2026/")

total = 0
for pagina in paginas:                     # cada iteración es una llamada a la API
    for objeto in pagina.get("Contents", []):   # .get() evita KeyError si la página viene vacía
        total += 1
print(f"Objetos encontrados: {total}")

Un vistazo equivalente en JavaScript

Para que veas que los conceptos se trasladan, el mismo listado de buckets con el AWS SDK for JavaScript v3:

# El SDK v3 es modular: se instala solo el cliente del servicio que necesitas
npm install @aws-sdk/client-s3
// Importa solo el cliente de S3 y el comando concreto que vamos a usar
import { S3Client, ListBucketsCommand } from "@aws-sdk/client-s3";

// El cliente lee credenciales con el mismo orden de precedencia que la CLI
const cliente = new S3Client({ region: "eu-west-1" });

async function listarBuckets() {
  try {
    // En el v3, cada operación es un objeto Command que se envía con send()
    const respuesta = await cliente.send(new ListBucketsCommand({}));
    respuesta.Buckets.forEach((b) => console.log(b.Name, b.CreationDate));
  } catch (error) {
    // El equivalente a ClientError: el código está en error.name
    console.error("Error de AWS:", error.name, "-", error.message);
  }
}

listarBuckets();

Diferencias respecto a boto3: el v3 es modular (menos peso en Lambda) y usa el patrón comando: creas un objeto XxxCommand y lo envías con send(). La lógica de credenciales, reintentos y errores es equivalente.

CloudShell como alternativa sin instalación

Como vimos en la lección 01-04, AWS CloudShell te da una terminal en el navegador con la CLI v2, Python y boto3 ya instalados, y autenticada automáticamente con el usuario de la consola.

CLI local CloudShell
Instalación No
Credenciales Configuras claves Heredadas, temporales
Rendimiento y ficheros locales Total Limitado (1 GB persistente)
Sesión Permanente Caduca por inactividad
Ideal para Trabajo diario, scripts, CI Pruebas rápidas, emergencias, formación

CloudShell es especialmente útil para el escenario "estoy en un ordenador que no es el mío y necesito diagnosticar algo ya". No sustituye a la CLI local para el trabajo diario, pero es una red de seguridad excelente.

Errores Comunes y Consejos

  • Unable to locate credentials. No has configurado el perfil, o has usado un nombre de perfil inexistente. Diagnostica con aws configure list.
  • ProfileNotFound con el perfil bien escrito. Casi siempre es la sección de ~/.aws/config sin el prefijo profile : debe ser [profile mercadofresco-dev], no [mercadofresco-dev].
  • You must specify a region. Ni el perfil ni la variable de entorno ni el comando indican región. Añade --region o configúrala en el perfil.
  • AccessDenied inesperado. Comprueba primero con qué identidad estás actuando con aws sts get-caller-identity; muy a menudo es el perfil equivocado, no un problema de permisos.
  • Subir claves a Git. El desastre más caro y más frecuente. .gitignore, git secrets y, mejor aún, credenciales temporales.
  • Usar claves permanentes dentro de una instancia EC2. Nunca. Se usa un rol de instancia (lección 04-01), que además rota las credenciales solo.
  • Olvidar la paginación. Tu script dice "hay 1.000 objetos" cuando hay 50.000. Usa paginadores en boto3 y --max-items/NextToken en la CLI.
  • Confundir aws s3 con aws s3api. El primero es de alto nivel (ls, cp, sync); el segundo expone la API completa (get-bucket-tagging, put-bucket-policy). Si una opción no existe en s3, búscala en s3api.
  • Ignorar ClientError. Un script que falla en silencio es peor que uno que se rompe.
  • Consejo: define alias en tu terminal para lo que repitas mucho, por ejemplo alias quien='aws sts get-caller-identity'.
  • Consejo: cuando un comando no funcione y no entiendas por qué, añade --debug. Verás la petición firmada completa y la respuesta del servicio.

Ejercicios

Ejercicio 1: instalación y exploración

  1. Instala AWS CLI v2 en tu sistema y verifica la versión.
  2. Configura un perfil llamado mercadofresco-admin con región eu-west-1 y salida json.
  3. Ejecuta aws sts get-caller-identity y anota tu ARN completo.
  4. Escribe un solo comando que muestre, en formato tabla, todas las regiones cuyo nombre empiece por eu-, con una única columna llamada Region.

Ejercicio 2: script bash de inventario

Escribe un script inventario-mercadofresco.sh que:

  1. Compruebe con qué identidad y en qué cuenta se está ejecutando, y lo muestre.
  2. Liste todos los buckets de S3 de la cuenta.
  3. Liste las zonas de disponibilidad de eu-west-1 con su ID físico.
  4. Liste las instancias EC2 con su ID, tipo, estado y zona (si no hay ninguna, debe indicarlo sin fallar).
  5. Todo con salida en tabla y sin abrir el paginador interactivo.

Ejercicio 3: script boto3 que crea y etiqueta un bucket

Escribe un script Python crear_bucket_mercadofresco.py que:

  1. Reciba el nombre del bucket como argumento de línea de comandos.
  2. Compruebe si ya existe (distinguiendo "no existe" de "sin permisos").
  3. Si no existe, lo cree en eu-west-1 con el bloqueo de acceso público activado.
  4. Le aplique las etiquetas Proyecto=mercadofresco, Entorno=pruebas, Componente=formacion.
  5. Muestre las etiquetas resultantes.
  6. Maneje los errores con ClientError y devuelva un código de salida distinto de cero si falla.

Coste: un bucket vacío no genera coste apreciable y la capa gratuita cubre 5 GB. Aun así, bórralo al terminar con aws s3 rb s3://<nombre-del-bucket>.

Soluciones

Solución 1

# 1. Instalación en Linux y verificación
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip && sudo ./aws/install
aws --version

# 2. Perfil
aws configure --profile mercadofresco-admin
# Región: eu-west-1 ; Salida: json

# 3. Identidad
aws sts get-caller-identity --profile mercadofresco-admin

# 4. Regiones europeas en tabla, columna única llamada Region
aws ec2 describe-regions \
  --query 'Regions[?starts_with(RegionName, `eu-`)].{Region:RegionName}' \
  --output table

Se puede usar starts_with() o contains(); starts_with es más preciso porque contains también aceptaría un nombre que llevara eu- en el medio.

Solución 2

#!/usr/bin/env bash
# inventario-mercadofresco.sh - inventario rápido de la cuenta de AWS

set -euo pipefail            # aborta ante error, variable no definida o fallo en una tubería

PERFIL="${1:-mercadofresco-admin}"   # primer argumento o valor por defecto
REGION="eu-west-1"
export AWS_PAGER=""                  # desactiva el paginador interactivo

echo "===== IDENTIDAD ====="
aws sts get-caller-identity \
  --profile "$PERFIL" \
  --query '{Cuenta:Account, Identidad:Arn}' \
  --output table

echo "===== BUCKETS S3 ====="
aws s3api list-buckets \
  --profile "$PERFIL" \
  --query 'Buckets[].{Nombre:Name, Creado:CreationDate}' \
  --output table

echo "===== ZONAS DE DISPONIBILIDAD DE $REGION ====="
aws ec2 describe-availability-zones \
  --profile "$PERFIL" --region "$REGION" \
  --query 'AvailabilityZones[].{AZ:ZoneName, IdFisico:ZoneId, Estado:State}' \
  --output table

echo "===== INSTANCIAS EC2 ====="
INSTANCIAS=$(aws ec2 describe-instances \
  --profile "$PERFIL" --region "$REGION" \
  --query 'Reservations[].Instances[].{Id:InstanceId, Tipo:InstanceType, Estado:State.Name, Zona:Placement.AvailabilityZone}' \
  --output table)

if [ -z "$INSTANCIAS" ]; then
  echo "(no hay instancias EC2 en $REGION)"
else
  echo "$INSTANCIAS"
fi

echo "===== FIN DEL INVENTARIO ====="

Detalles importantes:

  • set -euo pipefail evita que el script continúe alegremente tras un fallo.
  • export AWS_PAGER="" es imprescindible en scripts: sin ello, la CLI abriría un paginador y el script se quedaría bloqueado esperando a que alguien pulse q.
  • Guardar la salida en una variable permite comprobar si está vacía y dar un mensaje claro en lugar de imprimir una tabla vacía.

Se ejecuta con:

chmod +x inventario-mercadofresco.sh
./inventario-mercadofresco.sh mercadofresco-admin

Solución 3

#!/usr/bin/env python3
"""crear_bucket_mercadofresco.py - crea y etiqueta un bucket de S3 para MercadoFresco."""

import sys
import boto3
from botocore.exceptions import ClientError

REGION = "eu-west-1"
ETIQUETAS = [
    {"Key": "Proyecto",   "Value": "mercadofresco"},
    {"Key": "Entorno",    "Value": "pruebas"},
    {"Key": "Componente", "Value": "formacion"},
]


def existe_bucket(s3, nombre: str) -> bool:
    """True si el bucket existe y es accesible; False si no existe."""
    try:
        s3.head_bucket(Bucket=nombre)
        return True
    except ClientError as error:
        codigo = error.response["Error"]["Code"]
        if codigo in ("404", "NoSuchBucket"):
            return False
        if codigo == "403":
            print(f"El bucket '{nombre}' existe pero pertenece a otra cuenta.")
            sys.exit(1)
        raise


def crear_bucket(s3, nombre: str) -> None:
    """Crea el bucket en REGION con el acceso público bloqueado."""
    # Fuera de us-east-1 hay que indicar LocationConstraint explícitamente
    s3.create_bucket(
        Bucket=nombre,
        CreateBucketConfiguration={"LocationConstraint": REGION},
    )
    print(f"Bucket '{nombre}' creado en {REGION}.")

    # Bloqueo total de acceso público: las cuatro opciones a True
    s3.put_public_access_block(
        Bucket=nombre,
        PublicAccessBlockConfiguration={
            "BlockPublicAcls": True,
            "IgnorePublicAcls": True,
            "BlockPublicPolicy": True,
            "RestrictPublicBuckets": True,
        },
    )
    print("Acceso público bloqueado.")


def etiquetar(s3, nombre: str) -> None:
    """Aplica el conjunto de etiquetas (sobrescribe las existentes)."""
    s3.put_bucket_tagging(Bucket=nombre, Tagging={"TagSet": ETIQUETAS})
    print("Etiquetas aplicadas.")


def main() -> int:
    if len(sys.argv) != 2:
        print("Uso: python crear_bucket_mercadofresco.py <nombre-del-bucket>")
        return 1

    nombre = sys.argv[1]
    s3 = boto3.client("s3", region_name=REGION)

    try:
        if existe_bucket(s3, nombre):
            print(f"El bucket '{nombre}' ya existe en tu cuenta; no se crea de nuevo.")
        else:
            crear_bucket(s3, nombre)

        etiquetar(s3, nombre)

        actuales = s3.get_bucket_tagging(Bucket=nombre)["TagSet"]
        print("\nEtiquetas actuales del bucket:")
        for etiqueta in actuales:
            print(f"  {etiqueta['Key']} = {etiqueta['Value']}")

    except ClientError as error:
        print(f"Error de AWS: {error.response['Error']['Code']} - "
              f"{error.response['Error']['Message']}", file=sys.stderr)
        return 1

    return 0


if __name__ == "__main__":
    sys.exit(main())

Claves de la solución:

  • LocationConstraint: si creas un bucket en cualquier región que no sea us-east-1 y omites este parámetro, S3 lo crea en us-east-1. Es una de las rarezas históricas de la API.
  • put_public_access_block con las cuatro opciones a True es la protección que evita la fuga de datos más habitual en AWS.
  • put_bucket_tagging sobrescribe el conjunto completo de etiquetas: si quisieras añadir una sin borrar las demás, tendrías que leerlas primero con get_bucket_tagging y fusionarlas.
  • Códigos de salida: 0 si todo va bien, 1 si falla, para poder encadenar el script en un pipeline.

Ejecución y limpieza:

python crear_bucket_mercadofresco.py mercadofresco-pruebas-cli-jc-2741

# Limpieza obligatoria al terminar (rb = remove bucket; debe estar vacío)
aws s3 rb s3://mercadofresco-pruebas-cli-jc-2741

Conclusión

Con esta lección cierras el módulo de introducción y, sobre todo, das el salto de "usuario de AWS" a "operador de AWS". Has instalado la AWS CLI v2, has entendido la anatomía de un comando (aws <servicio> <operación> --parámetros) y las convenciones de nombres que te permiten adivinar comandos que aún no conoces. Has configurado credenciales con aws configure, has visto qué hay realmente dentro de ~/.aws/credentials y ~/.aws/config, y sabes trabajar con perfiles nombrados para no confundir jamás desarrollo con producción. Conoces el orden de precedencia de las credenciales, que explica casi todos los comportamientos extraños, y las reglas de seguridad innegociables: nunca claves en Git, rotación periódica y, dentro de AWS, roles en lugar de claves.

Has aprendido a domar la salida de los comandos con --output y con --query y JMESPath, a evitar la trampa silenciosa de la paginación, a verificar permisos sin gastar nada con --dry-run, y tienes en la mano el puñado de comandos de exploración que usarás cada día. Y has dado tus primeros pasos con los SDK: boto3 en Python, con la distinción entre client y resource, el manejo correcto de errores mediante ClientError y los paginadores, más el equivalente en JavaScript para ver que los conceptos son universales.

MercadoFresco ya tiene todo lo necesario para empezar de verdad: una cuenta segura y vigilada, una región elegida con criterio, una consola que sabes manejar y unas herramientas de línea de comandos y programación que hacen el trabajo repetible.

En el módulo 2, «Servicios principales de AWS», empezando por la lección 02-01 «Amazon EC2», levantaremos el primer servidor real de MercadoFresco en la nube y comenzaremos la migración que resolverá, uno a uno, los cuatro problemas que descubrimos en la primera lección.

Curso de AWS

Módulo 1: Introducción a AWS

Módulo 2: Servicios principales de AWS

Módulo 3: Redes y entrega de contenido

Módulo 4: Seguridad e identidad

Módulo 5: Monitorización y gestión

Módulo 6: Bases de datos

Módulo 7: Integración de aplicaciones

Módulo 8: Herramientas para desarrolladores

Módulo 9: Infraestructura como código y gobierno de cuentas

Módulo 10: Contenedores en AWS

Módulo 11: Mejores prácticas y gestión de costos

© Copyright 2026. Todos los derechos reservados