0Pricing
Linux Command Line & Bash Scripting Mastery · Lección

Scripting de recursos en la nube mediante CLI y jq

Administre de forma idempotente las CLI de proveedores cloud y analice respuestas JSON para aprovisionar y eliminar recursos.

Scripting de recursos en la nube mediante CLI y jq es una lección gratuita de Linux Command Line & Bash Scripting Mastery en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Linux Command Line & Bash Scripting Mastery, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Linux Command Line & Bash Scripting Mastery incluye 4 lecciones en total.

Por qué es importante que los scripts para la nube sean idempotentes

En el nivel C2, crear scripts para la nube no consiste en hacer clic en botones, sino en escribir código que pueda ejecutarse de forma segura varias veces sin crear recursos duplicados ni fallar en la segunda ejecución.

Un script idempotente comprueba si un recurso ya existe antes de crearlo. Este es el fundamento de una automatización de infraestructura fiable.

  • Las CLI de la nube (AWS, GCP, Azure) devuelven JSON; analizar esa salida es esencial.
  • jq es la herramienta Unix estándar para extraer, filtrar y transformar JSON desde scripts de shell.
  • Combinar la CLI, jq y la lógica condicional permite escribir scripts de aprovisionamiento robustos y repetibles.

A lo largo de esta lección aprovisionará buckets de S3, instancias EC2 y roles de IAM usando AWS CLI como referencia, con patrones que se transfieren directamente a gcloud y az.

Instalar y verificar las CLI de la nube

Antes de crear scripts, confirme que las herramientas adecuadas están disponibles. En CI, fije siempre las versiones para evitar diferencias entre entornos.

El fragmento siguiente comprueba AWS CLI v2, jq y el SDK de GCP, e instala únicamente lo que falta; es un patrón útil en scripts de arranque para máquinas virtuales o contenedores nuevos.

#!/usr/bin/env bash
set -euo pipefail

check_or_install() {
  local cmd="$1"
  local install_cmd="$2"
  if ! command -v "$cmd" &>/dev/null; then
    echo "[INFO] $cmd not found — installing..."
    eval "$install_cmd"
  else
    echo "[OK]   $cmd $("$cmd" --version 2>&1 | head -1)"
  fi
}

# AWS CLI v2
check_or_install aws \
  'curl -fsSL https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -o /tmp/awscliv2.zip && unzip -q /tmp/awscliv2.zip -d /tmp && sudo /tmp/aws/install'

# jq
check_or_install jq \
  'sudo apt-get install -y jq 2>/dev/null || sudo yum install -y jq'

# gcloud (optional)
check_or_install gcloud \
  'echo "Install gcloud SDK manually from https://cloud.google.com/sdk"'

echo "All prerequisites satisfied."

Consultar recursos existentes con jq

El primer paso de cualquier script idempotente es una lectura: consulte a la API si el recurso ya existe y, después, ramifique el flujo según corresponda.

AWS CLI siempre devuelve JSON. jq permite extraer exactamente el campo que necesita:

  • jq -r '.Buckets[].Name': muestra cadenas sin formato, un nombre de bucket por línea.
  • jq -e: termina con el código 1 si la expresión produce null o false, por lo que resulta ideal para las condiciones if.
  • jq '.[] | select(.Name == env.BUCKET)': filtra usando una variable del shell mediante env.

El fragmento siguiente muestra todos los buckets de S3 y comprueba si ya existe un bucket de destino.

#!/usr/bin/env bash
set -euo pipefail

BUCKET="my-devops-artifacts-$(date +%Y%m)"

echo "Fetching existing S3 buckets..."
EXISTING=$(aws s3api list-buckets --output json)

# Extract names as newline-separated list
echo "$EXISTING" | jq -r '.Buckets[].Name'

# Check if target bucket exists
if echo "$EXISTING" | jq -e --arg b "$BUCKET" '.Buckets[] | select(.Name == $b)' > /dev/null 2>&1; then
  echo "[EXISTS] Bucket $BUCKET already present — skipping creation."
else
  echo "[MISSING] Bucket $BUCKET not found — will create."
fi

Crear buckets de S3 de forma idempotente

