This commit is contained in:
Andras Bacsai 2023-11-27 15:50:22 +01:00
parent c1710c8f7b
commit 23571ae104
10 changed files with 62 additions and 1407 deletions

View File

@ -27,6 +27,7 @@ class General extends Component
public bool $isConfigurationChanged = false; public bool $isConfigurationChanged = false;
public ?string $initialDockerComposeLocation = null; public ?string $initialDockerComposeLocation = null;
public ?string $initialDockerComposePrLocation = null;
public bool $is_static; public bool $is_static;
@ -57,6 +58,7 @@ class General extends Component
'application.docker_registry_image_tag' => 'nullable', 'application.docker_registry_image_tag' => 'nullable',
'application.dockerfile_location' => 'nullable', 'application.dockerfile_location' => 'nullable',
'application.docker_compose_location' => 'nullable', 'application.docker_compose_location' => 'nullable',
'application.docker_compose_pr_location' => 'nullable',
'application.docker_compose' => 'nullable', 'application.docker_compose' => 'nullable',
'application.docker_compose_raw' => 'nullable', 'application.docker_compose_raw' => 'nullable',
'application.custom_labels' => 'nullable', 'application.custom_labels' => 'nullable',
@ -84,6 +86,7 @@ class General extends Component
'application.docker_registry_image_tag' => 'Docker registry image tag', 'application.docker_registry_image_tag' => 'Docker registry image tag',
'application.dockerfile_location' => 'Dockerfile location', 'application.dockerfile_location' => 'Dockerfile location',
'application.docker_compose_location' => 'Docker compose location', 'application.docker_compose_location' => 'Docker compose location',
'application.docker_compose_pr_location' => 'Docker compose location',
'application.docker_compose' => 'Docker compose', 'application.docker_compose' => 'Docker compose',
'application.docker_compose_raw' => 'Docker compose raw', 'application.docker_compose_raw' => 'Docker compose raw',
'application.custom_labels' => 'Custom labels', 'application.custom_labels' => 'Custom labels',
@ -125,10 +128,11 @@ public function loadComposeFile($isInit = false)
if ($isInit && $this->application->docker_compose_raw) { if ($isInit && $this->application->docker_compose_raw) {
return; return;
} }
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit); ['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation, 'initialDockerComposePrLocation' => $this->initialDockerComposePrLocation] = $this->application->loadComposeFile($isInit);
$this->emit('success', 'Docker compose file loaded.'); $this->emit('success', 'Docker compose file loaded.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->application->docker_compose_location = $this->initialDockerComposeLocation; $this->application->docker_compose_location = $this->initialDockerComposeLocation;
$this->application->docker_compose_pr_location = $this->initialDockerComposePrLocation;
$this->application->save(); $this->application->save();
return handleError($e, $this); return handleError($e, $this);
} }

View File

