Files
egress-proxies-operator/docs/provider-spec-template.md
kacerrandClaude Fable 5.1 d61868fe2d Add the provider spec template
Twenty-four fill-in entries, one per decision the contract leaves to the
provider, numbered to match the contract's profile table.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 11:28:52 +00:00

15 KiB

Provider spec template

Copy this file to docs/<type>-provider-spec.md, fill in every Answer line, and hand the result to whoever implements the provider together with provider-contract.md and providers-existing.md. An unanswered entry blocks implementation: it is a decision the contract leaves to the provider, and the implementer must not guess it.

Where an entry names an axis in backticks, the allowed answers are the profiles of that axis in contract §4, and the entry number matches the last column of the contract's §4.20 table. Evidence is where the answer was confirmed: a link into the backend's API documentation, or the experiment that was run. "Verify against the target version" is a valid interim answer only for the Evidence line, never for the Answer.

Header

  • Type (the type: value in the providers config, lowercase):
  • Example configured name:
  • Backend and API version this spec was written against:
  • Client (vendor-sdk with the module path, or thin-client):
  • Author and date:

1. Identity and providerID

Question. What is the providerID's shape, which location parts does it embed, and what validates the config values pasted into it?

Allowed answers. <location>/<name>, <location>/<allocated-key>, or a location-free <allocated-key> (contract §6; required when instances can migrate, see entry 2).

Obligations. Strict parsing; ErrPermanent for a malformed ID from Get and Delete; a regexp on every config value that becomes an ID segment, in config_<type>.go.

Answer:

Evidence:

2. Primary key (primary-key)

Question. Is req.Name the backend's key, or does the backend allocate an identifier?

Allowed answers. name-is-key; allocated-id.

Obligations. allocated-id: Create finds an existing instance by name before allocating; allocation is safe against a concurrent Create for another Proxy (deterministic from req.UID, or allocate-then-verify); Get and Delete use the allocated key; state how a collision with an instance the operator does not own is detected and reported.

Answer:

Evidence:

3. Placement mapping

Question. What do Placement.Region, Zone, MachineType and Image mean for this backend, which are required, which are ignored, and what happens when a value is unknown?

Allowed answers. Per field: a config catalogue key, a backend identifier passed through, or ignored (contract §2.1, MAY ignore with documentation).

Obligations. Unknown values are rejected with ErrPermanent naming the field and value before any backend call; ignored fields are listed in the provider's guide because they are still in the spec hash; an empty value with a single-choice catalogue may default, and the guide says so.

Answer:

Evidence:

4. Image source

Question. Where does the boot image come from, and what does a catalogue edit do to running instances?

Allowed answers. A catalogue in the config block (key to URL or backend image ID); a backend template or image referenced directly by Placement.Image; a URL fetched by the provider into the backend on first use.

Obligations. If the provider fetches or converts images: streaming, no buffering (contract §1.4), a completeness marker so a cut-off fetch is redone, concurrency control so two Creates fetch once, and a written warning that replacing an image in use may corrupt instances that depend on it. If the backend owns the image: the prerequisite is documented in the guide.

Answer:

Evidence:

5. Cloud-init delivery

Question. How does CreateRequest.CloudInit reach the guest, byte for byte?

Allowed answers. User-data metadata on the instance; a NoCloud seed built in-process and attached as media; a file placed on the backend and referenced from the instance config; not delivered (documented, with how the proxy gets configured instead).

Obligations. Size limits of the channel stated; whether vendor-data or a second document can ride along; the sentinel test (contract §11); the channel must be fully reachable through the API the provider uses (no step that needs shell access on the backend unless the spec says the provider has it).

Answer:

Evidence:

6. Machine type mapping

Question. How does Placement.MachineType become CPU, memory and disk?

Allowed answers. A catalogue of named profiles in the config block; a backend size name passed through.

Obligations. A catalogue needs validation of each profile; disk size default and its config field named.

Answer:

Evidence:

7. Network and IP discovery (readiness, ip-source)

Question. Which network does the instance attach to, where does its address come from, how long until it is usable, and what makes it reachable from the operator pod and from clients on spec.port?

Allowed answers. readiness: immediate; provisioning-window. ip-source: assigned-at-create; discovered (name the mechanism: guest agent, DHCP lease, neighbour table, and the order if several).

