Dijous, gener 15, 2026
SCRIPTING

Automatitzant la infraestructura amb APIs REST: exemples pràctics amb curl, jq i requests a Python

A l’administració de sistemes, les APIs REST són ja part del dia a dia. Moltes plataformes de virtualització, balancejadors, firewalls i serveis al núvol exposen funcions completes via API molt abans d’oferir interfícies gràfiques decents. Si es vol automatitzar de veritat —i evitar dependre del que el panell web permeti— cal treballar directament amb aquestes APIs.

Entendre primer l’API abans d’escriure codi

Abans d’escriure la primera línia a Python o preparar un curl, cal invertir temps a entendre com està construïda l’API. Això implica:

  • Autenticació: saber si és Basic Auth, Bearer Token, API Key en capçalera o en URL, OAuth, etc. No totes les llibreries i eines gestionen igual aquests esquemes. Per exemple, curl gestiona Basic Auth amb -o usuari:clau sense problemes, però amb Bearer Token necessites -H “Authorization: Bearer TOKEN”.
  • Formateig de dades: encara que sigui “REST” i retorni JSON, no totes fan servir el mateix estil de payloads ni de respostes. Hi ha APIs que exigeixen Content-Type: application/json i d’altres accepten application/x-www-form-urlencoded. Confondre això dona errors 400 que semblen inexplicables si no es revisa la documentació.
  • Paginació: un punt on molts scripts fallen. Hi ha APIs que retornen només un subconjunt de resultats i esperen que el client vagi demanant pàgina per pàgina (?page=2, ?offset=100&limit=100). No assumir que tot ve en una sola resposta.
  • Errors “normals”: algunes APIs responen amb HTTP 200 encara que hi hagi un error en el payload, incloent-hi un camp estatus: failed al JSON. D’altres en retornen 500 per qualsevol cosa, fins i tot errors del client.

La prova inicial s’hauria de fer sempre amb curl en mode verbós per veure exactament què s’envia i què es rep:

curl -v -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -X GET "https://api.exemple.com/v1/servers"

El mode -v mostra capçaleres, codis i possibles redireccions. Això estalvia molt de temps abans de passar a una cosa més elaborada.

curl i jq: parella útil per a exploració i scripts ràpids

Per a interaccions ràpides, curl és suficient, però gairebé sempre es vol filtrar o formatejar la sortida. Aquí és on jq entra com a eina imprescindible.

Per exemple, llistar només els noms de màquines virtuals d’una API de virtualització:

curl -s -H "Authorization: Bearer $TOKEN" \
     "https://api.exemple.com/v1/vms" | \
jq -r '.vms[] | .name'

Detalls pràctics:

  • -s a curl per silenciar la barra de progrés i que la sortida sigui neta.
  • -r a jq per a sortida sense cometes. Evita haver de fer set o tr després.
  • Filtrat amb expressions simples de jq evita recórrer l’estructura JSON a mà.

A APIs amb paginació, encadenar peticions i concatenar resultats es pot fer amb un bucle:

page=1
while true; do
  data=$(curl -s -H "Authorization: Bearer $TOKEN" \
              "https://api.exemple.com/v1/vms?page=$page")
  count=$(echo "$data" | jq '.vms | length')
  [ "$count" -eq 0 ] && break
  echo "$data" | jq -r '.vms[] | .name'
  page=$((page+1))
done

Aquest patró es repeteix molt i convé tenir-ho clar. L’error comú és no trencar el bucle quan l’API retorna una pàgina buida, generant trucades innecessàries o infinites.

Controlant capçaleres i formats

Un dels problemes més repetits en integrar APIs és la gestió de capçaleres HTTP. A curl, mai confiar que l’ API endivini el format:

-H "Accept: application/json" \
-H "Content-Type: application/json"

Tot i que moltes APIs ignoren Accept, posar-lo explícitament ajuda a detectar errors quan es retorna HTML o text pla per una fallada d’autenticació.