Una vez implementada la comprobación de existencia, incluya la creación dentro de una condición de protección. Una función de nube bien estructurada sigue este patrón:

  1. Leer el estado actual desde la API.
  2. Comparar el estado deseado con el estado real.
  3. Actuar únicamente sobre la diferencia.

Observe la opción --create-bucket-configuration: es obligatoria en todas las regiones excepto us-east-1. Codificar la región directamente en el script evita fallos silenciosos cuando AWS_DEFAULT_REGION no está definida.

#!/usr/bin/env bash
set -euo pipefail

REGION="eu-west-1"
BUCKET="my-devops-artifacts-$(date +%Y%m)"

ensure_bucket() {
  local bucket="$1"
  local region="$2"

  local existing
  existing=$(aws s3api list-buckets --query 'Buckets[].Name' --output json)

  if echo "$existing" | jq -e --arg b "$bucket" 'index($b) != null' > /dev/null 2>&1; then
    echo "[SKIP] Bucket $bucket already exists."
    return 0
  fi

  echo "[CREATE] Creating bucket $bucket in $region..."
  aws s3api create-bucket \
    --bucket "$bucket" \
    --region "$region" \
    --create-bucket-configuration LocationConstraint="$region"

  # Enable versioning immediately after creation
  aws s3api put-bucket-versioning \
    --bucket "$bucket" \
    --versioning-configuration Status=Enabled

  echo "[DONE] Bucket $bucket created with versioning enabled."
}

ensure_bucket "$BUCKET" "$REGION"

Analizar JSON anidado: estado de una instancia EC2

Las respuestas de EC2 están profundamente anidadas. Tanto el recorrido de rutas de jq como --query (JMESPath, nativo de AWS CLI) funcionan, pero jq es más potente para la lógica compleja.

Patrones clave de jq para EC2:

  • .Reservations[].Instances[]: aplana la estructura de dos matrices.
  • select(.State.Name == "running"): filtra por estado.
  • .Tags[] | select(.Key == "Name") | .Value: extrae el valor de una etiqueta.

El fragmento localiza una instancia en ejecución mediante su etiqueta Name y devuelve su ID y su IP privada.

#!/usr/bin/env bash
set -euo pipefail

INSTANCE_NAME="web-server-prod"

RESULT=$(aws ec2 describe-instances \
  --filters \
    "Name=tag:Name,Values=${INSTANCE_NAME}" \
    "Name=instance-state-name,Values=running" \
  --output json)

# Extract instance ID and private IP using jq
INSTANCE_ID=$(echo "$RESULT" | jq -r \
  '.Reservations[].Instances[] | .InstanceId')

PRIVATE_IP=$(echo "$RESULT" | jq -r \
  '.Reservations[].Instances[] | .PrivateIpAddress')

if [[ -z "$INSTANCE_ID" ]]; then
  echo "[WARN] No running instance named '$INSTANCE_NAME' found."
  exit 1
fi

echo "Instance ID : $INSTANCE_ID"
echo "Private IP  : $PRIVATE_IP"

Aprovisionar roles de IAM de forma idempotente

Los recursos de IAM son globales y no deben duplicarse. AWS devuelve un código de error específico, EntityAlreadyExists, cuando intenta crear un rol que ya existe. Capturar ese código es un patrón de idempotencia más limpio que realizar una llamada previa para listar recursos cuando se trabaja con IAM a gran escala.

El script siguiente demuestra cómo:

  • Capturar el código de salida de la CLI con || true para evitar que set -e termine la ejecución.
  • Analizar el JSON del mensaje de error que AWS escribe en stderr mediante la sustitución de procesos.
  • Asociar una política solo si aún no está asociada.
#!/usr/bin/env bash
set -euo pipefail

ROLE_NAME="DevOpsDeployRole"
POLICY_ARN="arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"

TRUST_POLICY='{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "ec2.amazonaws.com" },
    "Action": "sts:AssumeRole"
  }]
}'

# Attempt creation; ignore EntityAlreadyExists
CREATE_OUTPUT=$(aws iam create-role \
  --role-name "$ROLE_NAME" \
  --assume-role-policy-document "$TRUST_POLICY" \
  --output json 2>&1) || {
  if echo "$CREATE_OUTPUT" | grep -q 'EntityAlreadyExists'; then
    echo "[SKIP] Role $ROLE_NAME already exists."
  else
    echo "[ERROR] Unexpected error: $CREATE_OUTPUT" >&2
    exit 1
  fi
}

