From b1d6167e89474f7c9708443403dd02cd250d32fe Mon Sep 17 00:00:00 2001 From: Van-Dev Date: Sat, 1 Feb 2025 22:09:59 +0700 Subject: [PATCH 1/5] feat: add application api route --- .../Api/ApplicationsController.php | 76 +++++++++++++++++-- routes/api.php | 1 + 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 5265fbb37..cea5df566 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -1100,7 +1100,7 @@ class ApplicationsController extends Controller ], 422); } if (! $request->has('name')) { - $request->offsetSet('name', 'dockerfile-'.new Cuid2); + $request->offsetSet('name', 'dockerfile-' . new Cuid2); } $return = $this->validateDataApplications($request, $server); @@ -1187,7 +1187,7 @@ class ApplicationsController extends Controller ], 422); } if (! $request->has('name')) { - $request->offsetSet('name', 'docker-image-'.new Cuid2); + $request->offsetSet('name', 'docker-image-' . new Cuid2); } $return = $this->validateDataApplications($request, $server); if ($return instanceof \Illuminate\Http\JsonResponse) { @@ -1253,7 +1253,7 @@ class ApplicationsController extends Controller ], 422); } if (! $request->has('name')) { - $request->offsetSet('name', 'service'.new Cuid2); + $request->offsetSet('name', 'service' . new Cuid2); } $validationRules = [ 'docker_compose_raw' => 'string|required', @@ -1388,6 +1388,72 @@ class ApplicationsController extends Controller return response()->json($this->removeSensitiveData($application)); } + #[OA\Get( + summary: 'Get application logs.', + description: 'Get application logs by UUID.', + path: '/applications/{uuid}/logs', + operationId: 'get-application-logs-by-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Applications'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the application.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Get application logs by UUID.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + ref: '#/components/schemas/Application' + ) + ), + ] + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function logs_by_uuid(Request $request) + { + // TODO: Implement logs_by_uuid() method. + + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + $uuid = $request->route('uuid'); + if (! $uuid) { + return response()->json(['message' => 'UUID is required.'], 400); + } + $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); + if (! $application) { + return response()->json(['message' => 'Application not found.'], 404); + } + } + #[OA\Delete( summary: 'Delete', description: 'Delete application by UUID.', @@ -1690,7 +1756,7 @@ class ApplicationsController extends Controller $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) { $domain = trim($domain); if (filter_var($domain, FILTER_VALIDATE_URL) === false || ! preg_match('/^https?:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}/', $domain)) { - $errors[] = 'Invalid domain: '.$domain; + $errors[] = 'Invalid domain: ' . $domain; } return $domain; @@ -2926,7 +2992,7 @@ class ApplicationsController extends Controller $errors = []; $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) { if (filter_var($domain, FILTER_VALIDATE_URL) === false) { - $errors[] = 'Invalid domain: '.$domain; + $errors[] = 'Invalid domain: ' . $domain; } return str($domain)->trim()->lower(); diff --git a/routes/api.php b/routes/api.php index b884f4007..11c61aa87 100644 --- a/routes/api.php +++ b/routes/api.php @@ -88,6 +88,7 @@ Route::group([ Route::patch('/applications/{uuid}/envs', [ApplicationsController::class, 'update_env_by_uuid'])->middleware(['api.ability:write']); Route::delete('/applications/{uuid}/envs/{env_uuid}', [ApplicationsController::class, 'delete_env_by_uuid'])->middleware(['api.ability:write']); // Route::post('/applications/{uuid}/execute', [ApplicationsController::class, 'execute_command_by_uuid'])->middleware(['ability:write']); + Route::get('/applications/{uuid}/logs', [ApplicationsController::class, 'logs_by_uuid'])->middleware(['api.ability:write']); Route::match(['get', 'post'], '/applications/{uuid}/start', [ApplicationsController::class, 'action_deploy'])->middleware(['api.ability:write']); Route::match(['get', 'post'], '/applications/{uuid}/restart', [ApplicationsController::class, 'action_restart'])->middleware(['api.ability:write']); From e7988fc2951d2680a4cdca36b57c4e3a2d3439b1 Mon Sep 17 00:00:00 2001 From: Vann Date: Sat, 1 Feb 2025 17:21:38 +0000 Subject: [PATCH 2/5] feat: container logs --- .../Api/ApplicationsController.php | 29 +++++++++++++++---- bootstrap/helpers/docker.php | 15 ++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index cea5df566..56be2fe7d 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -1100,7 +1100,7 @@ class ApplicationsController extends Controller ], 422); } if (! $request->has('name')) { - $request->offsetSet('name', 'dockerfile-' . new Cuid2); + $request->offsetSet('name', 'dockerfile-'.new Cuid2); } $return = $this->validateDataApplications($request, $server); @@ -1187,7 +1187,7 @@ class ApplicationsController extends Controller ], 422); } if (! $request->has('name')) { - $request->offsetSet('name', 'docker-image-' . new Cuid2); + $request->offsetSet('name', 'docker-image-'.new Cuid2); } $return = $this->validateDataApplications($request, $server); if ($return instanceof \Illuminate\Http\JsonResponse) { @@ -1253,7 +1253,7 @@ class ApplicationsController extends Controller ], 422); } if (! $request->has('name')) { - $request->offsetSet('name', 'service' . new Cuid2); + $request->offsetSet('name', 'service'.new Cuid2); } $validationRules = [ 'docker_compose_raw' => 'string|required', @@ -1452,6 +1452,25 @@ class ApplicationsController extends Controller if (! $application) { return response()->json(['message' => 'Application not found.'], 404); } + + $container = getCurrentApplicationContainerStatus($application->destination->server, $application->id)->firstOrFail(); + // TODO: fix error when getting status + $status = getContainerStatus($application->destination->server, $container['name']); + // return response()->json([ + // 'logs' => $status, + // ]); + + if ($status !== 'running') { + return response()->json([ + 'message' => 'Application is not running.', + ], 400); + } + + $logs = getContainerLogs($application->destination->server, $container['Id']); + + return response()->json([ + 'logs' => 'yey', + ]); } #[OA\Delete( @@ -1756,7 +1775,7 @@ class ApplicationsController extends Controller $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) { $domain = trim($domain); if (filter_var($domain, FILTER_VALIDATE_URL) === false || ! preg_match('/^https?:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}/', $domain)) { - $errors[] = 'Invalid domain: ' . $domain; + $errors[] = 'Invalid domain: '.$domain; } return $domain; @@ -2992,7 +3011,7 @@ class ApplicationsController extends Controller $errors = []; $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) { if (filter_var($domain, FILTER_VALIDATE_URL) === false) { - $errors[] = 'Invalid domain: ' . $domain; + $errors[] = 'Invalid domain: '.$domain; } return str($domain)->trim()->lower(); diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 74d26e2f5..fbad04e64 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -852,6 +852,21 @@ function validateComposeFile(string $compose, int $server_id): string|Throwable } } +function getContainerLogs(Server $server, string $container_id, int $lines = 100): string +{ + if ($server->isSwarm()) { + $output = instant_remote_process([ + "docker service logs -n {$lines} {$container_id}", + ], $server); + } else { + $output = instant_remote_process([ + "docker logs -n {$lines} {$container_id}", + ], $server); + } + + return $output; +} + function escapeEnvVariables($value) { $search = ['\\', "\r", "\t", "\x0", '"', "'"]; From 7b60d76b770fa7e44ea1207b8de6241bc91d9ae2 Mon Sep 17 00:00:00 2001 From: Vann Date: Sun, 2 Feb 2025 10:01:01 +0000 Subject: [PATCH 3/5] feat: remove ansi color from log --- .../Api/ApplicationsController.php | 22 ++++++++++--------- bootstrap/helpers/docker.php | 2 ++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 56be2fe7d..784a43100 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -1438,8 +1438,6 @@ class ApplicationsController extends Controller )] public function logs_by_uuid(Request $request) { - // TODO: Implement logs_by_uuid() method. - $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); @@ -1453,23 +1451,27 @@ class ApplicationsController extends Controller return response()->json(['message' => 'Application not found.'], 404); } - $container = getCurrentApplicationContainerStatus($application->destination->server, $application->id)->firstOrFail(); - // TODO: fix error when getting status - $status = getContainerStatus($application->destination->server, $container['name']); - // return response()->json([ - // 'logs' => $status, - // ]); + $containers = getCurrentApplicationContainerStatus($application->destination->server, $application->id); + if ($containers->count() == 0) { + return response()->json([ + 'message' => 'Application is not running.', + ], 400); + } + + $container = $containers->first(); + + $status = getContainerStatus($application->destination->server, $container['Names']); if ($status !== 'running') { return response()->json([ 'message' => 'Application is not running.', ], 400); } - $logs = getContainerLogs($application->destination->server, $container['Id']); + $logs = getContainerLogs($application->destination->server, $container['ID']); return response()->json([ - 'logs' => 'yey', + 'logs' => $logs, ]); } diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index fbad04e64..696f6a8c4 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -864,6 +864,8 @@ function getContainerLogs(Server $server, string $container_id, int $lines = 100 ], $server); } + $output .= removeAnsiColors($output); + return $output; } From 11a5ec7c387961d1e074e4a7399a3b9ea4fa061a Mon Sep 17 00:00:00 2001 From: Vann Date: Sun, 2 Feb 2025 13:43:31 +0000 Subject: [PATCH 4/5] feat: add lines query parameter --- .../Controllers/Api/ApplicationsController.php | 14 +++++++++++++- routes/api.php | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 784a43100..b832a3128 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -1408,6 +1408,17 @@ class ApplicationsController extends Controller format: 'uuid', ) ), + new OA\Parameter( + name: 'lines', + in: 'query', + description: 'Number of lines to show from the end of the logs.', + required: false, + schema: new OA\Schema( + type: 'integer', + format: 'int32', + default: 100, + ) + ), ], responses: [ new OA\Response( @@ -1468,7 +1479,8 @@ class ApplicationsController extends Controller ], 400); } - $logs = getContainerLogs($application->destination->server, $container['ID']); + $lines = $request->query->get('lines', 100) ?: 100; + $logs = getContainerLogs($application->destination->server, $container['ID'], $lines); return response()->json([ 'logs' => $logs, diff --git a/routes/api.php b/routes/api.php index 11c61aa87..eaf155b3c 100644 --- a/routes/api.php +++ b/routes/api.php @@ -88,7 +88,7 @@ Route::group([ Route::patch('/applications/{uuid}/envs', [ApplicationsController::class, 'update_env_by_uuid'])->middleware(['api.ability:write']); Route::delete('/applications/{uuid}/envs/{env_uuid}', [ApplicationsController::class, 'delete_env_by_uuid'])->middleware(['api.ability:write']); // Route::post('/applications/{uuid}/execute', [ApplicationsController::class, 'execute_command_by_uuid'])->middleware(['ability:write']); - Route::get('/applications/{uuid}/logs', [ApplicationsController::class, 'logs_by_uuid'])->middleware(['api.ability:write']); + Route::get('/applications/{uuid}/logs', [ApplicationsController::class, 'logs_by_uuid'])->middleware(['api.ability:read']); Route::match(['get', 'post'], '/applications/{uuid}/start', [ApplicationsController::class, 'action_deploy'])->middleware(['api.ability:write']); Route::match(['get', 'post'], '/applications/{uuid}/restart', [ApplicationsController::class, 'action_restart'])->middleware(['api.ability:write']); From 2e652490c1d610d6af0a8d60f2e7cdac2c6ac598 Mon Sep 17 00:00:00 2001 From: Vann Date: Tue, 4 Feb 2025 02:56:15 +0000 Subject: [PATCH 5/5] chore: add openapi response --- app/Http/Controllers/Api/ApplicationsController.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index b832a3128..a797f1815 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -1428,7 +1428,10 @@ class ApplicationsController extends Controller new OA\MediaType( mediaType: 'application/json', schema: new OA\Schema( - ref: '#/components/schemas/Application' + type: 'object', + properties: [ + 'logs' => ['type' => 'string'], + ] ) ), ]