@ -1,151 +0,0 @@
<?php
namespace App\Jobs;
use App\Enums\ApplicationDeploymentStatus;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Server;
use App\Traits\ExecuteRemoteCommandNew;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Throwable;
class ApplicationDeployDockerImageJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommandNew;
public $timeout = 3600;
public $tries = 1;
public $remoteCommandOutputs = [];
public Server $server;
public string $containerName;
public function __construct(public ApplicationDeploymentQueue $deploymentQueueEntry, public Application $application)
{
}
public function handle()
{
// ray()->clearAll();
ray('Deploying Docker Image');
static::$batch_counter = 0;
try {
$deploymentUuid = data_get($this->deploymentQueueEntry, 'deployment_uuid');
$pullRequestId = data_get($this->deploymentQueueEntry, 'pull_request_id');
$this->server = data_get($this->application->destination, 'server');
$network = data_get($this->application->destination, 'network');
$dockerImage = data_get($this->application, 'docker_registry_image_name');
$dockerImageTag = data_get($this->application, 'docker_registry_image_tag');
$productionImageName = str("{$dockerImage}:{$dockerImageTag}");
$this->containerName = generateApplicationContainerName($this->application, $pullRequestId);
savePrivateKeyToFs($this->server);
ray("echo 'Starting deployment of {$productionImageName}.'");
$this->deploymentQueueEntry->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
$this->deploymentQueueEntry->addLogEntry('Starting deployment of ' . $productionImageName);
$this->server->executeRemoteCommand(
commands: collect(
[
[
"name" => "ls",
"command" => 'ls -la',
"hidden" => true,
],
[
"name" => "pwd",
"command" => 'pwd',
"hidden" => true,
]
],
),
loggingModel: $this->deploymentQueueEntry
);
$this->server->executeRemoteCommand(
commands: prepareHelperContainer($this->server, $network, $deploymentUuid),
loggingModel: $this->deploymentQueueEntry
);
$this->server->executeRemoteCommand(
commands: generateComposeFile(
deploymentUuid: $deploymentUuid,
server: $this->server,
network: $network,
application: $this->application,
containerName: $this->containerName,
imageName: $productionImageName,
pullRequestId: $pullRequestId
),
loggingModel: $this->deploymentQueueEntry
);
$this->deploymentQueueEntry->addLogEntry('----------------------------------------');
// Rolling update not possible
if (count($this->application->ports_mappings_array) > 0) {
$this->deploymentQueueEntry->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.');
$this->deploymentQueueEntry->addLogEntry('Stopping running container.');
$this->server->stopApplicationRelatedRunningContainers($this->application->id, $this->containerName);
} else {
$this->deploymentQueueEntry->addLogEntry('Rolling update started.');
// TODO
$this->server->executeRemoteCommand(
commands: startNewApplication(application: $this->application, deploymentUuid: $deploymentUuid, loggingModel: $this->deploymentQueueEntry),
loggingModel: $this->deploymentQueueEntry
);
// $this->server->executeRemoteCommand(
// commands: healthCheckContainer(application: $this->application, containerName: $this->containerName , loggingModel: $this->deploymentQueueEntry),
// loggingModel: $this->deploymentQueueEntry
// );
}
ray($this->remoteCommandOutputs);
$this->deploymentQueueEntry->update([
'status' => ApplicationDeploymentStatus::FINISHED->value,
]);
} catch (Throwable $e) {
$this->fail($e);
throw $e;
}
}
public function failed(Throwable $exception): void
{
$this->deploymentQueueEntry->addLogEntry('Oops something is not okay, are you okay? 😢', 'error');
$this->deploymentQueueEntry->addLogEntry($exception->getMessage(), 'error');
$this->deploymentQueueEntry->addLogEntry('Deployment failed. Removing the new version of your application.');
$this->server->executeRemoteCommand(
commands: removeOldDeployment($this->containerName),
loggingModel: $this->deploymentQueueEntry
);
$this->deploymentQueueEntry->update([
'status' => ApplicationDeploymentStatus::FAILED->value,
]);
}
// private function next(string $status)
// {
// // If the deployment is cancelled by the user, don't update the status
// if ($this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
// $this->application_deployment_queue->update([
// 'status' => $status,
// ]);
// }
// queue_next_deployment($this->application);
// if ($status === ApplicationDeploymentStatus::FINISHED->value) {
// $this->application->environment->project->team->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
// }
// if ($status === ApplicationDeploymentStatus::FAILED->value) {
// $this->application->environment->project->team->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
// }
// }
}

View File

@ -1,46 +0,0 @@
<?php
namespace App\Jobs;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Traits\ExecuteRemoteCommand;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ApplicationDeploySimpleDockerfileJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand;
public $timeout = 3600;
public $tries = 1;
public string $applicationDeploymentQueueId;
public function __construct(string $applicationDeploymentQueueId)
{
$this->applicationDeploymentQueueId = $applicationDeploymentQueueId;
}
public function handle()
{
ray('Deploying Simple Dockerfile');
$applicationDeploymentQueue = ApplicationDeploymentQueue::find($this->applicationDeploymentQueueId);
$application = Application::find($applicationDeploymentQueue->application_id);
$destination = $application->destination->getMorphClass()::where('id', $application->destination->id)->first();
$server = data_get($destination, 'server');
$commands = collect([]);
$commands->push(
[
'command' => 'echo "Starting deployment of simple dockerfile."',
],
[
'command' => 'ls -la',
]
);
$server->executeRemoteCommand(commands: $commands, logModel: $applicationDeploymentQueue);
}
}

View File

