From 4051afca4e3b57aa240f0b1ed2fd20a076bed09f Mon Sep 17 00:00:00 2001 From: Harry Bayliss Date: Thu, 22 May 2025 17:19:28 +0100 Subject: [PATCH] get deployment plan --- app/Drivers/Caddy/Caddy2Driver.php | 22 ++++++++++----- app/Drivers/Driver.php | 4 +-- app/Drivers/Postgres/Postgres17Driver.php | 28 ++++++++++++++----- app/Jobs/Services/DeployService.php | 3 +- app/Models/Deployment.php | 9 ++++++ ..._03_31_140110_create_deployments_table.php | 1 + 6 files changed, 50 insertions(+), 17 deletions(-) diff --git a/app/Drivers/Caddy/Caddy2Driver.php b/app/Drivers/Caddy/Caddy2Driver.php index 8714103..fc66ae4 100644 --- a/app/Drivers/Caddy/Caddy2Driver.php +++ b/app/Drivers/Caddy/Caddy2Driver.php @@ -5,6 +5,7 @@ namespace App\Drivers\Caddy; use App\Drivers\GatewayDriver; use App\Data\Deployments\Plan; use App\Data\Deployments\PlannedStep as Step; +use App\Enums\DeploymentStatus; use App\Models\Service; class Caddy2Driver extends GatewayDriver @@ -20,8 +21,15 @@ class Caddy2Driver extends GatewayDriver $this->containerName = $containerName; $this->containerId = $containerId; $this->service = $service; + } - $this->deploymentPlan = new Plan(steps: [ + public function getDeploymentPlan(string $deploymentHash): Plan + { + $previousDeployment = $this->service?->deployments() + ->where('status', DeploymentStatus::COMPLETED) + ->first(); + + return new Plan(steps: [ new Step( name: 'Generate Caddyfile', script: function () { @@ -36,23 +44,23 @@ class Caddy2Driver extends GatewayDriver ), new Step( name: 'Run the docker image', - script: function () { + script: function () use ($previousDeployment, $deploymentHash) { $script = collect(); - if ($this->containerName) { - $script->push('docker stop '.$this->containerName.' || true'); + if ($this->containerName && $previousDeployment) { + $script->push("docker stop \"{$this->containerName}-{$previousDeployment->hash}\" || true"); } elseif ($this->containerId) { - $script->push('docker stop '.$this->containerId.' || true'); + $script->push('docker stop ' . $this->containerId . ' || true'); } $runCommand = 'docker run -d'; if ($this->containerName) { - $runCommand .= " --name {$this->containerName}"; + $runCommand .= " --name \"{$this->containerName}-{$deploymentHash}\""; } $runCommand .= ' -p 80:80 -p 443:443 caddy:2'; $script->push($runCommand); - return $script->join("\n"); + return $script->join(" && "); } ), ]); diff --git a/app/Drivers/Driver.php b/app/Drivers/Driver.php index 218e56b..5747add 100644 --- a/app/Drivers/Driver.php +++ b/app/Drivers/Driver.php @@ -9,8 +9,6 @@ abstract class Driver { public ?Service $service; - public Plan $deploymentPlan; - public ?string $containerName; public ?string $containerId; @@ -20,4 +18,6 @@ abstract class Driver ?string $containerId = null, ?Service $service = null, ); + + abstract public function getDeploymentPlan(string $deploymentHash): Plan; } diff --git a/app/Drivers/Postgres/Postgres17Driver.php b/app/Drivers/Postgres/Postgres17Driver.php index 0e88a38..5130327 100644 --- a/app/Drivers/Postgres/Postgres17Driver.php +++ b/app/Drivers/Postgres/Postgres17Driver.php @@ -5,6 +5,7 @@ namespace App\Drivers\Postgres; use App\Data\Deployments\Plan; use App\Data\Deployments\PlannedStep as Step; use App\Drivers\DatabaseDriver; +use App\Enums\DeploymentStatus; use App\Models\Service; use Illuminate\Support\Str; @@ -19,27 +20,40 @@ class Postgres17Driver extends DatabaseDriver public ?array $credentials = null, ) { $credentials = $credentials ?? $this->defaultCredentials(); + } + + public function getDeploymentPlan(string $deploymentHash): Plan + { $user = $credentials['user'] ?? null; $password = $credentials['password'] ?? null; $db = $credentials['db'] ?? null; - $this->deploymentPlan = new Plan(steps: [ + if (!$user || !$password || !$db) { + throw new \InvalidArgumentException('Missing required credentials'); + } + + $previousDeployment = $this->service?->deployments() + ->where('status', DeploymentStatus::COMPLETED) + ->first(); + + return new Plan(steps: [ new Step( name: 'Run the docker image', secrets: [ 'password' => $password ], - script: function () use ($user, $password, $db) { + script: function () use ($user, $password, $db, $previousDeployment, $deploymentHash) { $script = collect(); - if ($this->containerName) { - $script->push('docker stop '.$this->containerName.' || true'); + + if ($this->containerName && $previousDeployment) { + $script->push("docker stop \"{$this->containerName}-{$previousDeployment->hash}\" || true"); } elseif ($this->containerId) { - $script->push('docker stop '.$this->containerId.' || true'); + $script->push('docker stop ' . $this->containerId . ' || true'); } $runCommand = 'docker run -d'; if ($this->containerName) { - $runCommand .= " --name {$this->containerName}"; + $runCommand .= " --name \"{$this->containerName}-{$deploymentHash}\""; } if ($password) { $runCommand .= ' -e POSTGRES_PASSWORD=[!password!]'; @@ -54,7 +68,7 @@ class Postgres17Driver extends DatabaseDriver $script->push($runCommand); - return $runCommand; + return $script->join(" && "); } ), new Step( diff --git a/app/Jobs/Services/DeployService.php b/app/Jobs/Services/DeployService.php index 2fe3cf7..b9b3b62 100644 --- a/app/Jobs/Services/DeployService.php +++ b/app/Jobs/Services/DeployService.php @@ -24,13 +24,14 @@ class DeployService implements ShouldQueue public function handle(): void { $driver = $this->service->driver(); + $deploymentPlan = $driver->getDeploymentPlan($this->deployment->hash); $this->service->update([ 'status' => ServiceStatus::INSTALLING, ]); $this->deployment = $this->service->deployments()->create([ 'status' => DeploymentStatus::PENDING, ]); - foreach ($driver->deploymentPlan->steps as $index => $plannedStep) { + foreach ($deploymentPlan->steps as $index => $plannedStep) { $step = $this->deployment->steps()->create([ 'order' => $index + 1, 'status' => DeploymentStatus::PENDING, diff --git a/app/Models/Deployment.php b/app/Models/Deployment.php index c4552f4..4a00634 100644 --- a/app/Models/Deployment.php +++ b/app/Models/Deployment.php @@ -10,6 +10,15 @@ class Deployment extends Model { protected $guarded = []; + public static function boot(): void + { + parent::boot(); + + static::creating(function (self $deployment) { + $deployment->hash = str()->random(16); + }); + } + protected function casts(): array { return [ diff --git a/database/migrations/2025_03_31_140110_create_deployments_table.php b/database/migrations/2025_03_31_140110_create_deployments_table.php index e5b4baa..a8e61fd 100644 --- a/database/migrations/2025_03_31_140110_create_deployments_table.php +++ b/database/migrations/2025_03_31_140110_create_deployments_table.php @@ -10,6 +10,7 @@ return new class extends Migration { Schema::create('deployments', function (Blueprint $table) { $table->id(); + $table->string('hash')->unique(); $table->morphs('target'); // server, service, etc. $table->string('status'); $table->dateTime('started_at')->nullable();