Obligations. discovered: what the guest must run, how it gets installed, what happens without it; discovery failure is Provisioning, not an error; only routable IPv4 is reported (state the filter). Reachability: firewall or routing steps the provider performs, or the prerequisite the guide states. Whether Port is used by the provider at all.

Answer:

Evidence:

8. GC marks and CreatedAt (gc-mark, created-at)

Question. Where do LabelManaged and LabelUID live, where does CreatedAt come from, and which secondary resources are marked?

Allowed answers. gc-mark: native-labels; written-metadata; mark-window. created-at: backend-timestamp; written-at-create.

Obligations. mark-window: the window's length in seconds, why it is far below MinAge, and how the same Proxy's next reconcile cleans an unmarked instance. Any value-format constraint of the backend's labels (allowed characters, length) and how a Proxy UID fits it.

Answer:

Evidence:

9. State mapping (provisioning-state)

Question. How does every backend state map to StateProvisioning, StateRunning, StateStopped, StateTerminated, and how does Get tell "defined but never started" from "stopped"?

Allowed answers. A table of backend state (plus lock or task flags) to provider state, with the unknown-state row mapping to StateStopped. provisioning-state: native; create-blocks; marker.

Obligations. Running requires an IP; marker names what is recorded at creation and how Get reads it; create-blocks names the cap on the wait.

Answer:

Backend state Lock or task Provider state

Evidence:

10. Create: blocking and task tracking (create-return, async-ops)

Question. What does Create wait for before returning, and how are asynchronous backend tasks tracked?

Allowed answers. create-return: submit; blocking. async-ops: none; task-handle; backend-lock.

Obligations. task-handle: where the handle is kept and how a failed task surfaces (in Get, as which class, with which message). backend-lock: what Get and Delete return while locked. Every step of Create is idempotent so a cut-off attempt resumes; list the steps and what each finds on a retry.

Answer:

Evidence:

11. Delete and secondary resources (delete-return, secondary-resources)

Question. In what order does Delete tear things down, what does it do when called again mid-delete, and what does Get return meanwhile?

Allowed answers. delete-return: returns-early; waits. secondary-resources: none; cascade (name the flags set at create); explicit (name each resource and how it is found by name).

Obligations. Idempotent at every step; "already deleting" is success and cheap; a location no longer in the config is a logged no-op success; any resource that can outlive the instance without being listable is a documented GC blind spot.

Answer:

Evidence:

12. ListByTag scope and partial failure (list-partial)

Question. What is the full scope ListByTag enumerates, and what happens when part of it is unreachable?

Allowed answers. fail-whole; omit-unreachable.

Obligations. The scope covers everything the provider can create in; server-side filtering on the mark where the backend allows it; a single unreadable instance may be skipped with a log line; the cost of one list at fleet size.

Answer:

Evidence:

13. Error classification (not-found, quota)

Question. Which backend responses map to which of the four classes, and which signal means "does not exist"?

Allowed answers. A table of backend status or code (with message pattern where needed) to class. not-found: status-404; error-code; list-and-miss. quota: detected; absent.

Obligations. One classify, a table test with captured real responses for every matched message; the permission-denied-versus-quota case decided; authentication failures, host-key or certificate failures, and cancellation each assigned a class with a reason.

Answer:

Backend response Class Why

Evidence:

14. Credentials and TLS (credentials)

Question. What credential does the operator use, where does it come from, when is it read, and how is the backend's TLS certificate trusted?