# Attach policy (attach-role-policy is idempotent by default)
aws iam attach-role-policy \
  --role-name "$ROLE_NAME" \
  --policy-arn "$POLICY_ARN"

echo "[OK] Role $ROLE_NAME ready with policy $POLICY_ARN."

jq avanzado: transformaciones, mapas y toentries

Las respuestas de infraestructura reales contienen decenas de campos. Las transformaciones de jq permiten remodelar la salida para herramientas posteriores, registros o archivos de configuración.

Patrones avanzados esenciales:

  • map(select(...)): filtra una matriz sin perder el contenedor de la matriz.
  • to_entries | map(select(.value != null)): elimina los campos nulos antes de escribir en una configuración.
  • [.[] | {id: .InstanceId, ip: .PrivateIpAddress}]: proyecta los datos a una nueva estructura.
  • @csv, @tsv, @base64: convertidores de formato integrados.

El fragmento siguiente extrae todas las instancias en ejecución y escribe un archivo de inventario TSV.

#!/usr/bin/env bash
set -euo pipefail

OUTPUT_FILE="/tmp/ec2_inventory.tsv"

aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --output json \
| jq -r '
  ["InstanceId", "Name", "PrivateIp", "Type", "AZ"],
  [
    .Reservations[].Instances[] | [
      .InstanceId,
      (.Tags // [] | map(select(.Key == "Name")) | .[0].Value // "(none)"),
      (.PrivateIpAddress // "N/A"),
      .InstanceType,
      .Placement.AvailabilityZone
    ]
  ][]
| @tsv' > "$OUTPUT_FILE"

echo "Inventory written to $OUTPUT_FILE:"
column -t "$OUTPUT_FILE"

Esperar operaciones asíncronas: sondeo con jq

Las operaciones en la nube son asíncronas. Crear una instancia EC2 devuelve el control inmediatamente con el estado pending. Los scripts fiables deben sondear hasta alcanzar el estado deseado antes de continuar.

El patrón siguiente usa un bucle until con retroceso exponencial. AWS CLI también proporciona subcomandos wait (por ejemplo, aws ec2 wait instance-running), pero el sondeo implementado manualmente ofrece un control personalizado del tiempo de espera y registros más detallados.

#!/usr/bin/env bash
set -euo pipefail

INSTANCE_ID="i-0abcdef1234567890"
MAX_WAIT=300   # seconds
INTERVAL=10
ELAPSED=0

echo "Waiting for instance $INSTANCE_ID to reach 'running' state..."

while true; do
  STATE=$(aws ec2 describe-instances \
    --instance-ids "$INSTANCE_ID" \
    --output json \
  | jq -r '.Reservations[0].Instances[0].State.Name')

  echo "  [$(date +%T)] state = $STATE"

  [[ "$STATE" == "running" ]] && break

  if [[ "$STATE" == "terminated" || "$STATE" == "shutting-down" ]]; then
    echo "[FATAL] Instance entered terminal state: $STATE" >&2
    exit 1
  fi

  if (( ELAPSED >= MAX_WAIT )); then
    echo "[TIMEOUT] Instance did not reach 'running' after ${MAX_WAIT}s." >&2
    exit 1
  fi

  sleep "$INTERVAL"
  (( ELAPSED += INTERVAL ))
done

echo "[OK] Instance $INSTANCE_ID is running."

Patrón multicloud: equivalentes de GCP y Azure

El patrón idempotente de leer y actuar se transfiere directamente a otras CLI de la nube. Tanto gcloud como az devuelven JSON y admiten filtros:

  • GCP: gcloud ... --format='json': canalice la salida a jq exactamente igual que con AWS. Use gcloud ... --quiet para suprimir las solicitudes de confirmación en los scripts.
  • Azure: az ... --output json: el patrón es idéntico. az group exists devuelve una cadena booleana simple (true/false), por lo que en casos sencillos no es necesario usar jq.

El fragmento muestra, uno junto al otro, la creación idempotente de un grupo de recursos en Azure y de un bucket de GCS en GCP mediante el mismo patrón de protección.

#!/usr/bin/env bash
set -euo pipefail

# --- Azure: idempotent resource group ---
RG="devops-rg"
LOCATION="westeurope"

if [[ $(az group exists --name "$RG") == "true" ]]; then
  echo "[SKIP] Azure resource group $RG already exists."
else
  echo "[CREATE] Creating Azure resource group $RG..."
  az group create --name "$RG" --location "$LOCATION" --output json \
    | jq '{name: .name, location: .location, provisioningState: .properties.provisioningState}'
fi

# --- GCP: idempotent GCS bucket ---
GCS_BUCKET="gs://devops-artifacts-prod"
PROJECT="my-gcp-project"

if gcloud storage buckets describe "$GCS_BUCKET" \
     --project="$PROJECT" --format='value(name)' &>/dev/null; then
  echo "[SKIP] GCS bucket $GCS_BUCKET already exists."
else
  echo "[CREATE] Creating GCS bucket $GCS_BUCKET..."
  gcloud storage buckets create "$GCS_BUCKET" \
    --project="$PROJECT" \
    --location=EU \
    --uniform-bucket-level-access
fi

Desmantelamiento: destrucción segura de recursos

Los scripts de destrucción son tan importantes como los de creación. Un desmantelamiento seguro:

  • Enumera los recursos antes de eliminar nada y muestra un resumen para que una persona lo revise.
  • Acepta una opción --dry-run para que los operadores puedan confirmar el plan sin ejecutarlo.
  • Elimina los recursos en el orden correcto de dependencias (por ejemplo, termina las instancias antes de eliminar los grupos de seguridad).

El fragmento siguiente termina todas las instancias EC2 etiquetadas como Env=staging con una protección de ejecución simulada.

#!/usr/bin/env bash
set -euo pipefail

DRY_RUN="${1:-}"

echo "Finding staging EC2 instances..."

INSTANCE_IDS=$(aws ec2 describe-instances \
  --filters \
    "Name=tag:Env,Values=staging" \
    "Name=instance-state-name,Values=running,stopped" \
  --output json \
| jq -r '[.Reservations[].Instances[].InstanceId] | @sh')

if [[ -z "$INSTANCE_IDS" ]]; then
  echo "[INFO] No staging instances found. Nothing to do."
  exit 0
fi

echo "Instances to terminate: $INSTANCE_IDS"

if [[ "$DRY_RUN" == "--dry-run" ]]; then
  echo "[DRY-RUN] No changes made."
  exit 0
fi

read -rp "Terminate these instances? [yes/N]: " CONFIRM
[[ "$CONFIRM" != "yes" ]] && { echo "Aborted."; exit 0; }

# shellcheck disable=SC2086
aws ec2 terminate-instances --instance-ids $INSTANCE_IDS --output json \
| jq '.TerminatingInstances[] | {id: .InstanceId, state: .CurrentState.Name}'

echo "[DONE] Termination initiated."

De extremo a extremo: script de inicialización de infraestructura idempotente

Con esto reunimos todo: un script de inicialización de nivel de producción coordina varios recursos en el orden correcto, es completamente idempotente y genera registros estructurados que un sistema de CI puede analizar.

Prácticas clave demostradas:

  • Registro estructurado mediante una función auxiliar log() que antepone [INFO], [WARN] y [ERROR].
  • Archivo de estado — escriba los ID de los recursos creados en un archivo de estado JSON para que las ejecuciones posteriores y los scripts de desmontaje compartan las mismas referencias.
  • Captura de errores — trap captura las salidas inesperadas e informa del número de línea que ha fallado.
#!/usr/bin/env bash
set -euo pipefail

STATE_FILE="/tmp/infra_state.json"
REGION="eu-west-1"
BUCKET="devops-bootstrap-$(date +%Y%m)"
ROLE="BootstrapRole"

log() { echo "[$(date -u +%T)] [$1] ${*:2}"; }
trap 'log ERROR "Script failed at line $LINENO"' ERR

# Initialize state
[[ -f "$STATE_FILE" ]] || echo '{}' > "$STATE_FILE"

# --- Step 1: S3 bucket ---
EXISTING_BUCKETS=$(aws s3api list-buckets --query 'Buckets[].Name' --output json)
if echo "$EXISTING_BUCKETS" | jq -e --arg b "$BUCKET" 'index($b) != null' > /dev/null; then
  log INFO "Bucket $BUCKET exists — skipping."
else
  aws s3api create-bucket --bucket "$BUCKET" --region "$REGION" \
    --create-bucket-configuration LocationConstraint="$REGION" > /dev/null
  log INFO "Bucket $BUCKET created."
fi

# Update state file
jq --arg b "$BUCKET" '.bucket = $b' "$STATE_FILE" > /tmp/_state_tmp && mv /tmp/_state_tmp "$STATE_FILE"

# --- Step 2: IAM role ---
if aws iam get-role --role-name "$ROLE" &>/dev/null; then
  log INFO "Role $ROLE exists — skipping."
else
  aws iam create-role --role-name "$ROLE" \
    --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]}' \
    --output json | jq '{RoleName: .Role.RoleName, Arn: .Role.Arn}'
  log INFO "Role $ROLE created."