A Python amb requests, és igual d’important definir-les bé:

import requests
 
headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Accept": "application/json",
    "Content-Type": "application/json"
}
 
r = requests.get("https://api.exemple.com/v1/vms", headers=headers)

Evitar concatenar la URL a mà si la base canvia o si l’API fa servir versions (/v1/, /v2/). Un patró segur és definir la BASE_URL i construir les rutes relatives.

Validació i gestió d’ errors a Python

L’error típic a scripts d’integració és assumir que requests.get() retorna sempre un JSON vàlid.   No sempre és així.

Un exemple defensiu:

try:
    r = requests.get(url, headers=headers, timeout=10)
    r.raise_for_status()
    data = r.json()
except requests.exceptions.HTTPError as e:
    print(f"Error HTTP: {e} ({r.status_code})")
except requests.exceptions.Timeout:
    print("Timeout a la petició")
except ValueError:
    print("La resposta no és un JSON vàlid")

Detalls pràctics:

  • timeout: sense ell, un problema de xarxa pot deixar l’script penjat indefinidament.
  • raise_for_status(): força a tractar codis 4xx/5xx com a excepcions.
  • ValueError en json(): algunes APIs retornen HTML en errors, el que trenca .json().

Autenticació i renovació de tokens

A APIs amb OAuth2 o tokens temporals, automatitzar l’obtenció i renovació és essencial. Un patró comú és guardar el token en un arxiu temporal i refrescar-lo si expira:

if [ ! -f /tmp/token.txt ] || [ $(find /tmp/token.txt -mmin +50) ]; then
  TOKEN=$(curl -s -X POST \
    -d "client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET" \
    "https://api.exemple.com/oauth/token" | jq -r '.access_token')
  echo "$TOKEN" > /tmp/token.txt
else
  TOKEN=$(cat /tmp/token.txt)
fi

Això evita demanar un token a cada execució i no sobrecarrega l’endpoint d’autenticació.

Seguretat a scripts i credencials

Punts que poden passar-se per alt:

  • No hardcodejar claus: encara que sigui un script intern, qualsevol còpia o backup pot exposar-les.
  • Variables d’entorn: a Bash i Python, llegir el token des de os.environ és més segur que guardar-lo a l’script.
  • Logs i errors: evitar imprimir tokens a consola o logs. Un print(response.text) en debug pot deixar credencials exposades.

Integracions idempotents

Quan una API gestiona recursos com usuaris, màquines o regles de firewall, és important que l’script pugui executar-se diverses vegades sense causar inconsistències.

Exemple: crear un usuari només si no existeix:

users = requests.get(f"{BASE_URL}/users", headers=headers).json()
if not any(u['name'] == 'usuari1' for u in users):
    requests.post(f"{BASE_URL}/users", headers=headers, json={"name": "usuari1"})

A curl:

if ! curl -s -H "Authorization: Bearer $TOKEN" \
    "$BASE_URL/users" | jq -e '.[] | select(.name=="usuari1")' > /dev/null; then
  curl -s -X POST -H "Authorization: Bearer $TOKEN" \
       -H "Content-Type: application/json" \
       -d '{"name":"usuari1"}' "$BASE_URL/users"
fi

Evita crear duplicats o haver de netejar després.

Control de canvis i proves

Encara que sembli obvi, provar en entorns de staging no sempre és possible. Quan es treballa directament sobre producció:

  • Fer servir sempre mètodes GET abans de POST o DELETE per confirmar que el recurs objectiu és el correcte.
  • Limitar scripts a un subconjunt d’ IDs o noms abans de massificar canvis.
  • Guardar les respostes completes a un log per poder auditar després.

A Python:

import json
 
with open("log.json", "w") as f:
    json.dump(data, f, indent=2)

Exemple complet: inventari de VMs amb curl i Python

Amb curl + jq:

BASE_URL="https://api.exemple.com/v1"
TOKEN="..."
page=1
 