Allowed answers. mounted-files (Secret name, mount path, file names, optional: true); environment; workload-identity. TLS: system-roots; ca-file; insecure (the flag's exact name).

Obligations. The minimum privilege set the credential needs, listed; rotation behaviour (free, or restart needed); the sentinel test for the credential (contract §7); the constructor never dials.

Answer:

Evidence:

15. Config block

Question. What are the fields of <Type>Config, their defaults, and their validation?

Allowed answers. A table: field, required, default, validation, and which identifier or path the value ends up in.

Obligations. Contract §10: setBlocks, the type allow-list in Config.validate and the constructor map in cmd/main.go, config_<type>.go with its tests, regexps for values pasted into IDs or file names.

Answer:

Field Required Default Validation Ends up in

Evidence:

16. Checker

Question. What are the check targets, what does one check do, which reasons can it report, and what goes into Details?

Allowed answers. One target per thing that fails independently; the shared reasons from internal/provider/check.go first, provider-specific reasons only with a stated "what the admin does differently"; Details keys listed.

Obligations. Same credential, verb and scope as ListByTag and Create; cheap (one page, one object); never disrupts a reconcile that shares a connection; returns promptly on ctx; the status.type comment in api/v1alpha1/providerhealth_types.go updated.

Answer:

Evidence:

17. Client and child spans (client-kind, child-spans)

Question. What talks to the backend, and how do its calls appear under the operation span?

Allowed answers. client-kind: vendor-sdk (module path, and whether it is maintained); thin-client; rpc-over-ssh. child-spans: sdk-instrumentation; transport-wrapper; span-decorator; none.

Obligations. The seam interface's name and the fake that implements it; thin-client: one error type carrying status and body, transport timeouts, parent-gated otelhttp; none is a documented gap; the span recorder test (contract §11).

Answer:

Evidence:

18. Timeouts (timeouts)

Question. What bounds each backend call, the dial, and any long step such as a download or a wait loop, and what does a cancelled ctx do to a shared connection?

Allowed answers. per-call (the budgets, per operation); ctx-only (cite the client's own timeout).

Obligations. Contract §1.5: no caller sets a deadline; a hung call pins a worker or stalls GC. Cancellation behaviour on a shared connection stated (abandon the call, or close the connection).

Answer:

Evidence:

19. Concurrency

Question. What is shared between concurrent calls (connections, caches, locks), and what happens when two calls for the same instance name overlap?

Allowed answers. A description of the shared state and its locking.

Obligations. Safe for 3 reconciles, a GC sweep and a check round at once; two Delete calls for one instance overlap safely; one fetch per image per location when several Creates race.

Answer:

Evidence:

20. Logging (logging)

Question. Which tiers does the provider log, with which keys on every line?

Allowed answers. tiered; outcome-only; none (cite the client's own logging).

Obligations. Logger from logf.FromContext(ctx) on every call; never CloudInit or credentials; provider and the instance name on every line that concerns an instance.

Answer:

Evidence:

21. Tests (backend-test)

Question. What is the fake, which contract tests exist, and how is the provider proven against the real backend?

Allowed answers. backend-test: cluster-e2e; integration-tag (the <TYPE>_TEST_* variables, the make test-integration-<type> target); manual-runbook.

Obligations. Every item of the contract §11 list, ticked here:

  • repeat Create
  • Get after Delete
  • Delete twice
  • malformed ID
  • unknown state maps to StateStopped
  • StateRunning requires an IP
  • UID and CreatedAt read back
  • ListByTag excludes unmarked instances
  • Get during an in-progress delete
  • Get during an asynchronous operation or lock
  • never started is never StateStopped
  • cancelled ctx returns promptly
  • classify table with captured responses
  • Checker reasons, expired ctx, stable order
  • sentinel tests for CloudInit and the credential
  • child spans under the operation span
  • real-backend lifecycle test

Answer:

Evidence:

22. Deployment

Question. What changes in config/ and the Makefile?

Allowed answers. The list: the optional: true mount in config/manager/manager.yaml; the commented entries in config/manager/providers_config.yaml and config/samples/providers-config.yaml; config/samples/proxy_<type>.yaml with a cloud-init that installs a proxy on the sample image; a Makefile helper for the Secret; RBAC markers if the provider talks to the cluster's own API; whether hack/providers-dev.yaml gets an entry.

Answer:

Evidence:

23. Docs

Question. Which documents are written or updated?

Allowed answers. docs/<type>-provider.md with its section list; the row and section in providers-existing.md; the architecture.md rows (components table, §5, §11, and §10 if spans are added); the README row; the docs/testing.md section; the CHANGELOG.md entry after confirmation.

Answer:

Evidence:

24. Known limits accepted at design time

Question. Which of the contract's §13 limits bite this provider in practice, and which new gaps does this design accept?

Allowed answers. A list, each with the consequence for an operator and the workaround.

Answer:

Evidence: