Saltar al contenido

Webhooks ​

Un webhook avisa a su sistema de que algo cambió en un repositorio, para que no tenga que consultar periódicamente. El worker de Arkvory envía un POST HTTP a su URL por cada evento del feed de cambios del repositorio. Úselo para iniciar un despliegue cuando se publica una compilación, o para actualizar una caché cuando una ruta recibe una versión nueva.

El administrador define las suscripciones en un archivo. Todavía no hay API ni página de la consola para ellas. También puede leer el feed usted mismo con GET /api/v1/repositories/<repository>/changes.

Cómo funciona la entrega ​

  • Una suscripción sigue un repositorio y envía a una URL.
  • Los eventos llegan en orden, de uno en uno. El siguiente espera hasta que el receptor responda con un estado 2xx.
  • La entrega ocurre al menos una vez. Tras un fallo o una respuesta perdida, el mismo evento puede llegar otra vez. Cada evento tiene un id estable: deduplique por él.
  • Una suscripción nueva recibe solo eventos nuevos. Los anteriores a su creación no se envían.
  • Un receptor caído retrasa solo su suscripción. El evento espera. Arkvory reintenta a los 12 segundos, duplica la pausa hasta una hora y envía los eventos en orden cuando el receptor vuelve a responder. Las subidas y descargas nunca esperan a un webhook.

Configurar una suscripción ​

Necesita acceso a los archivos del servidor y permiso para reiniciar el worker. Consulte Configuración.

  1. Cree un archivo con el secreto de firma: al menos 16 caracteres imprimibles aleatorios, por ejemplo /root/ci.secret. El receptor necesita el mismo secreto.
  2. Ejecute configure con la suscripción. El comando valida lo que usted indica, copia el secreto en config/webhooks, escribe el archivo de suscripciones y el ajuste, reinicia los servicios y restaura la configuración anterior si no quedan listos. No contacta con el receptor.
bash
sudo arkvory configure --root /opt/proanima-arkvory \
  --webhook ci --webhook-repository releases \
  --webhook-url https://ci.example.com/hooks/arkvory \
  --webhook-secret-file /root/ci.secret \
  --webhook-actions artifact.publish
  1. Busque webhook.started en el registro del worker, publique un archivo y observe su receptor.

El comando escribe config/webhooks/webhooks.json, que también puede escribir a mano e indicar en ARKVORY_WEBHOOKS_FILE. Para cambiar una suscripción, ejecute el comando de nuevo con el mismo nombre. --webhook-detach ci la elimina: se borran sus archivos y se olvida su posición. Una llamada cambia una suscripción y no se puede mezclar con otras opciones de configure.

El archivo tiene estos campos:

json
{
  "webhooks": [
    {
      "id": "ci",
      "repository": "releases",
      "url": "https://ci.example.com/hooks/arkvory",
      "secretFile": "/opt/proanima-arkvory/config/webhooks/ci.secret",
      "actions": ["artifact.publish"]
    }
  ]
}
CampoSignificado
idNombre de la suscripción: de 1 a 64 caracteres a-z, 0-9, _ y -. Su progreso se guarda con este nombre.
repositoryEl repositorio cuyo feed se envía.
urlEl receptor. HTTPS, sin nombre de usuario, query ni fragmento, hasta 2048 caracteres. HTTP sin cifrar solo se permite para localhost, 127.0.0.1 y [::1].
secretFileRuta absoluta del archivo con el secreto de firma.
nextSecretFileSegundo secreto opcional para una rotación.
actionsLista opcional de acciones del feed que se envían, por ejemplo artifact.publish, artifact.delete, asset.replace, stage.added y package.register. Sin ella se envían todas las acciones.

Un archivo incorrecto detiene el worker al arrancar con worker.unavailable. Se permiten hasta 16 suscripciones. En una instalación con Compose el comando también monta config/webhooks en el contenedor del worker.

La petición ​

EncabezadoValor
Content-Typeapplication/json
X-Arkvory-DeliveryEl id del evento. Es el mismo en cada repetición de un evento.
X-Arkvory-EventLa acción del feed, por ejemplo artifact.publish.
X-Arkvory-TimestampHora Unix en segundos en que se firmó la petición.
X-Arkvory-Signaturesha256=<hex>. Durante una rotación hay dos valores separados por una coma.

El cuerpo es JSON:

json
{
  "id": "releases:128",
  "repository": "releases",
  "sequence": "128",
  "action": "artifact.publish",
  "artifactId": "00000000-0000-4000-8000-000000000001",
  "detail": null
}

sequence es la posición en el feed, una cadena decimal. detail es la ruta del archivo o la etapa en las acciones que la tienen. El cuerpo no incluye autor, contenido de archivos ni metadatos: use artifactId para leer el estado actual con la API.

Responda con cualquier estado 2xx en menos de 10 segundos. Un estado 3xx (no se siguen las redirecciones), 4xx, 5xx, un error de conexión o un tiempo agotado cuenta como fallo. Arkvory lee como máximo 4 KiB de la respuesta y la ignora.

Verificar la firma ​

La firma es HMAC-SHA256 con su secreto sobre el texto <timestamp>.<body>, escrita como sha256= y el resumen hexadecimal. Compruébela antes de confiar en una petición:

  1. Lea el cuerpo sin procesar, antes de analizar el JSON.
  2. Rechace la petición si la marca de tiempo difiere de su reloj en más de 5 minutos.
  3. Calcule la firma y compárela con una función de tiempo constante. Si el encabezado tiene dos valores, acepte cualquiera.

Node.js:

