Terraform and Pulumi | JustMonitorIt API
On this page

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

  1. Create a token with the member role (or admin) under Settings → API tokens. See Authentication.

  2. 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" {}
    
  3. Pass the token in the JUSTMONITORIT_API_TOKEN environment variable (or api_token in the provider block, from a sensitive variable), then run terraform 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) or locations = { codes = [...] } (2-12 locations; the justmonitorit_locations data source lists them). domain_expiry monitors leave locations and interval_s out: they look the domain up once a day.
  • Heartbeats: interval_s is the period between the job's pings (60-604800 seconds), and there are no locations. The computed ping_url (sensitive: anyone who has it can ping the monitor) is what the job requests; followed by /fail it reports a failed run. In Pulumi it is the pingUrl output.
  • Changing the kind replaces the monitor (a new id, new history).
  • Removing an attribute puts its default back.
  • cert_expiry_warn_days = 0 turns the certificate warning off (the API's null).

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 as https://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, and apply puts the configuration back.
  • Concurrent edits: an update fails, without changing anything, if the monitor or contact was changed after Terraform last read it. Run apply again.
  • 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_… (or con_… 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.