Expand description
upcloud-api — the UpCloud API 1.3 surface as ONE trait, ONE wire, ONE
decision about which cloud answers.
§Why this crate exists
On 2026-09-21 the nordisk estates had five UpCloud clients:
private-gunnar-ops’s xtask (two of them, until lane T3 made them one),
gunnar/deploy/upcloud (the re-image procedure, with its OWN scripted fake),
gunnar-loadbench, private-holger-ops’s xtask, and monetize-cloud-impl.
Each one spelled https://api.upcloud.com/1.3 for itself. Each one decided
for itself whether a run was aimed at a fake. Four bugs that day were the
same bug: a mock run reached the account because one client in the chain
decided differently from the rest — the worst of them was
gunnar-upcloud’s Api::new(DEFAULT_BASE), so cargo xtask mock reimage --apply would have re-imaged the LIVE appliance with the estate’s token
while the banner said FAKE.
A test against the fake is only worth something if the code under test is the SAME code that runs against the account. So:
UpCloudApiis the only surface. No method takes a path, a query string or a base URL, so no caller can name an endpoint.- There is one wire implementation. It builds every request for the
account and for a mock the same way, from the same code — only the base
differs. A run against
mock-upcloudtherefore exercises the exact URL, body and header construction a run against the account does. - The base comes from an
Endpoint, and anEndpointis decided ONCE, at the edge of the program, from what the operator TYPED (Endpoint::account/Endpoint::mock). A mock endpoint is loopback-only by construction; the account endpoint refuses to exist while the shell carries a mock variable (MOCK_ENVS). - The in-process fake —
FakeUpCloudovermock-upcloud’sEstate— lives beside that state machine in themock-upcloudcrate, so a fault armed once applies to terraform (the HTTP face) and to Rust callers (the trait face) alike: one world, two faces. overis where a typed call becomes ONE method, path and body: the wire and every fake are anExchangeunderOver, so a fake answers the exact request the account would have been sent.guardis the test every consuming repository runs: nothing outside the files it names may spell the provider or build an API path.
§The method set is DERIVED, not designed
Every method exists because a real call site in one of the ported clients calls that endpoint. The doc on each names the caller. A general-purpose UpCloud client is a second thing to keep true; this is not one.
Re-exports§
Modules§
- body
- The JSON each write sends. Public so an in-process fake reads the SAME bodies the wire sends rather than a parallel copy of UpCloud’s quirks.
- guard
- The test every consuming repository runs: nothing outside the files it names spells the provider or builds an UpCloud API path.
- net
- The transport boundary: what is retried and what never is.
- over
- One spelling of UpCloud’s paths, under every implementation.
Structs§
- Mock
Base - A loopback base ending
/1.3. The field is private, so the only way to hold one isEndpoint::mock— which is what makes “a fake pointed at a real host” unrepresentable rather than merely refused. - NewStorage
- A new storage.
- Options
- Timeouts. A single API call and the upload of a multi-hundred-MiB medium cannot share one budget without one of them being wrong.
- Reply
- One answer from the API, with NO judgement about its status. The callers
decide what a code means, because they genuinely disagree: a
404onGET /server/{uuid}is a FACT for a sweep and a REFUSAL for a resize. A transport failure is theErrand is never a status.
Enums§
- Backups
- What a storage’s delete does with the storage’s backups.
- Boot
Order - What the hypervisor boots first on its next START (a guest reboot does not re-read it — MEASURED 2026-09-14).
- Console
- The VNC console.
Vncre-provisions host AND port; the reply carries them. - Credential
- How the wire authenticates. The account’s clients use a token; monetize’s plugin can also be configured with a sub-account’s username and password.
- Device
Kind - How a device rides on a server.
- Endpoint
- Which UpCloud answers. Built only by
Endpoint::account(refuses in a mock-carrying shell) orEndpoint::mock(refuses anything off loopback). - Stop
- How a server is stopped.
Softcarries a grace in seconds;Hardpulls the plug, and is the word a delete needs first. - With
Storages - Whether a server’s delete takes its storages with it, and what becomes of their backups. UpCloud spells this as a query string; no caller spells it.
Constants§
- ACCOUNT_
BASE_ FOR_ DISPLAY - The account’s base, for a REPORT and a display comparison — never to build a
request from; nothing can be called on a
&'static str. - MOCK_
API_ FLAG - The flag
Endpoint::child_argsemits andEndpoint::from_flagreads. - MOCK_
BASE_ ENV - The variable a mock-selected run reads its loopback base from. On an
account-selected run its PRESENCE is a refusal (
Endpoint::account). - MOCK_
ENVS - Every variable whose presence means “this shell is aimed at a fake”.
- TF_
MOCK_ BASE_ ENV - The terraform provider’s own debug knob. Not read here — named so an account run can refuse when the shell carries it.
Traits§
- UpCloud
Api - The UpCloud API 1.3 surface the nordisk estates actually use. Typed in, typed out. Not one method takes a path, a query string or a base URL.
Functions§
- connect
- The only constructor. The implementation a run holds is a function of
the
Endpoint, and anEndpointis only ever built from what the operator typed. - delete_
server_ query - The query a delete sends — ONE spelling, for the wire and for any fake that records what a call means.
- delete_
storage_ query - See
delete_server_query. - is_
loopback http://127.0.0.1:PORTorhttp://localhost:PORT, and nothing else. No IPv6 — these estates have none by law, and[::1]is refused with the rest.- label_
query ?label=k%3Dv&label=…, or""— every character outside RFC 3986’s unreserved set percent-encoded, so a label is never read as query syntax.- redact_
upload_ url https://fi-hel1.img.upcloud.com/uploader/session/<secret>→…/uploader/session/…: the session id IS a credential (anyone holding it can write the storage a box boots from), so it is never printed whole.
Type Aliases§
- Label
- One label,
key=value. UpCloud filters lists by it (?label=key%3Dvalue) and carries it on storages and servers.