Terraform, OpenTofu and Pulumi
Manage monitors and contacts as code. The JustMonitorIt provider works with Terraform (1.0 or later) and OpenTofu, and its Pulumi version uses the same code. It talks to the REST API with an API token, so the same roles, validation, plan limits and audit log apply.
Set up
-
Create a token with the
memberrole (oradmin) under Settings → API tokens. See Authentication. -
Require the provider. It is installed from JustMonitorIt's own registry, so the source starts with
dash.justmonitorit.com:terraform { required_providers { justmonitorit = { source = "dash.justmonitorit.com/justmonitorit/justmonitorit" version = "~> 0.1" } } } provider "justmonitorit" {} -
Pass the token in the
JUSTMONITORIT_API_TOKENenvironment variable (orapi_tokenin the provider block, from a sensitive variable), then runterraform init. The provider works on the organisation of its token.
terraform init checks each download against a checksum list signed with
the registry key (ID 160A38870F4B46E0, fingerprint
356C A0E2 8CBD 74A8 2B7C AE77 160A 3887 0F4B 46E0; the public key is at
/_registry/signing-key.asc)
and records the checksums in .terraform.lock.hcl.
Monitors
Set exactly one kind: http, http_adv, tcp, icmp, ssl_expiry,
domain_expiry, imap, smtp or heartbeat. The fields of each are
those of the monitor kinds, with the same defaults.
resource "justmonitorit_contact" "ops" {
name = "Ops team"
email = { address = "ops@example.com" }
}
resource "justmonitorit_monitor" "homepage" {
name = "Homepage"
interval_s = 60
locations = { regions = ["eu"] }
http = { url = "https://example.com/", keyword = "Welcome" }
labels = ["web", "production"]
contact_ids = [justmonitorit_contact.ops.id]
}
resource "justmonitorit_monitor" "api" {
name = "Status API"
interval_s = 30
locations = { regions = ["eu", "na"] }
degraded_after_ms = 2000
http_adv = {
url = "https://api.example.com/health"
headers = [{ name = "X-Api-Key", secret_value = var.health_key }]
assertions = [
{ type = "json_path", path = "$.status", op = "equals", json_value = jsonencode("ok") },
]
}
contact_ids = [justmonitorit_contact.ops.id]
}
resource "justmonitorit_monitor" "domain" {
name = "example.com registration"
domain_expiry = { domain = "example.com", expiry_warn_days = 45 }
}
resource "justmonitorit_monitor" "backup" {
name = "Nightly backup"
interval_s = 86400
heartbeat = { grace_s = 1800 }
}
output "backup_ping_url" {
value = justmonitorit_monitor.backup.ping_url
sensitive = true
}
- Locations:
locations = { regions = [...] }(every location in those regions) orlocations = { codes = [...] }(2-12 locations; thejustmonitorit_locationsdata source lists them).domain_expirymonitors leavelocationsandinterval_sout: they look the domain up once a day. - Heartbeats:
interval_sis the period between the job's pings (60-604800 seconds), and there are nolocations. The computedping_url(sensitive: anyone who has it can ping the monitor) is what the job requests; followed by/failit reports a failed run. In Pulumi it is thepingUrloutput. - Changing the kind replaces the monitor (a new id, new history).
- Removing an attribute puts its default back.
cert_expiry_warn_days = 0turns the certificate warning off (the API'snull).
Contacts
Set exactly one channel: email, sms, pushover, discord,
pagerduty, slack or webhook.
resource "justmonitorit_contact" "pagerduty" {
name = "PagerDuty"
pagerduty = { routing_key = var.pagerduty_routing_key, region = "eu" }
notify_on = ["down", "recovered", "degraded"]
}
Email and SMS contacts start unverified: the recipient confirms them before
alerts are sent, as when you add them in the web app. Creating them sends a
code, and an organisation can send 10 codes a day, so a configuration with
more than 10 new email or SMS contacts needs a second apply the next day.
A webhook contact stays unverified until its host has a TXT record that
proves you control it. The contact's webhook_txt_name and
webhook_txt_value give the record, so your DNS provider's resource can
publish it in the same configuration:
resource "justmonitorit_contact" "webhook" {
name = "Ops webhook"
webhook = { url = "https://hooks.example.com/justmonitorit?token=${var.webhook_token}" }
}
# For example with Cloudflare:
resource "cloudflare_dns_record" "justmonitorit" {
zone_id = var.zone_id
name = justmonitorit_contact.webhook.webhook_txt_name
type = "TXT"
content = "\"${justmonitorit_contact.webhook.webhook_txt_value}\""
ttl = 300
}
Then press Check again on the contact in the web app (or POST .../contacts/{id}/verify/send) once the record is live.
Attach contacts with a monitor's contact_ids. Leave contact_ids out and
a new monitor gets every contact with auto attach, and Terraform leaves
its contacts alone afterwards. paused works the same way: set it to manage
pausing in code, leave it out to pause and resume in the web app.
How it behaves
- Secrets (
basic_auth.password,bearer_token,secret_value,user_key,webhook_url,routing_key) are write-only in the API: the provider keeps them in the state, marked sensitive. Protect your state as you protect the secrets. A secret changed in the web app is not detected. - Values the API normalises, such as
https://Example.com(stored ashttps://example.com/), stay as you wrote them in the state; a later change in the web app still shows as a difference. - Changes made elsewhere show in
terraform plan, andapplyputs the configuration back. - Concurrent edits: an update fails, without changing anything, if the
monitor or contact was changed after Terraform last read it. Run
applyagain. - Rate limits: requests over the per-minute write limit wait and retry for up to 5 minutes, so large applies just take longer. A longer limit fails at once and says when to retry.
- Import:
terraform import justmonitorit_monitor.homepage mon_…(orcon_…for a contact). Set any secrets in the configuration and apply once after importing.
Data sources: justmonitorit_locations, justmonitorit_monitor (with its
live status and uptime_24h) and justmonitorit_contact.
OpenTofu
Use the same source. OpenTofu checks the same signature.
Pulumi
The Pulumi provider has the same resources, with Pulumi names
(justmonitorit:Monitor, justmonitorit:Contact) and camelCase properties
(intervalS, httpAdv, secretValue). Install its plugin once, then use it
from a YAML program:
pulumi plugin install resource justmonitorit v0.1.0 \
--server https://dash.justmonitorit.com/_registry/pulumi
pulumi config set --secret justmonitorit:apiToken jmi_...
name: monitoring
runtime: yaml
resources:
homepage:
type: justmonitorit:Monitor
properties:
name: Homepage
intervalS: 60
locations:
regions: [eu]
http:
url: https://example.com/
For TypeScript, Python, Go or C#, generate a local SDK from the Terraform
provider: pulumi package add terraform-provider dash.justmonitorit.com/justmonitorit/justmonitorit.