while true; do
  data=$(curl -s -H "Authorization: Bearer $TOKEN" \
              "$BASE_URL/vms?page=$page")
  count=$(echo "$data" | jq '.vms | length')
  [ "$count" -eq 0 ] && break
  echo "$data" | jq -r '.vms[] | "\(.id) \(.name) \(.status)"'
  page=$((page+1))
done

Amb Python requests:

import requests
 
BASE_URL = "https://api.exemple.com/v1"
TOKEN = "..."
headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Accept": "application/json"
}
 
page = 1
while True:
    r = requests.get(f"{BASE_URL}/vms", params={"page": page}, headers=headers, timeout=10)
    r.raise_for_status()
    vms = r.json().get("vms", [])
    if not vms:
        break
    for vm in vms:
        print(vm["id"], vm["name"], vm["status"])
    page += 1

Aquest patró és estable i fàcil d’ estendre per guardar resultats a CSV, JSON o bases de dades.

Lidiar amb throttling i límits de peticions

Moltes APIs imposen límits per minut o per hora. Ignorar això a scripts que fan bucles llargs pot provocar bloquejos temporals o errors 429 (Too Many Requests). Cal detectar-ho i actuar.

A curl no hi ha gestió automàtica, però es pot detectar el codi i pausar:

while true; do
  response=$(curl -s -w "%{http_code}" -o /tmp/resp.json \
              -H "Authorization: Bearer $TOKEN" "$BASE_URL/vms?page=$page")
  if [ "$response" -eq 429 ]; then
    sleep 30
    continue
  fi
  # processar /tmp/resp.json
done

A Python amb requests, un patró segur inclou reintents amb time.sleep() i backoff exponencial:

import time
import requests
 
MAX_RETRIES = 5
WAIT_BASE = 5  # segons
 
for attempt in range(MAX_RETRIES):
    r = requests.get(f"{BASE_URL}/vms", headers=headers)
    if r.status_code == 429:
        wait_time = WAIT_BASE * (2 ** attempt)
        time.sleep(wait_time)
        continue
    r.raise_for_status()
    break

Un error comú és fer reintents cecs davant de qualsevol error HTTP, cosa que en casos de 4xx que no són 429 només agreuja el problema.

Automatització recurrent: cron i make

Quan un script que interactua amb una API es torna rutinari (inventari diari, sincronització d’usuaris, backup de configuracions), cal integrar-lo bé a l’execució programada.

Amb cron:

  • Fer servir rutes absolutes per a binaris (/usr/bin/curl, /usr/bin/python3).
  • Redirigir sortida i errors a logs separats:0 3 * * * /usr/local/bin/mi_script.sh >> /var/log/mi_script.log 2>&1
  • Carregar variables d’ entorn amb tokens abans de cridar a l’ script:. /etc/mi_script.env && /usr/local/bin/mi_script.sh

Amb make:

Als entorns on es combinen diversos scripts i processos, Makefile permet definir dependències i ordre d’ execució:

TOKEN_FILE := /tmp/token.txt
BASE_URL := https://api.exemple.com/v1
 
token:
        curl -s -X POST \
          -d "client_id=$(CLIENT_ID)&client_secret=$(CLIENT_SECRET)" \
          "$(BASE_URL)/oauth/token" | jq -r '.access_token' > $(TOKEN_FILE)
 
inventory: token
        @TOKEN=$$(cat $(TOKEN_FILE)) && \
        curl -s -H "Authorization: Bearer $$TOKEN" "$(BASE_URL)/vms"

Això evita repetir autenticació a cada target i manté un ordre clar.

Control transaccional en automatitzacions crítiques

Quan l’API modifica recursos crítics (per exemple, regles de firewall o eliminació d’instàncies), cal garantir que no es quedin canvis a mitges.

Patró de “confirmació doble”:

  • Executar un GET i guardar l’ estat actual.
  • Fer el canvi (POST, PUT o DELETE).
  • Confirmar amb un altre GET que el canvi s’ha aplicat.
  • Si falla, revertir fent servir les dades guardades.