@ -456,7 +456,6 @@ private function deploy_docker_compose_buildpack()
if ($this->pull_request_id === 0) { if ($this->pull_request_id === 0) {
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}."); $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}.");
} else { } else {
ray('asd');
$this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}."); $this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}.");
} }
$this->server->executeRemoteCommand( $this->server->executeRemoteCommand(

File diff suppressed because it is too large Load Diff

View File

@ -138,7 +138,22 @@ public function dockerComposeLocation(): Attribute
return Attribute::make( return Attribute::make(
set: function ($value) { set: function ($value) {
if (is_null($value) || $value === '') { if (is_null($value) || $value === '') {
return '/docker-compose.yml'; return '/docker-compose.yaml';
} else {
if ($value !== '/') {
return Str::start(Str::replaceEnd('/', '', $value), '/');
}
return Str::start($value, '/');
}
}
);
}
public function dockerComposePrLocation(): Attribute
{
return Attribute::make(
set: function ($value) {
if (is_null($value) || $value === '') {
return '/docker-compose-pr.yaml';
} else { } else {
if ($value !== '/') { if ($value !== '/') {
return Str::start(Str::replaceEnd('/', '', $value), '/'); return Str::start(Str::replaceEnd('/', '', $value), '/');
@ -595,6 +610,7 @@ function parseCompose(int $pull_request_id = 0)
function loadComposeFile($isInit = false) function loadComposeFile($isInit = false)
{ {
$initialDockerComposeLocation = $this->docker_compose_location; $initialDockerComposeLocation = $this->docker_compose_location;
$initialDockerComposePrLocation = $this->docker_compose_pr_location;
if ($this->build_pack === 'dockercompose') { if ($this->build_pack === 'dockercompose') {
if ($isInit && $this->docker_compose_raw) { if ($isInit && $this->docker_compose_raw) {
return; return;
@ -603,11 +619,12 @@ function loadComposeFile($isInit = false)
['commands' => $cloneCommand] = $this->generateGitImportCommands(deployment_uuid: $uuid, only_checkout: true, exec_in_docker: false, custom_base_dir: '.'); ['commands' => $cloneCommand] = $this->generateGitImportCommands(deployment_uuid: $uuid, only_checkout: true, exec_in_docker: false, custom_base_dir: '.');
$workdir = rtrim($this->base_directory, '/'); $workdir = rtrim($this->base_directory, '/');
$composeFile = $this->docker_compose_location; $composeFile = $this->docker_compose_location;
$prComposeFile = $this->docker_compose_pr_location;
$commands = collect([ $commands = collect([
"mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}", "mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}",
$cloneCommand, $cloneCommand,
"git sparse-checkout init --cone", "git sparse-checkout init --cone",
"git sparse-checkout set .$workdir$composeFile", "git sparse-checkout set .$workdir$composeFile .$workdir$prComposeFile",
"git read-tree -mu HEAD", "git read-tree -mu HEAD",
"cat .$workdir$composeFile", "cat .$workdir$composeFile",
]); ]);
@ -620,13 +637,26 @@ function loadComposeFile($isInit = false)
$this->docker_compose_raw = $composeFileContent; $this->docker_compose_raw = $composeFileContent;
$this->save(); $this->save();
} }
$commands = collect([
"cat .$workdir$prComposeFile",
]);
$composePrFileContent = instant_remote_process($commands, $this->destination->server, false);
if (!$composePrFileContent) {
$this->docker_compose_pr_location = $initialDockerComposePrLocation;
$this->save();
throw new \Exception("Could not load compose file from $workdir$prComposeFile");
} else {
$this->docker_compose_pr_raw = $composePrFileContent;
$this->save();
}
$commands = collect([ $commands = collect([
"rm -rf /tmp/{$uuid}", "rm -rf /tmp/{$uuid}",
]); ]);
instant_remote_process($commands, $this->destination->server, false); instant_remote_process($commands, $this->destination->server, false);
return [ return [
'parsedServices' => $this->parseCompose(), 'parsedServices' => $this->parseCompose(),
'initialDockerComposeLocation' => $this->docker_compose_location 'initialDockerComposeLocation' => $this->docker_compose_location,
'initialDockerComposePrLocation' => $this->docker_compose_pr_location,
]; ];
} }
} }

View File

@ -36,10 +36,6 @@ function queue_application_deployment(int $application_id, string $deployment_uu
if ($running_deployments->count() > 0) { if ($running_deployments->count() > 0) {
return; return;
} }
// New deployment
// dispatchDeploymentJob($deployment);
// Old deployment
dispatch(new ApplicationDeploymentJob( dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id, application_deployment_queue_id: $deployment->id,
))->onConnection('long-running')->onQueue('long-running'); ))->onConnection('long-running')->onQueue('long-running');
@ -50,39 +46,11 @@ function queue_next_deployment(Application $application)
{ {
$next_found = ApplicationDeploymentQueue::where('application_id', $application->id)->where('status', 'queued')->first(); $next_found = ApplicationDeploymentQueue::where('application_id', $application->id)->where('status', 'queued')->first();
if ($next_found) { if ($next_found) {
// New deployment
// dispatchDeploymentJob($next_found->id);
// Old deployment
dispatch(new ApplicationDeploymentJob( dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $next_found->id, application_deployment_queue_id: $next_found->id,
))->onConnection('long-running')->onQueue('long-running'); ))->onConnection('long-running')->onQueue('long-running');
} }
} }
function dispatchDeploymentJob(ApplicationDeploymentQueue $deploymentQueueEntry)
{
$application = Application::find($deploymentQueueEntry->application_id);
$isRestartOnly = data_get($deploymentQueueEntry, 'restart_only');
$isSimpleDockerFile = data_get($application, 'dockerfile');
$isDockerImage = data_get($application, 'build_pack') === 'dockerimage';
// if ($isRestartOnly) {
// ApplicationRestartJob::dispatch(queue: $deploymentQueueEntry, application: $application)->onConnection('long-running')->onQueue('long-running');
// } else if ($isSimpleDockerFile) {
// ApplicationDeploySimpleDockerfileJob::dispatch(applicationDeploymentQueueId: $id)->onConnection('long-running')->onQueue('long-running');
// } else
if ($isDockerImage) {
ApplicationDeployDockerImageJob::dispatch(
deploymentQueueEntry: $deploymentQueueEntry,
application: $application
)->onConnection('long-running')->onQueue('long-running');
} else {
throw new Exception('Unknown build pack');
}
}
// Deployment things // Deployment things
function generateHostIpMapping(Server $server, string $network) function generateHostIpMapping(Server $server, string $network)
{ {

View File

@ -1089,11 +1089,16 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return collect([]); return collect([]);
} }
} else if ($resource->getMorphClass() === 'App\Models\Application') { } else if ($resource->getMorphClass() === 'App\Models\Application') {
try { if ($pull_request_id !== 0 && $resource->dockerComposePrLocation() !== $resource->dockerComposeLocation()) {
$yaml = Yaml::parse($resource->docker_compose_raw);
} catch (\Exception $e) { } else {
throw new \Exception($e->getMessage()); try {
$yaml = Yaml::parse($resource->docker_compose_raw);
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
} }
$server = $resource->destination->server; $server = $resource->destination->server;
$topLevelVolumes = collect(data_get($yaml, 'volumes', [])); $topLevelVolumes = collect(data_get($yaml, 'volumes', []));
if ($pull_request_id !== 0) { if ($pull_request_id !== 0) {

View File

@ -12,11 +12,15 @@
public function up(): void public function up(): void
{ {
Schema::table('applications', function (Blueprint $table) { Schema::table('applications', function (Blueprint $table) {
$table->string('docker_compose_location')->nullable()->default('/docker-compose.yml')->after('dockerfile_location'); $table->string('docker_compose_location')->nullable()->default('/docker-compose.yaml')->after('dockerfile_location');
$table->longText('docker_compose')->nullable()->after('docker_compose_location'); $table->string('docker_compose_pr_location')->nullable()->default('/docker-compose-pr.yaml')->after('docker_compose_location');
$table->longText('docker_compose_raw')->nullable()->after('docker_compose');
$table->text('docker_compose_domains')->nullable()->after('docker_compose_raw');
$table->longText('docker_compose')->nullable()->after('docker_compose_location');
$table->longText('docker_compose_pr')->nullable()->after('docker_compose_location');
$table->longText('docker_compose_raw')->nullable()->after('docker_compose');
$table->longText('docker_compose_pr_raw')->nullable()->after('docker_compose');
$table->text('docker_compose_domains')->nullable()->after('docker_compose_raw');
}); });
} }
@ -27,8 +31,11 @@ public function down(): void
{ {
Schema::table('applications', function (Blueprint $table) { Schema::table('applications', function (Blueprint $table) {
$table->dropColumn('docker_compose_location'); $table->dropColumn('docker_compose_location');
$table->dropColumn('docker_compose_pr_location');
$table->dropColumn('docker_compose'); $table->dropColumn('docker_compose');
$table->dropColumn('docker_compose_pr');
$table->dropColumn('docker_compose_raw'); $table->dropColumn('docker_compose_raw');
$table->dropColumn('docker_compose_pr_raw');
$table->dropColumn('docker_compose_domains'); $table->dropColumn('docker_compose_domains');
}); });
} }

View File

@ -116,9 +116,12 @@
@endif @endif
@if ($application->build_pack === 'dockercompose') @if ($application->build_pack === 'dockercompose')
<span wire:init='loadComposeFile(true)'></span> <span wire:init='loadComposeFile(true)'></span>
<x-forms.input placeholder="/docker-compose.yml" id="application.docker_compose_location" <x-forms.input placeholder="/docker-compose.yaml" id="application.docker_compose_location"
label="Docker Compose Location" label="Docker Compose Location"
helper="It is calculated together with the Base Directory:<br><span class='text-warning'>{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }}</span>" /> helper="It is calculated together with the Base Directory:<br><span class='text-warning'>{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }}</span>" />
<x-forms.input placeholder="/docker-compose.yaml" id="application.docker_compose_pr_location"
label="Docker Compose Location For Pull Requests"
helper="It is calculated together with the Base Directory:<br><span class='text-warning'>{{ Str::start($application->base_directory . $application->docker_compose_pr_location, '/') }}</span>" />
@endif @endif
@if ($application->build_pack === 'dockerfile') @if ($application->build_pack === 'dockerfile')
<x-forms.input id="application.dockerfile_target_build" label="Docker Build Stage Target" <x-forms.input id="application.dockerfile_target_build" label="Docker Build Stage Target"