Black Friday en 15 días: ¿podrán pagar sus clientes? -25 % el primer año y puesta en marcha gratuita, hasta el 30 de noviembreVer la oferta →
Integración
Una entrada en un mapa, un terraform apply: el Ping, el Flow del proceso de compra y el objetivo de disponibilidad de un cliente quedan listos.
Obtener mi clave de APIContrato OpenAPI
Documento completo, sin autenticación, listo para su generador de clientes.
Usted mantiene varios sitios de clientes. Cada sitio es una entrada en un archivo de variables. Para esa entrada, Terraform crea un Ping de la página de inicio, un Flow del proceso de compra hasta la página de pago y un objetivo de disponibilidad.
Con los dos clientes de abajo, el primer terraform apply crea siete recursos: tres por cliente y un webhook de alertas común. Un cliente más es una entrada más y tres recursos más. Nadie hace clic en la consola.
El ejemplo completo, con sus validaciones, está en el repositorio del provider, carpeta examples/agency-client. Esta página sigue su código en versión corta. Los dominios example.com están reservados para la documentación: sustitúyalos por los sitios que tiene derecho a supervisar.
terraform.tfvars
webhook_url = "https://hooks.example.com/pathly"
clients = {
bakery = {
name = "Example Bakery"
home_url = "https://www.example.com/"
product_url = "https://www.example.com/products/sourdough"
checkout_url = "https://www.example.com/checkout"
severity = "critical"
}
bookshop = {
name = "Example Bookshop"
home_url = "https://shop.example.com/"
product_url = "https://shop.example.com/books/atlas"
checkout_url = "https://shop.example.com/checkout"
objective_pct = 99.5
}
}Aplicar
# PATHLY_API_TOKEN viene de su gestor de secretos, nunca de un archivo
terraform init
terraform plan
terraform apply
# Plan: 7 to add, 0 to change, 0 to destroy.La variable clients es un mapa de objetos. Su clave es un identificador corto y estable: forma parte de la dirección de cada recurso y de la etiqueta del cliente. Cada recurso recorre ese mapa con for_each.
El Ping es un pathly_scenario con una url: comprueba que la página de inicio responde. El Flow es un pathly_scenario de tipo browser, con sus acciones en steps. Un navegador real abre la página de producto, añade el producto al carrito y abre la página de pago. El primer paso siempre es un goto.
El Flow se detiene cuando aparece la página de pago. Ningún paso hace clic en un botón de pago, y solo escribe valores de prueba. Los selectores y el texto esperado dependen de cada sitio: compruébelos en el inspector del navegador antes de aplicar.
El objetivo de disponibilidad es un pathly_sla_target ligado al Ping. objective_pct fija el objetivo sobre una ventana móvil de window_days días. warn_at_budget_ratio avisa cuando se ha consumido el 80 % del presupuesto de error, mientras queda margen para actuar.
La API nunca devuelve los pasos de un Flow, solo su huella (scenario_fingerprint). El código es, por tanto, la única copia legible del recorrido: guárdelo en el repositorio, revisado como todo lo demás.
variables.tf
terraform {
required_providers {
pathly = {
source = "pathlyhq/pathly"
version = "~> 0.1"
}
}
}
provider "pathly" {}
variable "clients" {
type = map(object({
name = string
home_url = string
product_url = string
checkout_url = string
severity = optional(string, "major")
objective_pct = optional(number, 99.9)
}))
}Ping
resource "pathly_scenario" "home" {
for_each = var.clients
name = "${each.value.name} - home page"
url = each.value.home_url
interval_sec = 300
expected_status = 200
severity = each.value.severity
folder = each.value.name
tags = ["terraform", "client:${each.key}"]
}Flow
resource "pathly_scenario" "checkout" {
for_each = var.clients
name = "${each.value.name} - checkout up to payment"
type = "browser"
interval_sec = 3600
severity = each.value.severity
folder = each.value.name
tags = ["terraform", "client:${each.key}", "checkout"]
steps = [
{
op = "goto"
url = each.value.product_url
},
{
op = "click"
selector = "button[name=add-to-cart]"
},
{
op = "goto"
url = each.value.checkout_url
},
{
op = "assert_text"
text = "Payment"
},
]
}Objetivo de disponibilidad
resource "pathly_sla_target" "home" {
for_each = var.clients
scenario_id = pathly_scenario.home[each.key].id
name = "${each.value.name} - ${each.value.objective_pct} %"
objective_pct = each.value.objective_pct
window_days = 30
exclude_maintenance = true
warn_at_budget_ratio = 0.8
}Un solo pathly_webhook recibe los fallos (run.failed) y las recuperaciones (run.recovered) de todos los clientes. Cada envío va firmado con HMAC mediante un secreto que se devuelve una sola vez, al crearlo. Verifique esa firma en el lado receptor; de lo contrario, cualquiera puede falsificar una alerta.
El envío nombra el escenario por su identificador (monitorId) y por su nombre (monitorName), no por sus etiquetas. Para encontrar al cliente, su receptor compara el identificador con la salida scenario_ids, o lee el principio del nombre: cada nombre empieza por el del cliente.
La etiqueta client:<clave> y la carpeta sirven para ordenar. La carpeta agrupa los escenarios de un cliente en la consola. La etiqueta filtra el inventario, por ejemplo con filter_tag en la fuente de datos pathly_scenarios.
Los webhooks salientes no forman parte de todos los planes. En un plan que no los incluye, la API rechaza la creación del webhook.
pathly_webhook
variable "webhook_url" {
type = string
}
resource "pathly_webhook" "alerts" {
url = var.webhook_url
events = ["run.failed", "run.recovered"]
}scenario_ids
output "scenario_ids" {
value = {
for key, client in var.clients : key => {
home = pathly_scenario.home[key].id
checkout = pathly_scenario.checkout[key].id
}
}
}Los escenarios de un solo cliente
data "pathly_scenarios" "bakery" {
filter_tag = "client:bakery"
}La clave de API vive en la variable de entorno PATHLY_API_TOKEN, que proporciona su gestor de secretos o los secretos de la CI. El bloque provider queda vacío. Una clave escrita en un archivo .tf acaba en el repositorio, y una clave leída de una variable de Terraform acaba en claro en el estado.
Dé a la clave los alcances necesarios y nada más: scenarios:write, alerting:write y sla:write para aplicar. Añada maintenance:write solo si gestiona ventanas de mantenimiento en el mismo código. Escribir implica leer. Para una tarea que solo ejecuta terraform plan, basta una segunda clave con los alcances :read.
Si el proceso de compra de un cliente exige iniciar sesión, la cuenta de prueba pasa por una variable marcada como sensitive, separada del mapa clients: Terraform no permite que un mapa sensible dirija un for_each. Cree esa cuenta para la supervisión, sin medio de pago guardado. No use nunca la cuenta de un comprador real.
El estado de Terraform contiene el secreto del webhook, porque la API solo lo devuelve al crearlo. También contiene las contraseñas de prueba que escribe el Flow. Guárdelo en un backend remoto, cifrado en reposo y con acceso restringido. No lo suba al repositorio ni publique el secreto en una salida no sensible.
Alcances de la clave
# terraform apply scenarios:write alerting:write sla:write
# maintenance:write (maintenance windows only)
# terraform plan scenarios:read alerting:read sla:readCuentas de prueba, separadas del mapa clients
variable "test_logins" {
type = map(object({
username = string
password = string
}))
sensitive = true
default = {}
}Pasos de inicio de sesión del Flow
steps = [
{
op = "goto"
url = "https://shop.example.com/login"
},
{
op = "fill"
selector = "input[name=email]"
value = var.test_logins[each.key].username
},
{
op = "fill"
selector = "input[name=password]"
value = var.test_logins[each.key].password
},
{
op = "click"
selector = "button[type=submit]"
},
# ...goto product page, add to cart, goto checkout, assert_text
]Un compañero con prisa crea un escenario a mano en la consola. Se escapa de la revisión y nadie sabe que existe. La fuente de datos pathly_scenarios vuelve a leer todo el inventario de la organización. La salida scenarios_outside_terraform enumera lo que Pathly contiene y el código ignora.
Una lista vacía significa que el código lo describe todo. Un escenario que aparece en ella se trata de dos maneras: lo importa al código o lo elimina. Si otros repositorios de Terraform gestionan parte de la organización, reduzca el inventario con filter_tag o filter_folder.
Programe un terraform plan cada noche en la CI, con la opción -detailed-exitcode. El código de salida 0 indica que nada se ha movido. El código 2 indica que el plan no está vacío: un atributo descrito en el código cambió en la consola, o cambió la lista de escenarios fuera del código. La tarea falla y le avisa.
Esta tarea nocturna solo lee: dele una clave con los alcances :read. También necesita acceso al backend del estado y las mismas variables que la tarea que aplica el código.
Dos límites que conviene conocer. Un atributo ausente del código conserva el valor fijado en la consola, sin aparecer en el plan. Los pasos de un Flow no se pueden releer por la API. Escriba, por tanto, en el código cada atributo que quiera mantener.
scenarios_outside_terraform
data "pathly_scenarios" "all" {
depends_on = [pathly_scenario.home, pathly_scenario.checkout]
}
output "scenarios_outside_terraform" {
value = [
for s in data.pathly_scenarios.all.scenarios : {
id = s.id
name = s.name
folder = s.folder
}
if !contains(
concat(
[for home in pathly_scenario.home : home.id],
[for checkout in pathly_scenario.checkout : checkout.id],
),
s.id,
)
]
}GitHub Actions — plan nocturno
name: pathly-drift
on:
schedule:
- cron: "0 3 * * *"
jobs:
plan:
runs-on: ubuntu-latest
env:
PATHLY_API_TOKEN: ${{ secrets.PATHLY_API_TOKEN_READ }}
TF_VAR_webhook_url: ${{ secrets.PATHLY_WEBHOOK_URL }}
TF_VAR_test_logins: ${{ secrets.PATHLY_TEST_LOGINS }}
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_wrapper: false
- run: terraform init -input=false
- run: terraform plan -input=false -lock=false -detailed-exitcodeAñadir un cliente: una entrada más en el mapa, una pull request, un apply. El plan anuncia tres recursos por crear y no toca nada más. Ese es el control que hay que hacer en la revisión.
Modificar un cliente: cambie su entrada. Una nueva URL, una severidad o un objetivo se actualizan en el sitio. No cambie el nombre de la clave sin precaución: forma parte de la dirección de los recursos. Terraform destruiría los escenarios y los volvería a crear, sin su historial de ejecuciones. Si debe renombrarla, declare un bloque moved por recurso.
Retirar un cliente: elimine su entrada. El plan anuncia tres recursos por destruir. Los escenarios destruidos se llevan su historial de ejecuciones: exporte antes lo que el cliente deba conservar.
Importar un sitio creado a mano: añada primero su entrada al mapa y luego importe el escenario existente en la dirección de esa entrada, con terraform import pathly_scenario.<nombre> <UUID del escenario>. El identificador es un UUID, visible en la URL del escenario en la consola. Sin la importación, Terraform crearía un segundo escenario junto al primero.
Tras importar un Flow, el siguiente apply reescribe los pasos con los del código, ya que la API no los devuelve. Un objetivo de disponibilidad ya definido en la consola para ese escenario se adopta en el primer apply, sin error de conflicto.
Un cliente más
clients = {
# ...
cycles = {
name = "Example Cycles"
home_url = "https://store.example.org/"
product_url = "https://store.example.org/bikes/city"
checkout_url = "https://store.example.org/checkout"
}
}Importar desde la línea de comandos
terraform import 'pathly_scenario.home["cycles"]' 00000000-0000-4000-8000-000000000000
terraform import 'pathly_scenario.checkout["cycles"]' 11111111-1111-4111-8111-111111111111
terraform planO un bloque import, revisado con lo demás
import {
to = pathly_scenario.home["cycles"]
id = "00000000-0000-4000-8000-000000000000"
}