fix: enforce webhook lockouts and stop cancelled runner provisioning

- Manual webhooks (GitHub, GitLab, Gitea, Bitbucket) now reject all
  deliveries for a locked-out scope before reading secrets or verifying
  signatures, including correctly signed ones, until the window expires.
- ProvisionGithubRunnerJob checks whether the execution has already
  finished before and after each provisioning step, and only sets
  ready_at or Failed while it is still occupying/provisioning. A runner
  that is cancelled, timed out or completed mid-provisioning is no longer
  started, retried or overwritten.
- VolumeCloneJob creates bind-volume clones as plain local volumes
  without the source driver options, so the clone never mounts the
  source host directory.
- Image pulls for additional servers no longer ignore docker pull errors.
- Add tests for each change.
This commit is contained in:
Andras Bacsai
2026-10-02 18:24:52 +02:00
parent 5fd7191d4a
commit f95ddd97fa
12 changed files with 244 additions and 36 deletions
+19 -1
View File
@@ -340,10 +340,16 @@ class MultiServerRecordingDeploymentJob extends ApplicationDeploymentJob
/** Output that `docker images -q` returns; empty means the image is missing. */
public string $localImageOutput = 'sha256:0123456789ab';
public bool $failImagePull = false;
public function execute_remote_command(...$commands): void
{
foreach ($commands as $command) {
$this->recordedCommands[] = $command['command'] ?? $command[0];
$remoteCommand = $command['command'] ?? $command[0];
$this->recordedCommands[] = $remoteCommand;
if ($this->failImagePull && str_starts_with($remoteCommand, 'docker pull ') && ! ($command['ignore_errors'] ?? false)) {
throw new RuntimeException('Registry image pull failed.');
}
if (isset($command['save'])) {
(new ReflectionProperty(ApplicationDeploymentJob::class, 'saved_outputs'))->getValue($this)->put($command['save'], $this->localImageOutput);
}
@@ -390,6 +396,18 @@ describe('inline Dockerfile image on additional servers', function () {
]);
});
test('an additional server fails when pulling the image fails even if an old image is cached', function () {
[$job, $reflection] = makeInlineDockerfileJob($this->application->fresh(), true);
$job->failImagePull = true;
$job->localImageOutput = 'sha256:stale-cached-image';
expect(fn () => $reflection->getMethod('pull_image_for_additional_server')->invoke($job))
->toThrow(RuntimeException::class, 'Registry image pull failed.');
expect($job->recordedCommands)->toBe([
"docker pull 'registry.example.com/nginx-multi:latest'",
]);
});
test('a deployments only server explains that the main server must push a missing image', function () {
$this->extraServer->settings->update(['server_role' => 'deployment']);
[$job, $reflection] = makeInlineDockerfileJob($this->application->fresh(), true, $this->extraServer->fresh());