Multi-step HTTP flows check
flow — Multi-step HTTP flows
Ordered HTTP steps where a value captured from one response is used by the next; the finding names the step that failed. Declarative only, captured values never printed.
checkfleet check flow --config checkfleet.yml
Half of what an operator cares about is not a single request. “Login works” is get a token, use it, check the answer — and a probe that only asks the first of those reports a healthy service while nobody can log in.
A flow is an ordered list of steps. Each step is one HTTP request; a value captured from its response can be substituted into any later step:
extractcapturesname: selector. Three selectors, no more:json:<dotted.path>(a numeric segment indexes an array, soitems.0.idworks),header:<Name>,regex:<pattern with one capture group>.- `` substitutes a captured value into a later step’s
url,headersorbody. That is the entire syntax: no expressions, no filters, no property access. expect_status(default 200),expect_body(a substring) andmax_latency_msper step;max_latency_mson the flow budgets the whole thing, because a login that takes eight seconds is broken regardless of which hop spent them.keep_cookiescarries a cookie jar across the steps, which is what a session login needs.insecure_skip_verifyskips TLS verification.- Credentials come from the environment by name:
headers_envsets a header from a variable,body_envreads the whole request body from one.
The finding names the step that failed. step 2/3 "use the token": expected
status 200, got 401 — “the flow is broken” without saying where is a finding
that saves nobody any time. The first failure stops the flow, since every later
step would fail for the same reason and bury the one that matters.
BAD when the target answered and answered wrong (a status, a missing
substring, a selector that found nothing); ERROR when the check could not
measure at all (unreachable host, a named env var that is unset). One finding per
flow, named after it.
Two rules this module is built around
Nothing in the config is ever executed. The steps are declarative and the substitution resolves one form of reference against values the flow itself captured. A check that evaluated code from its own config would be a new security surface on a process that already holds the credentials of up to 30 production systems — so there is no scripting here, and there will not be.
Captured values are never printed. A login flow captures a token, and a finding travels to a terminal, a CI log and a JSON file. Messages name what was looked for — the selector, the header, the missing capture — never what was found. A URL that had a captured value substituted into it is redacted out of transport errors, which quote the whole URL back.
flow:
flows:
- name: login
max_latency_ms: 5000
steps:
- name: get a token
method: POST
url: https://auth.example.com/token
headers: {Content-Type: application/x-www-form-urlencoded}
body_env: CF_FLOW_LOGIN_BODY # carries the client secret
extract:
token: json:access_token
- name: use the token
url: https://api.example.com/me
headers:
Authorization: "Bearer "
expect_body: '"active":true'
Target discovery
The certs, nats, haproxy, patroni, consul, redis and tls modules can
take their targets from three sources, usable together. Results are merged and
de-duplicated by address; when two sources name the same address the first one
wins, so a hand-written inventory keeps its host name.
checkfleet targets resolves them before any check runs and tags each
discovered host with its source, so you can see what a run would cover without
running it. --no-discover lists only the targets written in the config.
ansible_inventory
A standard Ansible INI inventory (a file or a directory of files):
- host lines and their
ansible_host=value are used; :varsand:childrensections are ignored;- hosts are de-duplicated.
Every discovered host becomes a target on the module’s port (443 for certs,
8222 for nats, 8404 for haproxy, 8008 for patroni, 8500 for consul,
6379 for redis, 443 for tls).
consul_service
Pulls the instances of a service from a Consul catalog — service (required),
address (default 127.0.0.1:8500), scheme, tag, token_env. Only instances
passing their own health checks by default (only_healthy), because a target list
carrying known-dead nodes turns one outage into a second, noisier one.
dns_srv
A list of SRV lookups: name (the full record, e.g.
_nats._tcp.service.consul) and an optional resolver — names served only by
Consul’s DNS on :8600 or a cluster’s CoreDNS are invisible to the system
resolver, so this is not an edge case.
Both sources know a port and keep it by default; set keep_port: false when the
module supplies its own, as certs does with 443.
A source that cannot be reached is a warning in targets and an ERROR finding
in a run — never silence. The hosts the other sources did resolve are still
checked: one broken source must not quietly mean “nothing to check”, which would
report a healthy fleet from a typo in a path.
See Configuration → Target discovery for the full key reference.
See the full module reference for every check, or configuration for the config keys.