fi

jq --arg r "$ROLE" '.role = $r' "$STATE_FILE" > /tmp/_state_tmp && mv /tmp/_state_tmp "$STATE_FILE"

log INFO "Bootstrap complete. State: $(cat "$STATE_FILE" | jq -c .)"

Comprobación de conocimientos: protección de idempotencia con jq

Compruebe su comprensión de los scripts idempotentes para la nube con jq.

Repaso: automatización de recursos en la nube mediante la CLI y jq

En esta lección se ha cubierto el ciclo de vida completo de la automatización idempotente de la nube mediante scripts de shell, CLI de servicios cloud y jq.

Principios fundamentales:

  • Leer antes de escribir — consulte siempre primero el estado existente y actúe únicamente sobre las diferencias.
  • jq -e para las comprobaciones — utilice el modo basado en el código de salida para dirigir las ramas de if a partir de las respuestas JSON.
  • Semántica de los códigos de error — capture los códigos de error específicos del proveedor, como EntityAlreadyExists, en lugar de enumerar previamente los recursos cuando resulte más eficiente.
  • Consultar periódicamente el estado asíncrono — utilice bucles until con tiempos de espera; nunca suponga que un recurso está listo inmediatamente después de crearlo.
  • Archivos de estado — escriba los ID de los recursos en un archivo JSON compartido para que cada fase del script y el desmontaje compartan las mismas referencias.
  • Opciones de simulación — admita siempre --dry-run para que el operador pueda revisar las acciones de forma segura antes de ejecutar acciones destructivas.

