Skip to main content

Module http_store

Module http_store 

Source
Expand description

HttpEnvironmentStore — remote HTTP-backed implementation of EnvironmentMutations.

Talks to a future greentic-operator-store-server (PR-4) over the A8 HTTP contract specified in greentic_deploy_spec::remote. JSON over the wire, reqwest::blocking::Client for transport so the sync EnvironmentMutations trait stays sync (no Tokio runtime needed at call sites — see project memory project_next_gen_deployment_phase_b for the block_in_place panic precedent that rules out async-in-sync).

§Route table

Every mutation maps to a single HTTP endpoint. The server (PR-4) mirrors this table.

Trait methodMethodPath
create_environmentPOST/environments
update_environmentPATCH/environments/{env_id}
migrate_merge_bindingsPOST/environments/{env_id}/migrate-bindings
stage_revisionPOST/environments/{env_id}/revisions
warm_revisionPOST/environments/{env_id}/revisions/{rid}/warm
drain_revisionPOST/environments/{env_id}/revisions/{rid}/drain
archive_revisionPOST/environments/{env_id}/revisions/{rid}/archive
add_bundlePOST/environments/{env_id}/bundles
update_bundlePATCH/environments/{env_id}/bundles/{deployment_id}
remove_bundleDELETE/environments/{env_id}/bundles/{deployment_id}
add_pack_bindingPOST/environments/{env_id}/packs
update_pack_bindingPATCH/environments/{env_id}/packs/{slot}
remove_pack_bindingDELETE/environments/{env_id}/packs/{slot}
rollback_pack_bindingPOST/environments/{env_id}/packs/{slot}/rollback
add_extension_bindingPOST/environments/{env_id}/extensions
update_extension_bindingPATCH/environments/{env_id}/extensions
remove_extension_bindingDELETE/environments/{env_id}/extensions
rollback_extension_bindingPOST/environments/{env_id}/extensions/rollback
set_traffic_splitPOST/environments/{env_id}/traffic
rollback_traffic_splitPOST/environments/{env_id}/traffic/rollback
add_messaging_endpointPOST/environments/{env_id}/messaging
link_messaging_bundlePOST/environments/{env_id}/messaging/{eid}/link
unlink_messaging_bundlePOST/environments/{env_id}/messaging/{eid}/unlink
set_messaging_welcome_flowPOST/environments/{env_id}/messaging/{eid}/welcome-flow
remove_messaging_endpointDELETE/environments/{env_id}/messaging/{eid}
rotate_messaging_webhook_secretPOST/environments/{env_id}/messaging/{eid}/rotate-secret
bootstrap_trust_rootPOST/environments/{env_id}/trust-root/bootstrap
seed_trust_root_if_absentPOST/environments/{env_id}/trust-root/seed
add_trusted_keyPOST/environments/{env_id}/trust-root/keys
remove_trusted_keyDELETE/environments/{env_id}/trust-root/keys/{key_id}
load_environmentGET/environments/{env_id}

load_environment is the one READ verb (no idempotency key, no audit envelope) — the remote dispatch uses it to evaluate client-side preconditions such as the warm health-gate’s expected lifecycle.

The backup/restore group (A8 #5, PR-4.4) is server-only — LocalFsStore has no implementation, so these are inherent methods on HttpEnvironmentStore, not EnvironmentMutations verbs:

Inherent methodMethodPath
create_backupPOST/environments/{env_id}/backups
list_backupsGET/environments/{env_id}/backups
delete_backupDELETE/environments/{env_id}/backups/{backup_id}
restorePOST/environments/{env_id}/restore

§Headers

  • Content-Type: application/json / Accept: application/json on every request.
  • Authorization: Bearer <token> when AuthMethod::Bearer.
  • Idempotency-Key: <ulid> when the payload carries an IdempotencyKey.

§ETag / CAS

The store does ETag-chained optimistic concurrency entirely at the transport layer — no change to the EnvironmentMutations trait or any CLI call site. Per environment, it remembers the last strong ETag it observed (from the [MutationEnvelope] of every successful mutation, and from load_environment’s response) in [Self::cached_etag], and replays it as a quoted If-Match header on the next mutation against that env. The server (A8 §1) then rejects the write with 412 if any other writer advanced the environment since that ETag. The cache is keyed by env because one store represents a remote endpoint, not a single env.

This catches the two windows a per-invocation CLI actually exposes:

  • read-then-decide-then-write within one command (e.g. revisions warm reads the lifecycle, runs health checks, then writes) — the write is pinned to the ETag the decision was based on;
  • multi-write commands (e.g. op env apply) — each write is pinned to the ETag the previous write returned, so a concurrent writer slipping between two of our writes is caught.

A single-write command on a fresh store has no cached ETag, so it sends no If-Match; the server’s own load-pinned generation (a torn-write guard) still applies. Cross-invocation lost-update protection (one operator overwriting another’s separately-issued command) needs an explicit caller-supplied expectation and is a separate, opt-in follow-up. A 412/428 clears the cache so a stale ETag is never replayed.

LocalFsStore needs none of this — its flock serialises writers — so CAS lives only here, not on the shared trait.

§Error mapping

Transport errors (connection refused, timeout, TLS handshake) map to StoreError::Conflict("transport: ..."). A dedicated StoreError::Transport variant would be cleaner but adding a new enum variant cascades into every match site — follow-up.

§Follow-ups

  • Explicit cross-invocation CAS (caller-supplied --expect-generation)
  • StoreError::Transport variant
  • AuthMethod::Mtls for production (mTLS)
  • PR-3c wires dispatch between LocalFsStore and HttpEnvironmentStore

Structs§

HttpEnvironmentStore
Remote HTTP-backed implementation of EnvironmentMutations.

Enums§

AuthMethod
How the client authenticates to the remote store server.
ConstructionError
Errors that can occur when constructing an HttpEnvironmentStore.