Commit Graph
3 Commits
Author SHA1 Message Date
Brad Ison 9b06290240 [management] Honour If-Match on Agent Network settings writes
With the validator derived and the helper in place, wire both together: reads
and writes hand back an ETag, and PUT and DELETE honour an If-Match against it.
A client that plans against one read and writes the whole object back now gets
refused instead of silently reverting whatever changed in between.

The comparison happens inside the write's own transaction, under the row lock
the fetch already takes, rather than in the handler. Comparing before the
transaction merely narrows the race: two writers read the same row, both find
their precondition satisfied, and both then write. That is the lost update
this is meant to prevent, and the concurrency test added here fails against
that shape while passing against this one.

It also runs ahead of each operation's other guards. A caller stale enough to
be holding an old validator is stale in its identity echo too, so answering
"you are working from an old read" is more useful than "the endpoint is
immutable"; on delete it matters more still, because the provider guard raises
the same 412 and would send a stale client hunting for providers it may not
know exist.

If-Match stays optional. The dashboard is human-driven, where last-write-wins
is acceptable and a surprise 412 is worse than a benign overwrite; Terraform
is machine-driven and can opt in. Making it mandatory would cost a dashboard
change and should use 428 when that day comes.

GET emits a validator for the pre-bootstrap defaults as well: they are a
representation like any other, and validating them means an If-Match taken
before bootstrap cannot match the row that appeared since.
2026-08-11 11:28:17 +02:00
Brad Ison ebfdf7d7b8 [management] Rework Agent Network endpoint identity and settings bootstrap (#7085)
Store the per-account gateway endpoint as {domain, proxy_address} with a
global unique index on the full hostname; dedicated = (domain ==
proxy_address). Bootstrap becomes an explicit POST carrying exactly one
of proxy_address (server allocates an adjective-noun label beneath it)
or endpoint (claimed verbatim, address-first); provider create loses its
bootstrap side effect. PUT is a full replace with every field required —
the immutable identity fields must be echoed unchanged and a mismatch is
rejected with 422. A guarded DELETE releases the endpoint: refused with
412 while providers exist or a proxy is actively serving the endpoint
hostname (matched case-insensitively); re-creating bootstraps fresh. A
self-addressed pin excludes its address from the account's cluster allow
list, and the live mapping update path now addresses the serving proxy
from the synthesized service. Existing rows are migrated on all three
store engines.
2026-08-10 19:06:55 +02:00
Maycon Santos bc7a15ab71 [management] Align agent-network API contracts for API clients (#7026)
## Describe your changes

Work on the Terraform provider (terraform-provider-netbird #177–#183)
surfaced places where the agent-network API broke its own contracts or
deviated from the conventions the rest of the management API follows,
forcing client-side workarounds.

Settings reads now follow the settings-endpoint convention: GET always
answers with a JSON object. Before bootstrap it returns the defaults
with an empty cluster/subdomain/endpoint (previously 200 with a JSON
`null` body, while the spec said 404). The settings PUT can bootstrap
the account by carrying a `cluster` — previously the row could only come
into existence through the first provider create, and a settings-first
setup was impossible; a differing cluster on a bootstrapped account is
rejected instead of silently ignored. PUT remains full-state.

The provider PUT schema promised omit-preserves semantics for several
operator-editable fields that the handler never delivered (it builds the
row from the request, like every other update handler). The schema
wording now matches the shipped full-state behavior; only the api_key
(secret) and session keys stay preserved by the manager. Identity
headers are always present in provider responses so an explicitly
cleared value round-trips as an empty string.

The Go REST client gains the full agent-network surface (catalog,
providers, policies, guardrails, budget rules, settings), including a
shim translating the legacy 200+`null` settings body from older servers
into an `IsNotFound` error.

Note for reviewers: the dashboard special-cased the `null` settings
body; it needs a small follow-up for the new defaults response (in
progress).
2026-08-04 01:59:09 +02:00