Estos patrones, combinados con un encabezado estricto set -euo pipefail y una captura ERR, forman los cimientos de los scripts de infraestructura de nivel de producción en el nivel C2.

Preguntas frecuentes

¿La lección «Scripting de recursos en la nube mediante CLI y jq» es gratis?

Sí — el texto completo de «Scripting de recursos en la nube mediante CLI y jq» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Linux Command Line & Bash Scripting Mastery, actualiza a CoddyKit PRO. El curso de Linux Command Line & Bash Scripting Mastery incluye 4 lecciones en total.

¿Qué aprenderé en «Scripting de recursos en la nube mediante CLI y jq»?

Administre de forma idempotente las CLI de proveedores cloud y analice respuestas JSON para aprovisionar y eliminar recursos. Practicas Linux Command Line & Bash Scripting Mastery con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Linux Command Line & Bash Scripting Mastery?

No se requiere experiencia previa. Linux Command Line & Bash Scripting Mastery en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.

¿Cuánto tiempo toma la lección «Scripting de recursos en la nube mediante CLI y jq»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Linux Command Line & Bash Scripting Mastery?

Sí. Cada lección de Linux Command Line & Bash Scripting Mastery incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Creación de Dockerfiles compactos y entrypoints de shell
  2. Creación de plantillas de configuración con envsubst y heredocs
  3. Scripting de recursos en la nube mediante CLI y jq
  4. Sondas de estado, puertas de disponibilidad y bucles de espera
← Volver a Linux Command Line & Bash Scripting Mastery