Black Friday tra 15 giorni: i Suoi clienti potranno pagare? -25 % il 1° anno e configurazione offerta, fino al 30 novembreVedi l’offerta →
Integrazione
Una voce in una mappa, un terraform apply: il Ping, il Flow del percorso d’acquisto e l’obiettivo di disponibilità di un cliente sono pronti.
Ottenere la mia chiave APIContratto OpenAPI
Documento completo, senza autenticazione, da passare al Suo generatore di client.
Lei gestisce più siti di clienti. Ogni sito è una voce in un file di variabili. Per quella voce Terraform crea un Ping della home page, un Flow del percorso d’acquisto fino alla pagina di pagamento e un obiettivo di disponibilità.
Con i due clienti qui sotto, il primo terraform apply crea sette risorse: tre per cliente e un webhook di avviso comune. Un cliente in più significa una voce in più e tre risorse in più. Nessuno fa clic nella console.
L’esempio completo, con le sue validazioni, si trova nel repository del provider, cartella examples/agency-client. Questa pagina ne segue il codice in versione breve. I domini example.com sono riservati alla documentazione: li sostituisca con i siti che ha il diritto di monitorare.
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
}
}Applicare
# PATHLY_API_TOKEN viene dal suo gestore di segreti, mai da un file
terraform init
terraform plan
terraform apply
# Plan: 7 to add, 0 to change, 0 to destroy.La variabile clients è una mappa di oggetti. La sua chiave è un identificatore breve e stabile: entra nell’indirizzo di ogni risorsa e nel tag del cliente. Ogni risorsa percorre questa mappa con for_each.
Il Ping è un pathly_scenario con una url: verifica che la home page risponda. Il Flow è un pathly_scenario di tipo browser, con le sue azioni in steps. Un browser reale apre la pagina del prodotto, aggiunge il prodotto al carrello e apre la pagina di pagamento. Il primo passo è sempre un goto.
Il Flow si ferma quando compare la pagina di pagamento. Nessun passo fa clic su un pulsante di pagamento, e inserisce solo valori di test. I selettori e il testo atteso dipendono da ogni sito: li verifichi nell’ispettore del browser prima di applicare.
L’obiettivo di disponibilità è un pathly_sla_target legato al Ping. objective_pct fissa l’obiettivo su una finestra mobile di window_days giorni. warn_at_budget_ratio avvisa quando l’80% del budget di errore è consumato, finché resta margine per agire.
L’API non restituisce mai i passi di un Flow, solo la loro impronta (scenario_fingerprint). Il codice resta quindi l’unica copia leggibile del percorso: lo conservi nel repository, revisionato come tutto il resto.
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"
},
]
}Obiettivo di disponibilità
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 riceve i fallimenti (run.failed) e i ripristini (run.recovered) di tutti i clienti. Ogni invio è firmato in HMAC con un segreto restituito una sola volta, alla creazione. Verifichi questa firma sul lato ricevente, altrimenti chiunque può falsificare un avviso.
L’invio indica lo scenario con il suo identificatore (monitorId) e con il suo nome (monitorName), non con i suoi tag. Per risalire al cliente, il suo ricevitore confronta l’identificatore con l’output scenario_ids, oppure legge l’inizio del nome: ogni nome comincia con quello del cliente.
Il tag client:<chiave> e la cartella servono a ordinare. La cartella tiene insieme gli scenari di un cliente nella console. Il tag filtra l’inventario, per esempio con filter_tag sulla sorgente dati pathly_scenarios.
I webhook in uscita non fanno parte di tutti i piani. Su un piano che non li include, l’API rifiuta la creazione 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
}
}
}Gli scenari di un solo cliente
data "pathly_scenarios" "bakery" {
filter_tag = "client:bakery"
}La chiave API vive nella variabile d’ambiente PATHLY_API_TOKEN, fornita dal suo gestore di segreti o dai secret della CI. Il blocco provider resta vuoto. Una chiave scritta in un file .tf finisce nel repository, e una chiave letta da una variabile Terraform finisce in chiaro nello stato.
Dia alla chiave gli ambiti necessari e nulla di più: scenarios:write, alerting:write e sla:write per applicare. Aggiunga maintenance:write solo se gestisce finestre di manutenzione nello stesso codice. Scrivere implica leggere. Per un job che esegue soltanto terraform plan, basta una seconda chiave con gli ambiti :read.
Se il percorso d’acquisto di un cliente richiede l’accesso, l’account di test passa da una variabile contrassegnata come sensitive, separata dalla mappa clients: Terraform non consente che una mappa sensibile guidi un for_each. Crei questo account per il monitoraggio, senza metodo di pagamento salvato. Non usi mai l’account di un acquirente reale.
Lo stato Terraform contiene il segreto del webhook, perché l’API lo restituisce solo alla creazione. Contiene anche le password di test inserite dal Flow. Lo conservi in un backend remoto, cifrato a riposo e con accesso ristretto. Non ne faccia il commit, e non pubblichi il segreto in un output non sensibile.
Ambiti della chiave
# terraform apply scenarios:write alerting:write sla:write
# maintenance:write (maintenance windows only)
# terraform plan scenarios:read alerting:read sla:readAccount di test, separati dalla mappa clients
variable "test_logins" {
type = map(object({
username = string
password = string
}))
sensitive = true
default = {}
}Passi di accesso 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 collega di fretta crea uno scenario a mano nella console. Sfugge alla revisione, e nessuno sa che esiste. La sorgente dati pathly_scenarios rilegge tutto l’inventario dell’organizzazione. L’output scenarios_outside_terraform elenca ciò che Pathly contiene e il codice ignora.
Una lista vuota significa che il codice descrive tutto. Uno scenario che vi compare si tratta in due modi: lo importa nel codice, oppure lo elimina. Se altri repository Terraform gestiscono una parte dell’organizzazione, restringa l’inventario con filter_tag o filter_folder.
Pianifichi un terraform plan ogni notte in CI, con l’opzione -detailed-exitcode. Il codice di uscita 0 dice che nulla si è mosso. Il codice 2 dice che il piano non è vuoto: un attributo descritto nel codice è cambiato nella console, oppure è cambiata la lista degli scenari fuori dal codice. Il job fallisce e La avvisa.
Questo job notturno si limita a leggere: gli dia una chiave con gli ambiti :read. Gli servono anche l’accesso al backend dello stato e le stesse variabili del job che applica il codice.
Due limiti da conoscere. Un attributo assente dal codice conserva il valore impostato nella console, senza comparire nel piano. I passi di un Flow non si possono rileggere tramite l’API. Scriva quindi nel codice ogni attributo che vuole tenere fermo.
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 notturno
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-exitcodeAggiungere un cliente: una voce in più nella mappa, una pull request, un apply. Il piano annuncia tre risorse da creare e non tocca nient’altro. È il controllo da fare in revisione.
Modificare un cliente: cambi la sua voce. Un nuovo URL, una severità o un obiettivo si aggiornano sul posto. Non rinomini la chiave alla leggera: fa parte dell’indirizzo delle risorse. Terraform distruggerebbe gli scenari e li ricreerebbe, senza la loro cronologia delle esecuzioni. Se deve rinominarla, dichiari un blocco moved per ogni risorsa.
Rimuovere un cliente: elimini la sua voce. Il piano annuncia tre risorse da distruggere. Gli scenari distrutti portano con sé la cronologia delle esecuzioni: esporti prima ciò che il cliente deve conservare.
Importare un sito creato a mano: aggiunga prima la sua voce alla mappa, poi importi lo scenario esistente all’indirizzo di quella voce, con terraform import pathly_scenario.<nome> <UUID dello scenario>. L’identificatore è un UUID, leggibile nell’URL dello scenario nella console. Senza import, Terraform creerebbe un secondo scenario accanto al primo.
Dopo l’import di un Flow, il successivo apply riscrive i passi con quelli del codice, poiché l’API non li restituisce. Un obiettivo di disponibilità già impostato nella console per quello scenario viene adottato al primo apply, senza errore di conflitto.
Un cliente in più
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"
}
}Importare da riga di comando
terraform import 'pathly_scenario.home["cycles"]' 00000000-0000-4000-8000-000000000000
terraform import 'pathly_scenario.checkout["cycles"]' 11111111-1111-4111-8111-111111111111
terraform planOppure un blocco import, revisionato con il resto
import {
to = pathly_scenario.home["cycles"]
id = "00000000-0000-4000-8000-000000000000"
}