Skip to content

Add a render variable for the manager endpoint (#79) - #81

Merged
sehkone merged 2 commits into
mainfrom
sehkone/issue-79
Aug 29, 2026
Merged

Add a render variable for the manager endpoint (#79)#81
sehkone merged 2 commits into
mainfrom
sehkone/issue-79

Conversation

@sehkone

@sehkone sehkone commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Closes #79

Summary

RenderVar gains ManagerEndpoint, serialized manager-endpoint, so a unit template can name the manager endpoint its module is pointed at instead of a package baking one deployment's manager in as a literal. One variant rather than three, because the value is a single argv element and Arg concatenates nothing.

Its rustdoc carries the four-part contract: the value is <server_name>@<address>:<port> as one argv element; server_name is the peer's internal-mTLS server name, a certificate identity the module verifies the presented leaf against rather than anything it resolves; address is a numeric address and never a name, because consumers parse this into a name plus a numeric socket address; port is the peer's RPC port. That contract is documentation for the caller composing the value. Nothing in the crate splits on @, validates the port, or rejects a name in the address position — the format belongs to the consuming module's own argument parser, and a second parser here would be free to drift from the one that actually reads it.

RenderContext gains manager_endpoint: Option<&'a str>, following service_account's precedent for a genuinely absent value. resolve_var refuses a template that names the variable against a None field with a new RenderError::UnresolvedVariable { var }, in the shape UnrepresentableValue already uses; a Some value is substituted verbatim, subject only to the existing representability check. The variant is permitted wherever an Arg is — no rule analogous to MainPid's confinement to exec_reload was invented, because there is no systemd behaviour to read one off.

MANIFEST_FORMAT_VERSION and MAX_MANIFEST_FORMAT_VERSION move 3 → 4 so an older build's refusal of a manifest naming the variable is legible as UnsupportedManifestFormat rather than an opaque decode error. MIN_MANIFEST_FORMAT_VERSION stays at 3: the variant is additive, so every already-published payload stays readable and installable.

Both fixtures that used manager-address as the canonical unknown variable now use no-such-variable, since a fictional name one letter from a real one is a trap for the next reader.

One thing worth a reviewer's eye

assets/test-fixtures/unsigned-container/manifest.json changes by one byte — "format_version":3 to 4. That fixture pins the unsigned writer's manifest bytes against the live writer, so a deliberate producer bump necessarily moves it; the block layout, lengths and every other byte are unchanged.

Test plan

  • ManagerEndpoint round-trips as manager-endpoint in both directions, and a template naming it decodes as one argv element.
  • A unit whose exec_start ends with the variant renders the context's value as the final argument, verbatim and as one element.
  • A None context plus a template naming the variant returns RenderError::UnresolvedVariable, and the error names the variable.
  • A None context plus a template not naming the variant renders exactly as it does today.
  • An unrepresentable resolved value reports through UnrepresentableValue with var set to the new variant — the variant joins HOST_RESOLVED_VARS, so the existing sweep also proves it reads its own context field.
  • A byte-for-byte golden covers a unit that uses the variant, anchored on the hand-rendered agent unit that already carries this argument.
  • The existing anchor goldens for templates that do not name the variant pass unchanged.
  • The variant renders in all four positions an Arg may occupy, and a None context refuses each of them.
  • A manifest at MIN_MANIFEST_FORMAT_VERSION still decodes, validates and renders its unit byte-for-byte; one above MAX_MANIFEST_FORMAT_VERSION is refused with UnsupportedManifestFormat. The range test pins the ceiling to the producer and the floor no higher, with the floor relation in a const block so raising it past the producer is a build failure.
  • The unknown-variable tests still assert an unknown name fails to deserialize, under their new fictional name.
  • A producer-version manifest whose wire spec names manager-endpoint decodes through the read path with its validator and renders, both with a supplied endpoint and with none.
  • cargo fmt --check, both clippy runs, cargo doc with -D warnings, and both test runs are green.

Three of the modules this crate's consumers package take the manager
they report to as a mandatory positional argument, and no combination
of the ten existing `RenderVar` variants composes it: `Hostname` is the
module's own host, `Domain` is the mTLS domain alone, and `Arg`
concatenates nothing, so three variants would render three arguments
where the module's parser expects one. The one remaining expressible
answer, a literal, bakes a single deployment's manager into a package
meant to be the same bytes on every host, and a module would then
verify the wrong peer's certificate.

`ManagerEndpoint` closes that gap as one argv element resolved on the
host. Its context field is optional because a module with no such peer
has nothing to supply, and a template naming it against a `None` field
is refused outright rather than rendering a default, an empty string or
a placeholder: the value reaches a root-executed `ExecStart=` and
decides which peer a module trusts. The crate parses none of it. The
format belongs to the consuming module's own argument parser, and a
second parser here would be a second definition of it, free to drift
from the one that actually reads it.

The producer's version and the ceiling of the accepted range move with
the schema, so an older build refuses a manifest naming the variable as
an unsupported format rather than as an opaque decode error. The floor
does not move, because the variant is additive: every payload already
published stays readable, and no release asset has to be republished.
The unsigned-writer fixture follows the producer, its one changed byte
being the version the writer stamps.

Closes #79
Every test naming the new variable either hand-built the unit template
or exercised the floor case, where the variable is absent. What none of
them covered is the path the version bump was cut for: a producer's
manifest whose wire spec names `manager-endpoint`, decoded through the
read path with its validator, and rendered.

So assert it end to end at the current version, both ways — the value
substituted verbatim as the final argument when the caller supplies
one, and the refusal when it does not. The floor test's context literal
becomes a helper the two share, since they now differ in exactly the
field under test.

Part of #79
@sehkone sehkone changed the title Add a render variable for the manager endpoint Add a render variable for the manager endpoint (#79) Aug 29, 2026
@sehkone

sehkone commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

[Reviewer Round 1]

Approved — no findings.

The PR implements Issue #79 as specified:

  • Adds and documents the closed RenderVar::ManagerEndpoint vocabulary in src/module_spec.rs.
  • Resolves it through optional RenderContext::manager_endpoint and returns a typed UnresolvedVariable error for None in src/render.rs and src/render.rs.
  • Bumps producer/max manifest format to 4 while retaining floor 3 in src/manifest.rs.
  • Includes meaningful rendering, absent-value, representability, placement, wire-format, and manifest read-path coverage.

PR linkage and test-plan hygiene are also correct. I inspected the diff and review metadata; I did not run the test suite, per instruction.

@sehkone

sehkone commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

[Review Verdict Round 1: APPROVED]

@sehkone

sehkone commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Suggested squash commit

Title

Add a render variable for the manager endpoint

Body

A module that takes its manager as a mandatory positional argument had
no way to say so in a declared spec. The ten existing `RenderVar`
variants all describe the module itself, its host, or systemd; none
names another component, so the only expressible answer was
`Arg::Literal`. That bakes one deployment's manager into a package
meant to be the same bytes on every host, and the module would then
verify the wrong peer's certificate.

`ManagerEndpoint` is one variant rather than three because the value is
a single argv element and `Arg` concatenates nothing. Its rustdoc
carries the contract the caller composing the value owns — a
certificate identity, a numeric address, a port — as documentation
rather than a check: the format belongs to the consuming module's own
argument parser, and a second parser here would be free to drift from
the one that actually reads it.

`RenderContext::manager_endpoint` is an `Option`, following
`service_account`, because the value is genuinely absent for a module
with no such peer. A template naming the variable against `None` is
refused with `RenderError::UnresolvedVariable` rather than rendering a
default, an empty string or a placeholder; the resolved value reaches a
root-executed `ExecStart=` and decides which peer a module trusts.

The producer and the accepted ceiling move so an older build's refusal
of a manifest naming the variable is legible as
`UnsupportedManifestFormat` rather than an opaque decode error. The
floor stays: the variant is additive, so every already-published
payload still decodes, validates and renders unchanged.

Both fixtures that used `manager-address` as the canonical unknown
variable now use `no-such-variable`, since a fictional name one letter
from a real one is a trap for the next reader.

Closes #79

@sehkone
sehkone merged commit 173b150 into main Aug 29, 2026
4 checks passed
@sehkone
sehkone deleted the sehkone/issue-79 branch August 29, 2026 06:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Let a unit template name the manager endpoint it is pointed at

1 participant