feat(deploy): pull compose images before stopping containers
Pull image-based Docker Compose services with --ignore-buildable before stopping the current deployment so image-only services are ready first. Fail the deploy if pull fails, and run the pull on the runtime server when a build server is used.
This commit is contained in:
parent
285684ad3c
commit
361d5a3c8c
2 changed files with 120 additions and 0 deletions
|
|
@ -808,6 +808,8 @@ private function deploy_docker_compose_buildpack()
|
|||
// This overwrites the build-time .env with ALL variables (build-time + runtime)
|
||||
$this->save_runtime_environment_variables();
|
||||
|
||||
$this->pull_docker_compose_images();
|
||||
|
||||
$this->stop_running_container(force: true);
|
||||
$this->application_deployment_queue->addLogEntry('Starting new application.');
|
||||
$networkId = $this->application->uuid;
|
||||
|
|
@ -911,6 +913,26 @@ private function deploy_docker_compose_buildpack()
|
|||
$this->application_deployment_queue->addLogEntry('New container started.');
|
||||
}
|
||||
|
||||
private function pull_docker_compose_images(): void
|
||||
{
|
||||
$this->application_deployment_queue->addLogEntry('Pulling image-based services before stopping the current deployment.');
|
||||
|
||||
if ($this->use_build_server) {
|
||||
$this->write_deployment_configurations();
|
||||
$this->server = $this->mainServer;
|
||||
$workdir = $this->application->workdir();
|
||||
$command = "{$this->coolify_variables} docker compose --env-file {$workdir}/.env --project-name {$this->application->uuid} --project-directory {$workdir} -f {$workdir}{$this->docker_compose_location} pull --ignore-buildable";
|
||||
} else {
|
||||
$workdir = $this->workdir;
|
||||
$command = executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --env-file {$workdir}/.env --project-name {$this->application->uuid} --project-directory {$workdir} -f {$workdir}{$this->docker_compose_location} pull --ignore-buildable");
|
||||
}
|
||||
|
||||
$this->execute_remote_command([
|
||||
$command,
|
||||
'hidden' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
private function deploy_dockerfile_buildpack()
|
||||
{
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
|
||||
|
|
|
|||
98
tests/Unit/DockerComposeImagePullOrderTest.php
Normal file
98
tests/Unit/DockerComposeImagePullOrderTest.php
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
use App\Jobs\ApplicationDeploymentJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Server;
|
||||
|
||||
class TestableDockerComposeImagePullDeploymentJob extends ApplicationDeploymentJob
|
||||
{
|
||||
public array $recordedCommands = [];
|
||||
|
||||
public array $commandServers = [];
|
||||
|
||||
public bool $failCommands = false;
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
public function execute_remote_command(...$commands): void
|
||||
{
|
||||
$reflection = new ReflectionClass(ApplicationDeploymentJob::class);
|
||||
$this->commandServers[] = $reflection->getProperty('server')->getValue($this);
|
||||
$this->recordedCommands[] = $commands;
|
||||
|
||||
if ($this->failCommands) {
|
||||
throw new RuntimeException('Image pull failed.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeDockerComposeImagePullJob(bool $useBuildServer = false): array
|
||||
{
|
||||
$job = new TestableDockerComposeImagePullDeploymentJob;
|
||||
$reflection = new ReflectionClass(ApplicationDeploymentJob::class);
|
||||
|
||||
$application = Mockery::mock(Application::class)->makePartial();
|
||||
$application->uuid = 'application-uuid';
|
||||
$application->shouldReceive('workdir')->andReturn('/runtime/application');
|
||||
|
||||
$queue = Mockery::mock(ApplicationDeploymentQueue::class)->makePartial();
|
||||
$queue->shouldReceive('addLogEntry')->once()->andReturnNull();
|
||||
|
||||
$buildServer = new Server;
|
||||
$buildServer->id = 1;
|
||||
$mainServer = new Server;
|
||||
$mainServer->id = 2;
|
||||
|
||||
foreach ([
|
||||
'application' => $application,
|
||||
'application_deployment_queue' => $queue,
|
||||
'server' => $buildServer,
|
||||
'build_server' => $buildServer,
|
||||
'mainServer' => $mainServer,
|
||||
'use_build_server' => $useBuildServer,
|
||||
'workdir' => '/artifacts/application',
|
||||
'deployment_uuid' => 'deployment-uuid',
|
||||
'docker_compose_location' => '/docker-compose.yaml',
|
||||
'coolify_variables' => '',
|
||||
] as $property => $value) {
|
||||
$reflection->getProperty($property)->setValue($job, $value);
|
||||
}
|
||||
|
||||
return [$job, $reflection, $buildServer, $mainServer];
|
||||
}
|
||||
|
||||
it('pulls image-only compose services before removing running containers', function () {
|
||||
$source = file_get_contents(__DIR__.'/../../app/Jobs/ApplicationDeploymentJob.php');
|
||||
$methodStart = strpos($source, 'private function deploy_docker_compose_buildpack()');
|
||||
$methodEnd = strpos($source, 'private function pull_docker_compose_images()', $methodStart);
|
||||
$deploymentMethod = substr($source, $methodStart, $methodEnd - $methodStart);
|
||||
|
||||
$pullPosition = strpos($deploymentMethod, '$this->pull_docker_compose_images();');
|
||||
$stopPosition = strpos($deploymentMethod, '$this->stop_running_container(force: true);');
|
||||
|
||||
expect($pullPosition)->not->toBeFalse()
|
||||
->and($stopPosition)->not->toBeFalse()
|
||||
->and($pullPosition)->toBeLessThan($stopPosition);
|
||||
});
|
||||
|
||||
it('aborts the deployment when pulling a compose image fails', function () {
|
||||
[$job, $reflection] = makeDockerComposeImagePullJob();
|
||||
$job->failCommands = true;
|
||||
|
||||
$reflection->getMethod('pull_docker_compose_images')->invoke($job);
|
||||
})->throws(RuntimeException::class, 'Image pull failed.');
|
||||
|
||||
it('pulls compose images on the runtime server when using a build server', function () {
|
||||
[$job, $reflection, $buildServer, $mainServer] = makeDockerComposeImagePullJob(useBuildServer: true);
|
||||
|
||||
$reflection->getMethod('pull_docker_compose_images')->invoke($job);
|
||||
|
||||
$command = $job->recordedCommands[0][0][0];
|
||||
|
||||
expect($job->commandServers)->toHaveCount(1)
|
||||
->and($job->commandServers[0])->toBe($mainServer)
|
||||
->and($job->commandServers[0])->not->toBe($buildServer)
|
||||
->and($command)->toContain('pull --ignore-buildable')
|
||||
->and($command)->toContain('--env-file /runtime/application/.env');
|
||||
});
|
||||
Loading…
Reference in a new issue