Repository navigation
feat(services): mount built-in service files from the release, not the checkout - #634
Merged
Merged
Conversation
…e checkout The ops-agent's bundle embeds every file a built-in mount names, with its executable bit and the history of its versions, and the release authority digest covers them. Render writes them into content-addressed trees in state/ops-agent/service-files, which a generation records and its containers mount read-only; Valhalla's valhalla.json gets a writable copy in data/valhalla/config, seeded from the release's file. The OTP and Pelias builds read the applied generation's copies.
Containers write the data directory, so one could replace a directory on the copy's path with a link and steer render's write elsewhere. Render now creates each directory below infra/docker/data as a real one and checks, right before the rename, that the path still resolves there.
Checking the path again before the rename still left a window in which a container could swap a directory for a link. On Linux each directory below infra/docker/data is now opened relative to the one above it, through /proc/self/fd, and the copy is written and renamed there; elsewhere (development) the path check stays.
…rate lstat Opening with O_DIRECTORY|O_NOFOLLOW refuses a link or a non-directory atomically; the lstat before it was a second, racy look. The container test reads Valhalla's copy and its inode through one descriptor.
…EASE=local, bundled or not
Overrides under infra/docker/overrides/services, the one-time import of checkout edits (only from a checkout at or behind the release; Traefik edits refuse while it waits), the host-render rule, and unusable-override errors in the admin panel. Stacked on #634, which lands it on main.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed and why
This is PR 5a of the plan to stop mounting the whole checkout into the operations agent. Built-in services now mount the files of the release the stack runs, not the checkout's
services/. 5b (#635) is merged into this branch, so this PR lands both on main. 5b adds overrides, the one-time import of checkout edits, the host-render rule and unusable-override errors in the admin panel; its description has the details. 5a never reaches main alone: on its own, it would silently drop an operator's edits, a Traefik basic-auth gate included.The release's files.
scripts/service-files.mjs(pnpm generate:service-files) writesembedded-service-files.bundle.ts.new URL(…), every file a built-in mount names (relative and@service:sources, directories expanded throughgit ls-files) and Traefik's configuration: 21 files.assets/, so the bundle digest covers them. The Dockerfile's bundle stage copies them (check-dockerfile-syncverifies that).services/service-file-history.json: the sha256 of every version each file had in Git history. 5b's import uses it.pnpm check-service-filesin the lint job, unshallowing first. It fails when the module is stale, or the history lacks any version, the current one included. The file is generated, never edited by hand.release1_…) covers each file's path, executable bit and bytes, and the history.Where a generation's containers find them. They go in content-addressed trees next to the generations,
infra/docker/state/ops-agent/service-files/<id>/<sha256>/<path>, not inG/service-files/:safeGenerationTreeon every generation. It refuses any directory that isn't 0700 and any file that isn't 0400 or 0600. A generation holding the copies would leave a rolled-back agent and CLI unable to readcurrent.service-files.json(0600), so it still fully identifies its bytes.d4da20793in a fixture, on generations this release writes.state/ops-agentabove it doesn't stand in their way.compose uprecreates exactly that service.docker inspect. When Docker can't tell, nothing is removed.@service:sources into the tree. The OTP graph build (build-config.json) and the Pelias build (pelias.json) mount the applied generation's copy, throughappliedServiceFile.OPENMAPX_RELEASE=local. The checkout's files are the release's, for the bundled agent and its fixed CLI as for the host CLI. Before, a bundled process rendered its image's files whatever the selector said.Traefik.
traefik.yml(in the generation) and the dynamic directory (in the agent's state, #633) now take the release's files, instead of mirroring the checkout. Running from source, a checkout file render may not use fails the render. Before, it was skipped and its last copy kept.Valhalla's writable copy.
valhalla.jsonmount withwritableCopy: "@infra:data/valhalla/config/valhalla.json". This is a new optional bind-mount field in@openmapx/extension-contracts; built-ins only, since community services can't declare bind mounts.state/ops-agent/writable-copies.json. In between,configure_valhalla.sh's rewrite stays.data/, so render never writes through a planted link:data/is created as a real directory;/proc/self/fd, and the copy is written and renamed there;check-ops-authorityrefuses a writable copy that reaches the agent's state.Verification
Real containers (
service-files.integration.test.ts, run in CI'sapi-privacygroup after pulling the pinned well-known and Valhalla images; PostGIS's image is that job's service image):state/ops-agentat 0700:nginx, serveassetlinks.jsonfrom the tree's 0755 directory;valhalla-scriptedruns its ownbuild_tilesstart, with tiles present and serving off:concurrency: "12", the release'sdrain_secondskept);Store (
service-file-store.test.ts):@service:resolved into pelias's tree;martin.yamlgets a new path while PostGIS's stays;release-service-files-local.test.ts, with the bundle's modules swapped in: the bundle's files by default, the checkout's underOPENMAPX_RELEASE=local;Gate C: a full real-manifest render mounts each service's files from its own tree, read-only. Nothing is mounted from
services/, and Valhalla mounts its copy.Image: I built the ops-agent image locally from this branch. Its bundle embeds the 21 files, and the fixed CLI starts.
Checks:
tsc --noEmitfor core, CLI, ops-agent, API and extension-contracts;check-service-files,check-ops-authority,check-dockerfile-sync,check-openapiandcheck-api-sdk. The OpenAPI document and generated admin model gain the optionalwritableCopy.Not performed:
Nothing on the production host was changed.
Release and upgrade impact
Product impact: migration, operator-action
release-notes/service-files-from-the-release.md, from 5b, covers both layers. It covers the one-time import and reviewing its list, overrides from now on (underumask 022), and selecting the release before a host render. It also says that the first stack-widecompose uprecreates every service whose mounts moved, PostGIS and Traefik included, and covers the PR 8 wait and rollback..changeset/service-bind-mount-writable-copy.md:@openmapx/extension-contractsminor (thewritableCopyfield),@openmapx/extension-clipatch.