style updates
This commit is contained in:
@@ -1,6 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\CloudDevOps\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Modules\CloudDevOps\Models\DeploymentHealthCheck;
|
||||
use Modules\CloudDevOps\Services\HealthCheckRunner;
|
||||
final class CloudDevOpsHealthCommand extends Command { protected $signature='cloud-devops:health'; protected $description='Run all active deployment health checks'; public function handle(HealthCheckRunner $runner): int { DeploymentHealthCheck::query()->each(fn($check)=>$runner->run($check)); $this->info('Health checks completed.'); return self::SUCCESS; } }
|
||||
|
||||
final class CloudDevOpsHealthCommand extends Command
|
||||
{
|
||||
protected $signature = 'cloud-devops:health';
|
||||
|
||||
protected $description = 'Run all active deployment health checks';
|
||||
|
||||
public function handle(HealthCheckRunner $runner): int
|
||||
{
|
||||
DeploymentHealthCheck::query()->each(fn ($check) => $runner->run($check));
|
||||
$this->info('Health checks completed.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\CloudDevOps\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Modules\CloudDevOps\Models\DeploymentRelease;
|
||||
final class CloudDevOpsPruneCommand extends Command { protected $signature='cloud-devops:prune {--days=}'; protected $description='Prune old deployment release history'; public function handle(): int { $days=(int)($this->option('days') ?: config('clouddevops.retention_days',90)); $count=DeploymentRelease::where('created_at','<',now()->subDays($days))->whereIn('status',['succeeded','failed','cancelled'])->delete(); $this->info("Pruned {$count} releases."); return self::SUCCESS; } }
|
||||
|
||||
final class CloudDevOpsPruneCommand extends Command
|
||||
{
|
||||
protected $signature = 'cloud-devops:prune {--days=}';
|
||||
|
||||
protected $description = 'Prune old deployment release history';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$days = (int) ($this->option('days') ?: config('clouddevops.retention_days', 90));
|
||||
$count = DeploymentRelease::where('created_at', '<', now()->subDays($days))->whereIn('status', ['succeeded', 'failed', 'cancelled'])->delete();
|
||||
$this->info("Pruned {$count} releases.");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -4,7 +4,8 @@ use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('deployment_environments', function (Blueprint $table): void {
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\CloudDevOps\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Modules\CloudDevOps\Models\DeploymentEnvironment;
|
||||
use Modules\CloudDevOps\Models\DeploymentRelease;
|
||||
final class CloudDevOpsApiController extends Controller { public function environments(): JsonResponse { return response()->json(['data'=>DeploymentEnvironment::withCount(['targets','pipelines','releases'])->get()]); } public function releases(): JsonResponse { return response()->json(['data'=>DeploymentRelease::with(['environment','steps'])->latest()->paginate()]); } }
|
||||
|
||||
final class CloudDevOpsApiController extends Controller
|
||||
{
|
||||
public function environments(): JsonResponse
|
||||
{
|
||||
return response()->json(['data' => DeploymentEnvironment::withCount(['targets', 'pipelines', 'releases'])->get()]);
|
||||
}
|
||||
|
||||
public function releases(): JsonResponse
|
||||
{
|
||||
return response()->json(['data' => DeploymentRelease::with(['environment', 'steps'])->latest()->paginate()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,22 +26,25 @@ final class CloudDevOpsController extends Controller
|
||||
|
||||
public function storeEnvironment(Request $request): RedirectResponse
|
||||
{
|
||||
$data = $request->validate(['name'=>'required|string|max:120','slug'=>'required|alpha_dash|max:120|unique:deployment_environments,slug','type'=>'required|in:development,staging,production']);
|
||||
$data = $request->validate(['name' => 'required|string|max:120', 'slug' => 'required|alpha_dash|max:120|unique:deployment_environments,slug', 'type' => 'required|in:development,staging,production']);
|
||||
DeploymentEnvironment::create($data);
|
||||
|
||||
return back()->with('status', 'Environment created.');
|
||||
}
|
||||
|
||||
public function deploy(Request $request, DeploymentEnvironment $environment, DeploymentManager $manager): RedirectResponse
|
||||
{
|
||||
$data = $request->validate(['pipeline_id'=>'required|integer','version'=>'nullable|string|max:120','commit_hash'=>'nullable|string|max:120']);
|
||||
$data = $request->validate(['pipeline_id' => 'required|integer', 'version' => 'nullable|string|max:120', 'commit_hash' => 'nullable|string|max:120']);
|
||||
$pipeline = DeploymentPipeline::where('environment_id', $environment->id)->findOrFail($data['pipeline_id']);
|
||||
$manager->createRelease($environment, $pipeline, $data + ['requested_by' => $request->user()?->id]);
|
||||
|
||||
return back()->with('status', 'Release created.');
|
||||
}
|
||||
|
||||
public function runCheck(DeploymentHealthCheck $check, HealthCheckRunner $runner): RedirectResponse
|
||||
{
|
||||
$runner->run($check);
|
||||
|
||||
return back()->with('status', 'Health check completed.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,29 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
final class DeploymentEnvironment extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
protected function casts(): array { return ['variables' => 'encrypted:array', 'is_protected' => 'boolean']; }
|
||||
public function targets(): HasMany { return $this->hasMany(DeploymentTarget::class, 'environment_id'); }
|
||||
public function pipelines(): HasMany { return $this->hasMany(DeploymentPipeline::class, 'environment_id'); }
|
||||
public function releases(): HasMany { return $this->hasMany(DeploymentRelease::class, 'environment_id'); }
|
||||
public function healthChecks(): HasMany { return $this->hasMany(DeploymentHealthCheck::class, 'environment_id'); }
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return ['variables' => 'encrypted:array', 'is_protected' => 'boolean'];
|
||||
}
|
||||
|
||||
public function targets(): HasMany
|
||||
{
|
||||
return $this->hasMany(DeploymentTarget::class, 'environment_id');
|
||||
}
|
||||
|
||||
public function pipelines(): HasMany
|
||||
{
|
||||
return $this->hasMany(DeploymentPipeline::class, 'environment_id');
|
||||
}
|
||||
|
||||
public function releases(): HasMany
|
||||
{
|
||||
return $this->hasMany(DeploymentRelease::class, 'environment_id');
|
||||
}
|
||||
|
||||
public function healthChecks(): HasMany
|
||||
{
|
||||
return $this->hasMany(DeploymentHealthCheck::class, 'environment_id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\CloudDevOps\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
final class DeploymentHealthCheck extends Model { protected $guarded = []; protected function casts(): array { return ['checked_at' => 'datetime']; } }
|
||||
|
||||
final class DeploymentHealthCheck extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return ['checked_at' => 'datetime'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\CloudDevOps\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
final class DeploymentPipeline extends Model { protected $guarded = []; protected function casts(): array { return ['steps' => 'array', 'requires_approval' => 'boolean', 'is_active' => 'boolean']; } }
|
||||
|
||||
final class DeploymentPipeline extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return ['steps' => 'array', 'requires_approval' => 'boolean', 'is_active' => 'boolean'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\CloudDevOps\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
final class DeploymentRelease extends Model { protected $guarded = []; protected function casts(): array { return ['metadata' => 'array', 'started_at' => 'datetime', 'completed_at' => 'datetime']; } public function environment(): BelongsTo { return $this->belongsTo(DeploymentEnvironment::class, 'environment_id'); } public function steps(): HasMany { return $this->hasMany(DeploymentStep::class, 'release_id')->orderBy('position'); } }
|
||||
|
||||
final class DeploymentRelease extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return ['metadata' => 'array', 'started_at' => 'datetime', 'completed_at' => 'datetime'];
|
||||
}
|
||||
|
||||
public function environment(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(DeploymentEnvironment::class, 'environment_id');
|
||||
}
|
||||
|
||||
public function steps(): HasMany
|
||||
{
|
||||
return $this->hasMany(DeploymentStep::class, 'release_id')->orderBy('position');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\CloudDevOps\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
final class DeploymentRollback extends Model { protected $guarded = []; }
|
||||
|
||||
final class DeploymentRollback extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\CloudDevOps\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
final class DeploymentSecret extends Model { protected $guarded = []; protected function casts(): array { return ['rotated_at' => 'datetime']; } }
|
||||
|
||||
final class DeploymentSecret extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return ['rotated_at' => 'datetime'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\CloudDevOps\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
final class DeploymentStep extends Model { protected $guarded = []; protected function casts(): array { return ['started_at' => 'datetime', 'completed_at' => 'datetime']; } }
|
||||
|
||||
final class DeploymentStep extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return ['started_at' => 'datetime', 'completed_at' => 'datetime'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\CloudDevOps\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
final class DeploymentTarget extends Model { protected $guarded = []; protected function casts(): array { return ['configuration' => 'encrypted:array', 'is_active' => 'boolean']; } }
|
||||
|
||||
final class DeploymentTarget extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return ['configuration' => 'encrypted:array', 'is_active' => 'boolean'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\CloudDevOps\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\CloudDevOps\Console\Commands\CloudDevOpsHealthCommand;
|
||||
use Modules\CloudDevOps\Console\Commands\CloudDevOpsPruneCommand;
|
||||
final class CloudDevOpsServiceProvider extends ServiceProvider { public function register(): void { $this->mergeConfigFrom(module_path('CloudDevOps','Config/config.php'),'clouddevops'); $this->app->register(RouteServiceProvider::class); } public function boot(): void { $this->loadViewsFrom(module_path('CloudDevOps','Resources/views'),'clouddevops'); $this->loadMigrationsFrom(module_path('CloudDevOps','Database/Migrations')); if ($this->app->runningInConsole()) { $this->commands([CloudDevOpsHealthCommand::class,CloudDevOpsPruneCommand::class]); } } }
|
||||
|
||||
final class CloudDevOpsServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
$this->mergeConfigFrom(module_path('CloudDevOps', 'Config/config.php'), 'clouddevops');
|
||||
$this->app->register(RouteServiceProvider::class);
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
$this->loadViewsFrom(module_path('CloudDevOps', 'Resources/views'), 'clouddevops');
|
||||
$this->loadMigrationsFrom(module_path('CloudDevOps', 'Database/Migrations'));
|
||||
if ($this->app->runningInConsole()) {
|
||||
$this->commands([CloudDevOpsHealthCommand::class, CloudDevOpsPruneCommand::class]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\CloudDevOps\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
final class RouteServiceProvider extends ServiceProvider { public function boot(): void { Route::middleware('web')->group(module_path('CloudDevOps','Routes/web.php')); Route::middleware('api')->prefix('api')->group(module_path('CloudDevOps','Routes/api.php')); } }
|
||||
|
||||
final class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function boot(): void
|
||||
{
|
||||
Route::middleware('web')->group(module_path('CloudDevOps', 'Routes/web.php'));
|
||||
Route::middleware('api')->prefix('api')->group(module_path('CloudDevOps', 'Routes/api.php'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Modules\CloudDevOps\Http\Controllers\CloudDevOpsApiController;
|
||||
Route::middleware(['auth:sanctum','permission:cloud-devops.view'])->prefix('v1/cloud-devops')->group(function (): void { Route::get('/environments',[CloudDevOpsApiController::class,'environments']); Route::get('/releases',[CloudDevOpsApiController::class,'releases']); });
|
||||
|
||||
Route::middleware(['auth:sanctum', 'permission:cloud-devops.view'])->prefix('v1/cloud-devops')->group(function (): void {
|
||||
Route::get('/environments', [CloudDevOpsApiController::class, 'environments']);
|
||||
Route::get('/releases', [CloudDevOpsApiController::class, 'releases']);
|
||||
});
|
||||
|
||||
@@ -34,6 +34,7 @@ final class DeploymentManager
|
||||
{
|
||||
abort_unless(config('clouddevops.allow_execution'), 403, 'Deployment execution is disabled.');
|
||||
$release->update(['status' => 'running', 'started_at' => now()]);
|
||||
|
||||
return $release->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\CloudDevOps\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Modules\CloudDevOps\Models\DeploymentHealthCheck;
|
||||
final class HealthCheckRunner { public function run(DeploymentHealthCheck $check): DeploymentHealthCheck { $started = microtime(true); try { $response = Http::timeout(10)->get((string) $check->endpoint); $status = $response->status() === $check->expected_status ? 'healthy' : 'unhealthy'; $message = 'HTTP '.$response->status(); } catch (\Throwable $e) { $status = 'unhealthy'; $message = $e->getMessage(); } $check->update(['status'=>$status,'response_time_ms'=>(int)((microtime(true)-$started)*1000),'checked_at'=>now(),'message'=>$message]); return $check->refresh(); } }
|
||||
|
||||
final class HealthCheckRunner
|
||||
{
|
||||
public function run(DeploymentHealthCheck $check): DeploymentHealthCheck
|
||||
{
|
||||
$started = microtime(true);
|
||||
try {
|
||||
$response = Http::timeout(10)->get((string) $check->endpoint);
|
||||
$status = $response->status() === $check->expected_status ? 'healthy' : 'unhealthy';
|
||||
$message = 'HTTP '.$response->status();
|
||||
} catch (\Throwable $e) {
|
||||
$status = 'unhealthy';
|
||||
$message = $e->getMessage();
|
||||
} $check->update(['status' => $status, 'response_time_ms' => (int) ((microtime(true) - $started) * 1000), 'checked_at' => now(), 'message' => $message]);
|
||||
|
||||
return $check->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\CloudDevOps\Tests\Feature;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Modules\CloudDevOps\Models\DeploymentEnvironment;
|
||||
use Tests\TestCase;
|
||||
final class CloudDevOpsModuleTest extends TestCase { use RefreshDatabase; public function test_environment_can_be_created(): void { $environment=DeploymentEnvironment::create(['name'=>'Production','slug'=>'production','type'=>'production']); $this->assertDatabaseHas('deployment_environments',['id'=>$environment->id,'slug'=>'production']); } }
|
||||
|
||||
final class CloudDevOpsModuleTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_environment_can_be_created(): void
|
||||
{
|
||||
$environment = DeploymentEnvironment::create(['name' => 'Production', 'slug' => 'production', 'type' => 'production']);
|
||||
$this->assertDatabaseHas('deployment_environments', ['id' => $environment->id, 'slug' => 'production']);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user