Black Friday dans 15 jours : vos clients pourront-ils payer ? -25 % la 1re année et mise en place offerte, jusqu’au 30 novembreVoir l’offre →
Intégration
Une entrée dans une map, un terraform apply : le Ping, le Flow du tunnel de commande et l’objectif de disponibilité d’un client sont en place.
Obtenir ma clé d’APIContrat OpenAPI
Document complet, sans authentification, à donner à votre générateur de client.
Vous maintenez plusieurs sites clients. Chaque site tient en une entrée dans un fichier de variables. Terraform crée pour lui un Ping de la page d’accueil, un Flow du tunnel de commande jusqu’à la page de paiement et un objectif de disponibilité.
Avec les deux clients ci-dessous, le premier terraform apply crée sept ressources : trois par client, et un webhook d’alerte commun. Un client de plus, c’est une entrée de plus et trois ressources de plus. Personne ne clique dans la console.
L’exemple complet, avec ses validations, est dans le dépôt du provider, dossier examples/agency-client. Cette page en suit le code, en version courte. Les domaines example.com sont réservés à la documentation : remplacez-les par les sites que vous avez le droit de surveiller.
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
}
}Appliquer
# PATHLY_API_TOKEN vient de votre coffre de secrets, jamais d'un fichier
terraform init
terraform plan
terraform apply
# Plan: 7 to add, 0 to change, 0 to destroy.La variable clients est une map d’objets. Sa clé est un identifiant court et stable : elle entre dans l’adresse de chaque ressource et dans le tag du client. Chaque ressource parcourt cette map avec for_each.
Le Ping est un pathly_scenario avec une url : il vérifie que la page d’accueil répond. Le Flow est un pathly_scenario de type browser, avec ses étapes dans steps. Un vrai navigateur ouvre la page produit, ajoute le produit au panier et ouvre la page de paiement. La première étape est toujours un goto.
Le Flow s’arrête quand la page de paiement s’affiche. Aucune étape ne clique sur un bouton de paiement, et il ne saisit que des valeurs de test. Les sélecteurs et le texte attendu dépendent de chaque site : vérifiez-les dans l’inspecteur du navigateur avant d’appliquer.
L’objectif de disponibilité est un pathly_sla_target lié au Ping. objective_pct fixe la cible sur une fenêtre glissante de window_days jours. warn_at_budget_ratio prévient quand 80 % du budget d’erreur est consommé, tant qu’il reste de la marge pour agir.
L’API ne renvoie jamais les étapes d’un Flow, seulement leur empreinte (scenario_fingerprint). Le code reste donc la seule copie lisible du parcours : gardez-le dans le dépôt, relu comme le reste.
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"
},
]
}Objectif de 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 seul pathly_webhook reçoit les échecs (run.failed) et les retours à la normale (run.recovered) de tous les clients. Chaque envoi est signé en HMAC avec un secret rendu une seule fois, à la création. Vérifiez cette signature côté réception, sinon n’importe qui peut forger une fausse alerte.
L’envoi nomme le scénario par son identifiant (monitorId) et par son nom (monitorName), pas par ses tags. Pour retrouver le client, votre récepteur compare l’identifiant à la sortie scenario_ids, ou lit le début du nom : chaque nom commence par celui du client.
Le tag client:<clé> et le dossier servent à trier. Le dossier range ensemble les scénarios d’un client dans la console. Le tag filtre l’inventaire, par exemple avec filter_tag sur la source de données pathly_scenarios.
Les webhooks sortants ne font pas partie de toutes les offres. Sur une offre qui ne les inclut pas, l’API refuse la création du 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
}
}
}Les scénarios d’un seul client
data "pathly_scenarios" "bakery" {
filter_tag = "client:bakery"
}La clé d’API vit dans la variable d’environnement PATHLY_API_TOKEN, fournie par votre coffre de secrets ou par les secrets de la CI. Le bloc provider reste vide. Une clé écrite dans un fichier .tf finit dans le dépôt, et une clé lue depuis une variable Terraform finit en clair dans l’état.
Donnez à la clé les portées utiles et rien de plus : scenarios:write, alerting:write et sla:write pour appliquer. Ajoutez maintenance:write seulement si vous gérez des fenêtres de maintenance dans le même code. Écrire implique lire. Pour une tâche qui ne lance que terraform plan, une seconde clé avec les portées :read suffit.
Si le tunnel d’un client demande une connexion, le compte de test passe par une variable marquée sensitive, à part de la map clients : Terraform refuse qu’une map sensible pilote un for_each. Créez ce compte pour la surveillance, sans moyen de paiement enregistré. N’utilisez jamais le compte d’un vrai acheteur.
L’état Terraform contient le secret du webhook, parce que l’API ne le rend qu’à la création. Il contient aussi les mots de passe de test saisis par le Flow. Rangez-le dans un backend distant, chiffré au repos, avec un accès restreint. Ne le commitez pas, et ne publiez pas le secret dans une sortie non sensible.
Portées de la clé
# terraform apply scenarios:write alerting:write sla:write
# maintenance:write (maintenance windows only)
# terraform plan scenarios:read alerting:read sla:readComptes de test, à part de la map clients
variable "test_logins" {
type = map(object({
username = string
password = string
}))
sensitive = true
default = {}
}Étapes de connexion du 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 collègue pressé crée un scénario à la main dans la console. Il échappe à la relecture, et personne ne sait qu’il existe. La source de données pathly_scenarios relit tout l’inventaire de l’organisation. La sortie scenarios_outside_terraform liste ce que Pathly contient et que le code ignore.
Une liste vide veut dire que le code décrit tout. Un scénario qui y apparaît se traite de deux façons : vous l’importez dans le code, ou vous le supprimez. Si d’autres dépôts Terraform gèrent une partie de l’organisation, réduisez l’inventaire avec filter_tag ou filter_folder.
Planifiez un terraform plan chaque nuit en CI, avec l’option -detailed-exitcode. Le code de sortie 0 dit que rien n’a bougé. Le code 2 dit que le plan n’est pas vide : un attribut décrit dans le code a changé dans la console, ou la liste des scénarios hors code a changé. La tâche échoue et vous prévient.
Cette tâche de nuit ne fait que lire : donnez-lui une clé avec les portées :read. Elle a aussi besoin de l’accès au backend de l’état et des mêmes variables que l’application du code.
Deux limites à connaître. Un attribut absent du code garde la valeur posée dans la console, sans apparaître dans le plan. Les étapes d’un Flow ne se relisent pas par l’API. Écrivez donc dans le code chaque attribut que vous voulez tenir.
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 de nuit
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-exitcodeAjouter un client : une entrée de plus dans la map, une pull request, un apply. Le plan annonce trois ressources à créer et ne touche à rien d’autre. C’est le contrôle à faire en relecture.
Modifier un client : changez son entrée. Une nouvelle URL, une sévérité ou un objectif se mettent à jour sur place. Ne renommez pas la clé sans précaution : elle fait partie de l’adresse des ressources. Terraform détruirait les scénarios puis les recréerait, sans leur historique d’exécutions. Si vous devez la renommer, déclarez un bloc moved par ressource.
Retirer un client : supprimez son entrée. Le plan annonce trois ressources à détruire. Les scénarios détruits emportent leur historique d’exécutions : exportez avant ce que le client doit garder.
Importer un site créé à la main : ajoutez d’abord son entrée dans la map, puis importez le scénario existant à l’adresse de cette entrée, avec terraform import pathly_scenario.<nom> <UUID du scénario>. L’identifiant est un UUID, lisible dans l’adresse du scénario dans la console. Sans import, Terraform créerait un second scénario à côté du premier.
Après l’import d’un Flow, le prochain apply réécrit les étapes avec celles du code, puisque l’API ne les renvoie pas. Un objectif de disponibilité déjà posé dans la console sur ce scénario est adopté au premier apply, sans erreur de conflit.
Un client de plus
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"
}
}Importer en ligne de commande
terraform import 'pathly_scenario.home["cycles"]' 00000000-0000-4000-8000-000000000000
terraform import 'pathly_scenario.checkout["cycles"]' 11111111-1111-4111-8111-111111111111
terraform planOu un bloc import, relu avec le reste
import {
to = pathly_scenario.home["cycles"]
id = "00000000-0000-4000-8000-000000000000"
}