Skip to content

http

!http

Category: integrations

Send a native HTTP(S) request from the controller, using the current host’s templating context. Supports methods, headers, a raw text body, expected status codes, and response capture without curl or a shell.

Notes

  • Runs once per targeted host (and per with.items iteration) from the controller. Without check, query/plan mode sends no request. With check, plan/check-local send only the read-only preflight request; the mutation is never executed. The check and syntax-check CLI modes remain static validation. Successful mutations default to executed/changed; changed_when overrides their reporting.
  • Express authentication and content formats through headers, such as Authorization and Content-Type. Body is a templated string sent verbatim; serialize JSON yourself and set Content-Type: application/json.
  • save writes a host variable containing status (HTTP code), body (response text), headers (lowercase names mapped to lists of values), rc (0 for expected status, 1 otherwise), and out (alias of body). json contains parsed JSON when valid, including JSON null; it is omitted for empty or invalid bodies. Responses are saved before an unexpected status fails the task. Transport failures do not write a result.
  • changed_when uses the existing condition language: true always reports changed, false reports success without change, and expressions such as (eq response.status 201) or (eq response.json.changed true) inspect this invocation’s actual response. The response binding is temporary and available without save. Host/item variables are captured per invocation. Expected status is validated before the condition is evaluated; condition errors fail the task without printing its expression or body.
  • JSON is required only when evaluation reaches response.json. Empty bodies produce a no-JSON-body error; nonempty invalid bodies produce an invalid-JSON error, both naming the condition field and HTTP status without exposing content. Short-circuit and/or guards can skip JSON access. A successful 204 with changed_when: false needs no JSON. Raw response.body/out remain available to conditions and save, but are never printed by the module itself.
  • check is an optional read-only HTTP request with a required matches condition and explicit URL. It inherits mutation headers, timeout, and verify_tls. Explicit check headers merge into the inherited headers, replacing matching names case-insensitively after templating. An empty check.headers map keeps the inherited headers; an empty header value explicitly replaces that value. check.timeout and check.verify_tls override only when supplied. Method (GET or HEAD, default GET; POST requires check.read_only: true), body, and expected_status remain independent with their normal defaults.
  • For APIs such as JMAP that use POST for queries, set check.method: POST and check.read_only: true. This explicitly declares that the check body contains only reads and is safe to execute during plan/check-local. Jetpack cannot verify application-level semantics: do not put mutations in a declared read-only check. POST without the opt-in and all other unsupported methods are rejected before network I/O. The parent mutation is never executed in plan/check-local.
  • The query phase validates the preflight’s expected status, then evaluates check.matches against its own temporary response. A true result reports matched/no change and skips mutation in both plan and apply. False reports needs execution in plan and executes mutation in apply. Transport errors, unexpected statuses, invalid JSON accesses, and invalid conditions fail without mutation. An explicitly expected empty 404 can establish absence with (and (eq response.status 200) (eq response.json.authoritative true)).
  • check.matches accepts the existing condition string or an explicit JSON/YAML mapping with path and exactly one of contains or contains_exactly. The path uses existing Handlebars variable paths (for example response.json.zones); a missing path is a mismatch, distinct from an explicit null. Object keys in the expected value remain literal.
  • Structural matching compares objects as recursive subsets, ignoring unspecified response keys. contains allows extra array entries and ignores their order, but each expected entry needs a distinct actual entry (duplicates count). contains_exactly requires identical array length and order. These rules apply recursively, including nested arrays. Scalars compare as typed JSON values: 3600 differs from the string 3600.
  • A whole-value variable template such as ‘{{ gravity_dns_ttl }}’ or ‘{{ gravity_authoritative_handlers }}’ preserves numbers, booleans, null, arrays, and objects. Mixed text such as ‘zone-{{ item }}’ stays text; rendered text is never guessed to be JSON. Typed values resolve nested variable templates with the existing eight-expansion bound. Expected values are evaluated against an invocation-local snapshot only when matching is reached; undefined expected variables fail without printing their values.
  • For structural paths into response.json, an explicitly accepted 404 returns false (resource absent) before JSON matching. An unexpected 404 still fails expected-status validation. Empty or invalid successful JSON responses fail with the existing redacted JSON diagnostics. Expression matching retains its existing JSON access and short-circuit behavior; non-JSON paths such as response.status do not require a JSON body.
  • check.matches establishes whether mutation is necessary; changed_when evaluates only the executed mutation’s response and determines the final apply report, even if it returns false after a mismatching preflight. The save variable captures only executed mutation responses: matched checks and plan do not write it or clear an earlier value. Preflight and mutation are separate requests, so server-side concurrency controls are still needed to handle another actor changing state between them.
  • Request values are omitted from debug output and errors. Captured Authorization, Proxy-Authorization, Cookie, Set-Cookie, and headers containing token, key, or secret have redacted values. Captured body/out remain raw; printing those variables may expose sensitive response content.
  • Redirects are not followed, preventing custom authentication headers from reaching another endpoint. A redirect is captured as its 3xx status and fails unless explicitly listed in expected_status.

Parameters

NameTypeRequiredDescription
urlstringyesTemplated HTTP(S) URL, including query parameters.
methodstringnoTemplated HTTP method, case-insensitive. Default GET.
headersmapnoHeader names and string values, both templated. Default empty.
bodystringnoOptional templated raw request body. Empty strings are allowed.
expected_statuslistnoNonempty list of HTTP status codes (100–599), optionally templated. Default any 2xx status.
savestringnoTemplated host-variable name to save the response map, following command/shell save.
changed_whenconditionnoDeferred condition evaluated against this invocation’s temporary response after expected-status validation. Default true (always changed). Use false for successful requests that should not claim a change.
checkmapnoRead-only preflight request inheriting headers, timeout, and TLS settings. Runs in query phase in both plan and apply; a matching resource skips mutation.
check.urlstringnoRequired when check is set. Templated HTTP(S) URL to inspect.
check.matchescondition or mapnoRequired when check is set. Existing condition string or structural mapping with path and exactly one contains/contains_exactly operator. True means already configured; false means mutation is needed.
check.methodstringnoTemplated GET or HEAD, or POST with check.read_only: true. Default GET; unsupported methods fail before I/O.
check.read_onlyboolnoExplicitly declare a POST check to contain only read operations. Literal boolean; defaults to false. Required for POST checks, unnecessary for GET/HEAD, and does not permit other methods.
check.headersmapnoTemplated preflight headers merged with mutation headers. Matching names replace inherited values case-insensitively; absent names are added.
check.bodystringnoOptional templated raw preflight body.
check.expected_statuslistnoExpected preflight status codes. Default any 2xx; explicitly include 404 when absence is a valid check result.
check.timeoutintnoPositive preflight timeout in seconds. Defaults to the mutation timeout (30 when neither is set).
check.verify_tlsboolnoPreflight TLS certificate verification flag. Defaults to the mutation setting (true when neither is set).
timeoutintnoTemplated positive timeout in seconds for the entire request and response body. Default 30.
verify_tlsboolnoTemplated TLS certificate verification flag. Default true.

Examples

- !http
  name: Configure DHCP scope
  url: "{{ gravity_url }}/api/v1/dhcp/scopes?scope=example.com"
  method: POST
  headers:
    Authorization: "Bearer {{ gravity_api_token }}"
    Content-Type: application/json
  body: "{{ gravity_dhcp_scope }}"
  expected_status: [200, 204]
  save: response
- !http
  url: https://api.example.com/health
  changed_when: "false"
  save: health
- !assert
  true: (eq health.status 200)
  msg: API health check failed
- !http
  url: https://api.example.com/resources
  method: POST
  headers:
    Content-Type: application/json
  body: '{"enabled":true}'
  expected_status: [200, 201, 204]
  changed_when: "(eq response.status 201)"
- !http
  name: Configure authoritative DNS zones
  url: '{{ gravity_bootstrap_url }}/api/v1/dns/zones?zone={{ item }}'
  method: POST
  headers:
    Authorization: 'Bearer {{ gravity_api_token }}'
    Content-Type: application/json
  body: |
    {
      "authoritative": true,
      "defaultTTL": 3600,
      "handlerConfigs": [{"type": "memory"}, {"type": "etcd"}],
      "hook": ""
    }
  expected_status: [200, 201, 204]
  changed_when: '(eq response.status 201)'
  check:
    method: GET
    url: '{{ gravity_bootstrap_url }}/api/v1/dns/zones?zone={{ item }}'
    expected_status: [200, 404]
    matches: '(and (eq response.status 200) (eq response.json.authoritative true) (eq response.json.defaultTTL 3600))'
  with:
    items: [example.com]
- !http
  name: Configure DNS zone
  url: 'https://api.example.com/zones?name={{ item }}'
  method: POST
  body: '{{ dns_zone_payload }}'
  expected_status: [200, 201, 204]
  changed_when: '(eq response.status 201)'
  check:
    url: 'https://api.example.com/zones?name={{ item }}'
    expected_status: [200, 404]
    matches:
      path: response.json.zones
      contains_exactly:
        - name: '{{ item }}'
          authoritative: true
          defaultTTL: '{{ gravity_dns_ttl }}'
          handlerConfigs: '{{ gravity_authoritative_handlers }}'
          hook: '{{ gravity_zone_hook }}'
  with:
    items: [example.com]
- !http
  name: Configure a resource through JMAP
  url: '{{ api_url }}/jmap'
  method: POST
  headers:
    Authorization: '{{ api_authorization }}'
    Content-Type: application/json
  body: '{{ update_request }}'
  check:
    url: '{{ api_url }}/jmap'
    method: POST
    read_only: true
    body: '{{ query_request }}'
    matches:
      path: response.json.methodResponses
      contains: '{{ expected_query_result }}'