js
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(secret, headers, rawBody) {
  const timestamp = headers['x-arkvory-timestamp'];
  if (!/^\d{1,12}$/.test(timestamp ?? '')) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = Buffer.from(
    'sha256=' + createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex'),
  );
  return String(headers['x-arkvory-signature'] ?? '')
    .split(',')
    .some((given) => {
      const actual = Buffer.from(given.trim());
      return actual.length === expected.length && timingSafeEqual(actual, expected);
    });
}

Python:

python
import hashlib, hmac, time

def verify(secret: bytes, headers, raw_body: bytes) -> bool:
    timestamp = headers.get("X-Arkvory-Timestamp", "")
    if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
        return False
    digest = hmac.new(secret, timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    expected = "sha256=" + digest
    given = headers.get("X-Arkvory-Signature", "").split(",")
    return any(hmac.compare_digest(part.strip(), expected) for part in given)

Manejar entregas repetidas ​

  • Guarde el id de cada evento procesado e ignore una repetición.
  • Responda rápido: ponga el trabajo en cola y devuelva 204. Un receptor lento retrasa todos los eventos posteriores de su suscripción.
  • Una repetición no significa que la operación ocurriera dos veces. Trate el evento como una pista y lea el estado actual desde la API.

Rotar el secreto ​

  1. Ejecute el mismo comando con --webhook-secret-file para el secreto actual y --webhook-next-secret-file para el nuevo. Cada petición llevará dos firmas.
  2. Cambie el receptor al secreto nuevo. Un receptor que acepta cualquiera de las firmas sigue funcionando mientras tanto.
  3. Ejecute el comando de nuevo con el archivo del secreto nuevo en --webhook-secret-file y sin --webhook-next-secret-file.

Receptores privados y certificados ​

  • Arkvory rechaza receptores en direcciones loopback, privadas, link-local y de metadatos de la nube, y nombres que se resuelven a ellas. Así no se puede usar el servidor para llegar a servicios internos.
  • Para enviar a un receptor de su red, añada la red con --webhook-allow-private, por ejemplo --webhook-allow-private 10.20.0.0/16 (ARKVORY_WEBHOOKS_ALLOW_PRIVATE). Se mantiene hasta que se elimina la última suscripción.
  • Si el certificado del receptor proviene de su propia autoridad, añada un archivo PEM con esa autoridad mediante --webhook-ca-file (ARKVORY_WEBHOOKS_CA_FILE). El certificado siempre se verifica.

Supervisar webhooks ​

El worker escribe webhook.step_failed con un errorCode cuando falla una entrega, y webhook.recovered cuando vuelve a funcionar. Las métricas arkvory_webhook_failing y arkvory_webhook_last_success_timestamp_seconds y la alerta ArkvoryWebhookFailing se describen en Supervisión. webhook.stopped marca el final del ciclo de una suscripción: al detenerse o cuando el worker pierde la propiedad del almacenamiento. webhook.prune_failed significa que el worker no pudo olvidar las suscripciones eliminadas al arrancar; lo intenta de nuevo en el siguiente arranque. Los códigos unavailable e internal solo aparecen en el registro, no en arkvory_webhook_failing.

Una suscripción que no avanza ​

Arkvory nunca omite un evento: en eso se basa la fiabilidad de la entrega. Por eso, un receptor que rechaza una y otra vez un evento, por ejemplo con 400 porque no puede interpretarlo, detiene toda su suscripción. No se envía ningún evento posterior de esa suscripción; las demás suscripciones siguen funcionando. El worker escribe webhook.step_failed con http_4xx y reintenta con pausas de hasta una hora, arkvory_webhook_failing vale 1 y la alerta ArkvoryWebhookFailing salta a los 15 minutos.

  1. Corrija el receptor para que responda al evento con 2xx. Arkvory lo envía en el siguiente reintento, como mucho una hora después, y luego todos los eventos en espera, en orden. Ejecutar configure de nuevo con el mismo nombre y repositorio conserva la posición.
  2. Si el evento nunca podrá aceptarse, elimine la suscripción con --webhook-detach y añádala de nuevo. Empieza al final del feed: los eventos en espera no se envían. Léalos del feed de cambios después del último sequence que procesó su receptor.

Solución de problemas ​

errorCodeCausa y qué hacer
blockedLa dirección del receptor no está permitida (loopback, privada, link-local o de metadatos). Use una dirección pública o añada la red a ARKVORY_WEBHOOKS_ALLOW_PRIVATE.
timeoutSin respuesta en 10 segundos. Responda más rápido y ponga el trabajo en cola.
networkConexión rechazada, nombre no encontrado o conexión restablecida. Compruebe la URL, el DNS y el cortafuegos desde el servidor.
tlsEl certificado no es de confianza, caducó o tiene un nombre incorrecto. Corríjalo o defina ARKVORY_WEBHOOKS_CA_FILE.
redirectEl receptor respondió 3xx. No se siguen las redirecciones: use la URL final.
http_4xxEl receptor rechazó la petición. Revise su comprobación de firma, su ruta y su clave.
http_5xxEl receptor falló. Arkvory sigue reintentando, con hasta una hora entre intentos.
secretEl archivo de secreto no existe, no se puede leer o mide menos de 16 bytes.
unavailableEl worker no pudo leer el feed ni el estado de la suscripción desde la base de datos. No se envió nada; lo reintenta a los 5 segundos. Revise la base de datos.
internalUn error inesperado en el worker. No se envió nada; lo reintenta a los 5 segundos. Notifíquelo junto con el registro del worker.

¿No llega nada? Compruebe que webhook.started está en el registro del worker, que ARKVORY_WEBHOOKS_FILE está definido, el nombre del repositorio y el filtro actions. Una suscripción nueva solo envía eventos posteriores a su primer paso.

Arkvory · ProAnimaStudio