Amb Python:

before = requests.get(f"{BASE_URL}/rules", headers=headers).json()
 
# aplicar canvi
r = requests.delete(f"{BASE_URL}/rules/{rule_id}", headers=headers)
r.raise_for_status()
 
# verificar
after = requests.get(f"{BASE_URL}/rules", headers=headers).json()
 
if any(r["id"] == rule_id for r in after["rules"]):
    # revertir
    requests.post(f"{BASE_URL}/rules", headers=headers, json=before_rule_data)

Això no substitueix un veritable suport de transaccions a l’API, però és millor que assumir que tot va sortir bé.

Formats de sortida per a integracions posteriors

L’API pot retornar JSON, però l’script pot necessitar CSV, YAML o taules planes per integrar-se amb altres sistemes. Un enfocament pràctic és transformar sempre un format intermedi estàndard i des d’aquí exportar.

Amb jq per a CSV:

curl -s -H "Authorization: Bearer $TOKEN" "$BASE_URL/vms" | \
jq -r '.vms[] | [.id, .name, .status] | @csv' > inventari.csv

Amb Python:

import csv
 
vms = requests.get(f"{BASE_URL}/vms", headers=headers).json()["vms"]
with open("inventari.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["id", "name", "status"])
    for vm in vms:
        writer.writerow([vm["id"], vm["name"], vm["status"]])

Estandarditzar això facilita el consum des d’ eines de BI, scripts d’ auditoria o comparacions entre entorns.

Versionat i documentació de scripts

Els scripts que interactuen amb APIs han d’estar en control de versions des del primer dia, encara que siguin de “prova”. És freqüent que es converteixin en processos de producció i sense històric és difícil entendre què va canviar o per què va fallar alguna cosa.

  • Repositoris privats: Gitlab, Gitea o similars.
  • Documentar paràmetres: a Bash, un bloc usage(); a Python, docstrings i argpar-se.
  • Historial d’endpoints: si l’API canvia de /v1/ a /v2/, documentar la data i raó al commit, no en un xat intern que es perdrà.

Exemple final: script híbrid exploratori

Un patró que funciona bé per a tasques exploratòries és barrejar curl i jq a Bash per a proves ràpides, i passar a Python quan es necessita lògica més complexa.

#!/bin/bash
set -euo pipefail
 
BASE_URL="https://api.exemple.com/v1"
TOKEN=$(cat /tmp/token.txt)
 
# Pas 1: obtenir IDs de VMs apagades
ids=$(curl -s -H "Authorization: Bearer $TOKEN" "$BASE_URL/vms" | \
      jq -r '.vms[] | select(.status=="stopped") | .id')
 
# Pas 2: iniciar cada VM amb Python
for id in $ids; do
  python3 - <<EOF
import requests
BASE_URL="$BASE_URL"
TOKEN="$TOKEN"
headers={"Authorization": f"Bearer {TOKEN}"}
r = requests.post(f"{BASE_URL}/vms/{id}/start", headers=headers)
r.raise_for_status()
print(f"VM {id} iniciada")
EOF
done

Aquest tipus de scripts permeten prototipar ràpid i després separar en mòduls nets.

Conclusió

Amb aquestes pràctiques, la interacció amb APIs REST per automatitzar infraestructura passa de ser una col·lecció de trucades improvisades a un flux controlat, predictible i segur. Qui hagi treballat amb APIs de VMware, AWS, Fortinet o Kubernetes reconeixerà patrons comuns: autenticació que caduca, paginació poc documentada, respostes que diuen “OK” però no ho estan, i límits de peticions que apareixen just a la finestra de manteniment.

La diferència entre un script que “funciona” i un que pot mantenir-se en producció està en aquests detalls: gestió d’errors, control de format, idempotència, seguretat de credencials, i proves prèvies amb curl i jq abans de moure’l a codi més complex.

Deixa un comentari

L'adreça electrònica no es publicarà. Els camps necessaris estan marcats amb *