September 2026
The Pathly Terraform provider, in practice
It is published on the registry. Here are the files you actually write: the smallest one worth having, a whole estate described as data, and taking over monitoring that was created by hand.
Installing
The provider is called pathlyhq/pathly on the public registry. There is nothing to compile and nothing to drop into a mirror: declaring the version is enough, and the init command downloads it. The ~> 0.1 constraint accepts patches and refuses a future 0.2, which is the right setting while the schema is not frozen by a 1.0.
terraform {
required_version = ">= 1.6"
required_providers {
pathly = {
source = "pathlyhq/pathly"
version = "~> 0.1"
}
}
}
# Aucun argument : la clé vient de PATHLY_API_TOKEN.
provider "pathly" {}The provider block is empty, and that is not carelessness. The key is read from the PATHLY_API_TOKEN environment variable. Passed through a Terraform variable, it would end up in plaintext in the state file, which one always forgets to encrypt until after pushing it somewhere. The provider also refuses to speak http to anything but localhost: the key travels in an authorisation header, and would be readable by every intermediary.
The smallest file worth having
One scenario, one address, one interval, and a piece of text expected on the page. That last point is the difference between watching a server and watching a site: an application error page answers HTTP 200 perfectly well, and only the expected text exposes it.
resource "pathly_scenario" "site" {
name = "Home page"
url = "https://shop.example.com"
interval_sec = 300
expect_text = "Our products"
tags = ["prod"]
}An estate described as data
Past three or four journeys, one block per resource becomes a list you re-read on every addition. Describing them in a variable and looping over it changes the nature of the file: adding a journey becomes one line of data, and the threshold, folder and tag rules stay the same for all of them without anyone thinking about it.
resource "pathly_scenario" "journey" {
for_each = var.journeys
name = each.value.name
url = each.value.url
interval_sec = each.value.interval_sec
expect_text = each.value.expect_text
# Un seuil de latence par parcours : le paiement tolère
# moins qu'une page d'accueil servie depuis le cache.
max_latency_ms = each.value.max_latency_ms
expected_status = 200
severity = each.value.severity
tags = concat(["terraform", var.environment], each.value.tags)
}
# Un objectif de disponibilité par parcours critique, qui
# alerte à 80 % du budget d'erreur dépensé : au-delà, il
# reste trop peu de marge pour le reste du mois.
resource "pathly_sla_target" "objective" {
for_each = {
for key, j in var.journeys : key => j if j.severity == "critical"
}
scenario_id = pathly_scenario.journey[each.key].id
objective_pct = 99.9
window_days = 30
exclude_maintenance = true
warn_at_budget_ratio = 0.8
}The availability objective excludes maintenance windows, which only makes sense if those windows also exist in the code. A maintenance declared on one side and an objective ignoring it on the other produce an error budget spent by an announced outage, that is, an alert punishing a planned operation.
# Sauvegarde hebdomadaire : les échecs dans cette fenêtre
# ne dépensent pas le budget d'erreur.
resource "pathly_maintenance_window" "backup" {
weekday = 7 # dimanche
start_minute = 3 * 60
duration_min = 120
reason = "Weekly backup"
}Spotting what was created by hand
This is the data source’s most useful role, and the one people think of least. It re-reads the inventory as the API sees it. Compared with what the code declares, the gap names the scenarios created in the console and missing from the files: the ones nobody reviews, and nobody will remove when an environment is torn down.
data "pathly_scenarios" "prod" {
filter_tag = var.environment
depends_on = [pathly_scenario.journey]
}
output "scenarios_outside_terraform" {
value = [
for s in data.pathly_scenarios.prod.scenarios : s.name
if !contains([for j in pathly_scenario.journey : j.id], s.id)
]
}The same reasoning applies to values the API never returns. A webhook destination is written and never read back: instead, a twelve-character fingerprint, which changes as soon as the address changes. It does not allow reconstructing the destination, and is enough to see on a plan that it was modified elsewhere.
output "webhook_fingerprint" {
value = pathly_webhook.alerts.url_fingerprint
}Taking over existing monitoring
Nobody rebuilds their estate to move to code, and the provider does not ask for it. Each resource is imported by its identifier, the one carried by the scenario’s address in the console. After the import, the plan shows the gap between what exists and what the file describes: as long as it is not empty, applying will modify the scenario in place rather than create a second one.
terraform import pathly_scenario.checkout mon_01H8ZK…
The criterion that decides whether any of this is usable fits in one sentence: after an apply, the next plan must be empty. A provider proposing a change on every pass teaches its users very quickly to stop reading its plans, and the day the plan says something true, nobody sees it. That is the provider’s least visible work, and the one its tests insist on most.
What the provider will never do
- Issue or revoke an API key: a key able to do that cancels every other key’s scopes, and a configuration file must not be able to widen itself.
- Invite a member: the same escalation by a detour, since the new account will then issue whatever keys it wants.
- Change plan or touch billing: a continuous integration pipeline must not be able to commit recurring spending.
These absences are not late features. A provider covering everything ends up requiring a key that can do everything, and that key then sits in the variables of a pipeline read by more people than one thinks. What is missing here is missing on purpose, and is done in the interface, by an identified human.