Repository navigation
feat(git): check git stacks on a schedule, deploy new commits, and try a deploy first (#138, part 4) - #181
Merged
Conversation
…edule git.json gets an optional autoDeploy key: what the scheduled check (next commits) does when a new commit changes the stack. "off" does nothing and is the default, so it is left out of the file and a stack written before this still loads; "notify" sends a notification; "deploy" deploys it. compose-git auto-deploy <stack> off|notify|deploy sets it, and status shows it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failed deploy pauses a git stack: the scheduled check (next commits) leaves it alone until a deploy works. compose-git resume <stack> clears the failure for someone who fixed the stack another way, by hand or with the web UI's Update, keeping the deployed commit as it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The web UI's deploy turns the stack's wait-for-healthy settings into compose.sh's --wait arguments inline; that moves into gitDeployWaitArguments() in Helpers.php, so the scheduled deploy (next commit) waits the same way. compose-git deploy's --wait arguments end in the same function, so the three cannot drift. No change in behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…do not throw withStackLock() throws when another operation holds the stack's lock. tryStackLock() is the part that takes the lock, returning null instead, for a caller that treats a busy stack as "try again later" rather than as an error: the scheduled check in the next commit. withStackLock() now uses it; nothing changes for its callers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
compose-git scheduled-check makes one pass over the git stacks whose auto-deploy is not off (the next commit runs it from cron): - It asks the remote for the branch's latest commit, once per repository, branch and credential in a pass, and fetches only when that is new. - "Behind" means the new commit changes the stack's own folder in the repository. In a repository shared by several stacks, a commit to another stack's folder changes nothing here. The comparison is with the deployed commit or, after a failed deploy, with the commit that failed: that is what the containers were last started from. - Each new commit that changes a stack is handled once: "notify" sends a notification; "deploy" runs compose.sh gitdeploy through compose_background.sh, which logs to the stack's last_cmd.log and notifies whether it worked. - A commit is not deployed, and one notification says why and what to do, when the stack's last deploy failed, its clone has local changes or a commit made by hand, or it is not running (stopped, or never deployed: as the image auto-update does, a commit never starts a stopped stack). Once that is cleared (or a notify stack is switched to deploy) the waiting commit is handled again. A stack whose project name is not settled is skipped, as the image auto-update skips it. - A scheduled deploy that fails before up (an image pull, say) is recorded as failed and not tried again: compose-git status shows it, and compose-git resume now clears it as well as a failed up, so the next pass tries again. - A stack another operation holds is tried again on the next pass, also when nothing needs fetching: what the stack runs from is read under its lock, so a deploy or rollback that is running is never acted on half done. An unreachable repository does not stop the other stacks being checked. What a pass learned is kept in git_check.json in the stack folder, written only when it changes, so a pass with nothing new writes nothing to the flash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…k asks for it The plugin's cron file gets a third line, marked #compose-gitcheck, that runs compose-git scheduled-check every 15 minutes. It is there while any git stack's auto-deploy is not off, and every rebuild of the file (image auto-update on or off, the backup schedule, compose-git auto-deploy) works that out again, so turning image auto-update off never removes it. The check runs on its own, not inside AutoUpdateRunner: that runner stops when no image auto-update is configured, and holds its own lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ck is behind - The Sources tab of a git stack gets "Automatic deploys": Off, notify me when a new commit changes the stack, or deploy it. It is saved at once, through the same code as compose-git auto-deploy, cron line included. - After a failed deploy, the stack menu offers "Resume Automatic Deploys", as compose-git resume does. - The stack list shows "behind" next to a git stack's branch and commit when the scheduled check last saw a newer commit that changes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The git stacks guide gets an "Automatic deploys" section: the three choices, what "changes the stack" means in a shared repository, why a commit is held back (a failed deploy, local changes, a stack that is not running) and how it goes on, and the cron line. The web UI table and the command reference gain auto-deploy, resume and scheduled-check, and "No automatic deploys yet" leaves the limitations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The checks a git deploy runs before up now return the problems they found, and prepare() still turns them into the error that stops the deploy. Nothing changes for a deploy. The next commit, a dry run of a deploy, lists the problems instead of stopping on them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plain git status refreshes the index and writes it back, taking .git/index.lock while it does. The Sources tab and the scheduled check ask for a clone's local changes without the stack's lock, so a checkout in the clone at that moment (a deploy's, or putting the previous commit back after a failed check) could fail on "index.lock: File exists" and leave the clone at the wrong commit. git status --no-optional-locks reads without writing the index, which is what git documents for background status readers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PHP keeps what realpath() and file_exists() found for a while. After a checkout replaced the compose file with a symlink, a process that had already resolved the file's path got the old answer, and the check that the compose file stays inside the clone passed. A deploy runs its checks in a fresh process (git_stack.php), so it was not affected. The dry run in the next commits resolves the compose file before and after the checkout in one process, and its test found this. Every checkout now clears PHP's file cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dry run runs every check a deploy runs before it pulls the images, and stops there: fetch, check out the commit, run the checks, then check the commit from before out again. It also compares the compose configuration of the two commits, and says whether up would recreate every container. No container is started, stopped or recreated, nothing is pulled or built, and the stack's state is not changed. Like a deploy, it checks the commit out in the clone itself, so for as long as the checks run, a container reading a folder of the clone sees the tried commit's files. The configuration is compared as "docker compose config --no-interpolate --no-env-resolution" prints it: variables as written, not their values, so nothing from the .env or the env files is printed. When it cannot be read or compared, the dry run says why and still reports the checks. The checks prune the plugin's override as a deploy's do; the dry run puts the override back as it was, unless it was saved again meanwhile (from the Labels tab, say). Local changes in the clone stop the dry run, as they stop a deploy, and are kept. The dry run takes the stack's lock and is refused while another operation on the stack runs, and refused for a stack whose project name is not settled, as a deploy is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Runs the dry run from the previous commit and prints what it found: which commit would be deployed (and the last one deployed successfully, when the clone holds another after a failed up), whether every check passed or what would stop the deploy, whether up would recreate every container, and how the compose configuration changes. The exit status is 0 when every check passed and 1 when one did not, so a script can try a commit before deploying it. A dry run does not pull images, so it says that a missing image or tag is found only by the deploy. --dry-run takes only --commit and --profile: the options for saving local changes and waiting for healthy containers mean nothing without a deploy, and are refused rather than ignored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The scheduled check's lock, git-scheduled-check.lock, sits in the folder where each stack's lock is <project name>.lock. A stack named git-scheduled-check therefore had the pass's own lock as its stack lock: the pass already held it, so that stack always looked busy and was never checked. The pass's lock is now .git-scheduled-check.lock. A project name never starts with a dot, so no stack's lock can be that file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The scheduled check compares one commit, the branch's head when it asked, and records that commit as handled. But it started the deploy without a commit, so the deploy fetched again and took whatever the branch's head was by then. If the branch moved from B to C in between, C was deployed while B was recorded: a failure of C before up was reported as B's, and the next pass could try C again despite the once-per-commit rule. The pass now passes the commit it compared to the deploy (compose.sh --git-commit). A newer commit is compared and handled by the next pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ride The dry run kept a copy of the plugin's override from before its checks, and after them wrote that copy back if the override was still as the checks' prune left it. A save from the Labels tab after the copy was taken but before the prune was lost that way: the prune read the newer save (and, when it had nothing to remove, left it as it was), so the override still matched what the prune left, and the older copy was written over it. The prune now says what it read and what it wrote when it changed the override. The dry run puts back what the prune read, and only when the prune changed something and nothing saved the override since. A save before the prune is what the prune read, so it is kept. The dry run also now puts back the file the prune writes, the project override (getProjectOverridePath()). It used to read getOverridePath(), which on a stack with an indirect override is the indirect file, not the one the prune changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dry run's documentation said nothing from the .env or the env files is printed. That holds for the configuration diff, which is read with --no-interpolate, but not for what the checks find: those are the deploy's own messages, read from the resolved configuration, so a missing folder or external volume whose path or name is built from a variable is named as it resolves. A deploy's log says the same. The docs, the --dry-run help and the code comment now say so, and keep the promise to the diff it holds for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… applies
The scheduled check decides to deploy a commit (automatic deploys set to
Deploy, no failed deploy, no local changes, the stack running) without
the stack's lock, since the deploy it starts must take that lock itself.
Things can change before the deploy gets it: a Stop or Down queued
meanwhile ran first, and the deploy then started the stack someone had
just stopped; or the stack was switched to Off or Notify while the check
asked the remote, and it was deployed anyway. And since the deploy now
gets the commit the check compared, a Pull and Redeploy (or a rollback)
that ran in between would be replaced by that commit, which may be older.
compose.sh gitdeploy takes a new --automatic-deploy-from option, which
the scheduled check passes with the deployed commit it compared from
("none" if none). Under the stack's lock, before fetching, git_stack.php
checks all of that again, and that the deployed commit is still the one
the check compared from (GitDeploy::assertAutomaticDeployStillApplies()).
When one no longer holds, nothing is changed and gitdeploy exits 75
(GIT_DEPLOY_EXIT_SKIPPED, EX_TEMPFAIL): no failed result is saved, the
background runner logs it without a failure notification, and the
scheduled check records the commit as not handled, so its next pass
decides again with what it finds then (and says so once, as for any
stopped stack). A clone or docker that cannot be asked is a skip too, as
it is for the scheduled check itself. A failed up that happens to exit 75
exits 1 instead, so it is never read as a skip.
The docker ps question moves to GitDeploy::hasRunningContainers(), and
the local-changes question to GitDeploy::localChanges(); the scheduled
check and the deploy both use them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
|
Thanks, all five fixed as new commits on top:
|
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
This is the last part I have ready for the git stacks feature. I've got a few additional feature ideas already, but don't plan to add any more to it besides any needed bug fixes until after everything has been fully reviewed and merged into release.
What. Git stacks can now be checked every 15 minutes. Per stack, choose what happens when a new commit on its
branch changes it: off (the default), notify (one Unraid notification per commit), or deploy (deployed
as Pull and Redeploy does it). It is set on the stack's Sources tab or with
compose-git auto-deploy. Andcompose-git deploy <stack> --dry-runruns every check a deploy runs, shows how the compose configurationchanges, and deploys nothing.
Why. This is the "poll and deploy" half of #138. The dry run lets a person or a script try a commit before
deploying it. It is a separate command: scheduled deploys do not run it first.
What it adds
one folder per stack, a commit to one stack leaves the others alone.
running is held back, with one notification saying why. A failed deploy pauses the stack until a deploy works
or Resume Automatic Deploys (stack menu,
compose-git resume) clears it.#compose-gitcheck, at 7 past the quarter hours, present while any stack asks for it.State goes in
git_check.jsonin the stack folder, written only when it changes.deploy --dry-runprints variables as written (--no-interpolate --no-env-resolution), so nothing from the.envis printed. Exit status 0 when every check passed, 1 when one did not.cached paths after a checkout.
How to read it: in order. Each commit is one step with its tests, and the messages carry the detail.
Commits 1-8 are the automatic deploys, 9-11 a refactor and the two fixes, 12-13 the dry run. The docs are in
docs/git-stacks.md("Automatic deploys", "What is checked before a deploy").Testing: 55 new PHPUnit tests, and the full suite and PHPStan pass at every commit. On Unraid 7.3.2 with
local repositories:
uppausing the stack, and Resume;--dry-runchecking a good commit, a bad one, an older one and a symlink out of the clone, with the clone,state and containers unchanged afterwards and no
.envvalue in the output.