The dhis2 connection¶
One dhis2 connection is one DHIS2 instance and the credential to talk to it. Every block in
this pack takes a connection field naming one by its code, and builds its client from that
row: the URL, the credential, the TLS setting and the timeout all come from the connection,
never from a step.
Settings¶
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
base_url |
string |
yes | The instance root every request path is resolved against, version pin included. | |
api_token |
string (password) or null |
null |
A personal access token, sent as Authorization: ApiToken: visible ASCII with no whitespace. |
|
basic_username |
string or null |
null |
The user half of HTTP basic authentication. | |
basic_password |
string (password) or null |
null |
The secret half of HTTP basic authentication. | |
verify_tls |
boolean |
true |
Whether certificates are verified; turning this off is a per-connection decision. | |
follow_redirects_with_auth |
boolean |
false |
Whether the URL base_url redirects to is used whatever its host or scheme, credential and all. |
|
timeout |
string (humane-duration) |
"30s" |
The timeout applied to each read of every request through this connection. | |
request_deadline |
string (humane-duration) |
"10m" |
The longest one request through this connection may take, start to last byte. | |
max_response |
string (size) or integer |
"64MB" |
The most one response through this connection may decode to; a larger one is refused. |
api_token and basic_password are the secret fields: they are encrypted at rest with the
instance key and redacted in every read, so dg connection show and the API answer with the
field present and its value withheld.
timeout and request_deadline are durations, written the way a document writes one -- 30s,
2m -- and each must be greater than zero. max_response is a size, written 64MB or 512KB.
What one answer may cost¶
The connection holds an instance to two limits, on every request a block makes and on the version probe that opens its client.
max_responseis counted as a body decodes, so a small compressed answer stops at the limit rather than expanding in the worker's memory. Past it the check is unhealthy and the step fails asrejected, naming the request. Raise it if a real read needs more, or narrow the read withfields.request_deadlinecovers one request start to last byte.timeoutlimits each read instead, so an instance sending a byte at a time never reaches it. Past the deadline the check is unhealthy and the step fails astransient, so a retry follows the step's own policy. Arequest_deadlineshorter thantimeoutis the one that fires, so raise both for a long synchronous import.
base_url says where the instance is and nothing else: a user, a password, a query or a fragment
in it is refused when the connection is created or updated, and the refusal does not repeat it.
The probe that follows base_url's redirects reads no response body at all, and the anonymous
/api/system/info read that tests a redirect target parses at most 1 MiB.
One credential, not two¶
A connection carries a personal access token or a basic username and password, and the model refuses anything else:
- an
api_tokenbeside abasic_usernameor abasic_passwordis refused, - neither is refused,
- a
basic_usernamewithout abasic_passwordis refused.
An api_token holding anything but visible ASCII, such as the trailing newline a pasted or
file-read token brings with it, is refused too, because it cannot be sent as a header value. The
refusal does not repeat the value. A basic password is base64-encoded on the way to the header and
takes any text.
A refusal happens when the connection is created or updated, not when a step first runs. A row stored before a rule existed fails when it is next read: its check is unhealthy and its steps fail.
An instance or a proxy that quotes the Authorization header it was sent never puts the
credential in a health check's detail or a step's failure: the header value, the credential
after its scheme, and a basic pair as the user and password it encodes are each replaced by
***.
A base_url that redirects¶
A client follows base_url's redirects once, without the credential, and talks to where they
land when all of these hold:
- the target keeps the scheme,
- the target's host is
base_url's host, a subdomain of it, or underdhis2.orgbeside it, - the target answers an anonymous
/api/system/infolike DHIS2: JSON with aversionorcontextPath, or DHIS2's own error envelope.
Any other target never receives the credential: an open redirect or a sign-in page does not, and
neither does a host that only shares its last labels with base_url. dhis2.health.co.uk and
attacker.co.uk end alike and have different registrants, and so do two names under a shared
platform domain such as azurewebsites.net or github.io. The connection stays on base_url, or
its check and steps fail with dhis2.connection.redirect_refused, naming the target.
Two sibling hosts under one domain of your own are not told apart from those, so
dhis2.example.org redirecting to hmis.example.org is not followed either. Set base_url to the
target.
The answer to /api/system/info is something any host can send, so that test tells DHIS2 from a
sign-in page and says nothing about who holds the target. The rule on the host is what keeps the
credential in place.
https://play.dhis2.org/2.43 and https://play.dhis2.org/2.42 are aliases for the newest play
demo of each major, and resolve this way: a connection naming one follows the demo as it moves.
http://play.dhis2.org/2.43 lands on https://, a change of scheme that is never followed on its
own. follow_redirects_with_auth: true follows any redirect, to any host or scheme, and sends the
credential there; set it only on a base_url whose redirect you trust.
Creating one¶
dg connection create dhis2 dhis2-play \
--name "DHIS2 play demo" \
--set base_url=https://play.dhis2.org/2.43 \
--set timeout=2m
At a terminal it asks how to sign in, api_token or basic, then for the fields of the form
chosen, the secret without echoing it; the play demo takes the login it publishes on its own
sign-in page. Outside a terminal the credential is given as flags, and a command with none is
refused naming both forms. dhis2-play is the code every document on the pack's shelves names.
With a personal access token instead:
dg connection create dhis2 production \
--set base_url=https://dhis.example.org \
--set api_token="$DHIS2_TOKEN" \
--set timeout=1m
dg connection create talks to the server and needs a token. dg connection ensure is the
process-side twin that writes the row itself, for a bootstrap container that has none; it is
declarative, so a field left out is cleared rather than kept.
A step then names the connection by its code:
steps:
export:
block: dhis2.data_value_set_export
config:
connection: play
data_set: BfMAe6Itzgt
period: 2026Q1
org_unit: ImspTQPwCqd
Checking one¶
The check builds the client from the stored config and opens it, which reads
/api/system/info with the credential attached. So a healthy verdict means the URL resolves,
the TLS setting works, the credential is accepted, and the version is the instance's own word
for itself rather than something inferred from an /api/N path.
The command writes one connection.checked record, carrying the code, whether it is
healthy, the version the instance reported, and a detail line naming the failure when it
is not. A check never raises: an unreachable host, a refused credential and a version the
client does not speak all come back as an unhealthy report with the reason in detail.
A check may also answer neither way: healthy is null when a kind cannot decide, which
this kind never does, since an instance either answers /api/system/info or it does not.
What a block does with it¶
Each block call opens its own client for the length of that call, from the connection's config as it is at that moment. There is no pooled client held between steps, so rotating a credential takes effect on the next step rather than on the next process restart.