checkfleet

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:

  • extract captures name: selector. Three selectors, no more: json:<dotted.path> (a numeric segment indexes an array, so items.0.id works), header:<Name>, regex:<pattern with one capture group>.
  • `` substitutes a captured value into a later step’s url, headers or body. That is the entire syntax: no expressions, no filters, no property access.
  • expect_status (default 200), expect_body (a substring) and max_latency_ms per step; max_latency_ms on the flow budgets the whole thing, because a login that takes eight seconds is broken regardless of which hop spent them.
  • keep_cookies carries a cookie jar across the steps, which is what a session login needs. insecure_skip_verify skips TLS verification.
  • Credentials come from the environment by name: headers_env sets a header from a variable, body_env reads 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;
  • :vars and :children sections 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 catalogservice (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.