mirror of
https://github.com/haproxy/haproxy.git
synced 2026-07-11 02:02:18 -04:00
The backport review page keeps the human edits (verdict overrides and
notes) only in the loaded DOM: they are lost on reload and never shared
between reviewers. This adds the server side of the shared persistence
design: update.awk, a GNU awk CGI script which stores these edits into
one file per major branch (e.g. "3.5") inside a dedicated git
repository, one line per touched commit:
<commit_id> [state <n|u|w|y>] [notes "<quoted notes>"]
The overlay only ever stores human edits keyed on the commit id; the AI
verdict and explanation stay in the generated HTML. Commit ids are
length-agnostic and matched by symmetric prefix (first match wins), so
the current 8-char pipeline and a future 12-char one both work without
any migration.
POST applies line-oriented directives ("<cid> state <n|u|w|y|revert>",
"<cid> notes <text to append>"), none of which carries a base value:
states are last-write-wins and notes are append-only (capped to 500
chars per push and sanitised so that no newline may ever enter a stored
line), which keeps concurrent edits conflict-free. Broken directives,
fields or lines are silently ignored, never fatal, and lines not being
modified are preserved byte-for-byte so that admin hand-edits survive.
Writers are serialised by a mkdir lock at an obvious place (<repo>/lock)
with PID-gated crash takeover via atomic rename, the file itself is
replaced by an atomic rename from a temp file inside the lock dir, and
every resulting state is committed to git, which acts as the event log
(git blame/log -L provide the full history). A crash at any point leaves
at worst a stale lock (reclaimed on the next write) or a valid but
uncommitted tree (folded into the next commit), never a broken file.
The takeover races are covered: the staleness decision and the takeover
rename are not one atomic operation, so the thief verifies after the
rename, discarding the stolen dir only if it still carries the pid that
was judged dead and renaming it back in place untouched otherwise; the
victim redoes the whole locked cycle from a fresh read when its final
rename fails, since nothing was applied to the branch file yet; a writer
finding its own pid in the lock adopts it as stale; and the release only
removes the lock after checking that it still contains our own pid.
A few awk specifics are worth noting: external commands (git, mkdir,
mv) go through /bin/sh, so everything interpolated into a command line
is shell-quoted (single-quote is escaped and the argument placed inside
single quotes); the -b (bytes) flag keeps all string operations byte-
based regardless of the locale; and a manual argv parsing due to gawk
silently consumes a leading "-r" argument, ignoring ours.
Also note that gawk uses a file cache for getline() and co, which opens
lots of traps so we need to be extremely careful about properly closing
files if we want to check for changes (e.g. lock's pid file).
Finally, writing through a redirection whose target cannot be opened is
a fatal awk error terminating the script without even a response, so the
writes into the (stealable) lock dir are arranged to resist a takeover:
the pid is written through the shell, where a vanished dir is a plain
command failure, and the temp file is opened the very instant the lock
is acquired, its descriptor surviving a later theft. The update.cgi
wrapper considers any >0 return code as a failure and returns a generic
error as it will indicate that the awk script itself couldn't produce
a valid response.
For now, only the POST ("save changes") action is implemented.
41 lines
2.1 KiB
Bash
41 lines
2.1 KiB
Bash
#!/bin/sh
|
|
# Example deployment wrapper for the storage backend of the patchbot review
|
|
# page. This wrapper is the only web-exposed piece: copy it next to the
|
|
# generated HTML pages (the page reaches it with a bare relative URL),
|
|
# adjust the paths below, and make sure the web server has the "**.cgi"
|
|
# pattern enabled so that this file is executed, not served (or its content,
|
|
# including the repository path, would leak).
|
|
#
|
|
# All deployment-specific settings belong here, as arguments: the backend
|
|
# itself (update.bin or update.awk, both honour the same interface) and the
|
|
# overlay git repository (with a configured committer identity) must both
|
|
# stay outside the document root.
|
|
#
|
|
# The wrapper supervises the backend rather than exec'ing it, relying on a
|
|
# simple contract: the backend always exits zero once it has emitted a
|
|
# response (including its own error responses), so any other termination
|
|
# means no response was produced (e.g. a fatal interpreter error) and a
|
|
# generic error is emitted instead, so that the browser always receives a
|
|
# response. The backend computes everything before emitting its response in
|
|
# one final burst, so a death with partial output is not a realistic case
|
|
# (and would anyway yield an unparsable response that the page treats as a
|
|
# failed save with the edits kept). The backend's stderr is discarded
|
|
# because the web server would send it to the client ahead of the response
|
|
# headers and corrupt them; point it to a file instead of /dev/null when
|
|
# debugging.
|
|
|
|
# The web server usually starts CGIs with a restricted compile-time PATH
|
|
# (e.g. thttpd's CGI_PATH): make sure it contains git, or adjust it here,
|
|
# otherwise nothing gets committed in the storage repository.
|
|
PATH=/usr/local/bin:/usr/bin:/bin
|
|
export PATH
|
|
|
|
# where the backend lives (update.awk or update.bin), and the storage repo
|
|
SCRIPTS_DIR=/home/patchbot/prog/bin
|
|
REPO_DIR=/home/patchbot/data/overlay
|
|
|
|
if ! "$SCRIPTS_DIR/update.awk" -r "$REPO_DIR" 2>/dev/null; then
|
|
printf 'Status: 500 Internal Server Error\r\n'
|
|
printf 'Content-Type: text/plain\r\n\r\n'
|
|
printf 'backend error, nothing was saved\n'
|
|
fi
|