Merge remote-tracking branch 'origin/next' into volume-backups-server-s3
This commit is contained in:
commit
995ec5fbb6
17 changed files with 2125 additions and 47 deletions
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Actions\Service;
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Spatie\Activitylog\Contracts\Activity;
|
use Spatie\Activitylog\Contracts\Activity;
|
||||||
|
|
||||||
|
|
@ -12,7 +13,7 @@ class DeployServiceApplication
|
||||||
|
|
||||||
public string $jobQueue = 'high';
|
public string $jobQueue = 'high';
|
||||||
|
|
||||||
public function handle(ServiceApplication $serviceApplication, bool $pullLatestImages = false, bool $forceRebuild = false): Activity
|
public function handle(ServiceApplication|ServiceDatabase $serviceApplication, bool $pullLatestImages = false, bool $forceRebuild = false): Activity
|
||||||
{
|
{
|
||||||
$service = $serviceApplication->service;
|
$service = $serviceApplication->service;
|
||||||
$composeServiceName = $serviceApplication->name;
|
$composeServiceName = $serviceApplication->name;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Actions\Service;
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class RestartServiceApplication
|
class RestartServiceApplication
|
||||||
|
|
@ -11,7 +12,7 @@ class RestartServiceApplication
|
||||||
|
|
||||||
public string $jobQueue = 'high';
|
public string $jobQueue = 'high';
|
||||||
|
|
||||||
public function handle(ServiceApplication $serviceApplication): void
|
public function handle(ServiceApplication|ServiceDatabase $serviceApplication): void
|
||||||
{
|
{
|
||||||
$service = $serviceApplication->service;
|
$service = $serviceApplication->service;
|
||||||
$server = $service->destination->server;
|
$server = $service->destination->server;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Actions\Service;
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopServiceApplication
|
class StopServiceApplication
|
||||||
|
|
@ -11,7 +12,7 @@ class StopServiceApplication
|
||||||
|
|
||||||
public string $jobQueue = 'high';
|
public string $jobQueue = 'high';
|
||||||
|
|
||||||
public function handle(ServiceApplication $serviceApplication): void
|
public function handle(ServiceApplication|ServiceDatabase $serviceApplication): void
|
||||||
{
|
{
|
||||||
$service = $serviceApplication->service;
|
$service = $serviceApplication->service;
|
||||||
$server = $service->destination->server;
|
$server = $service->destination->server;
|
||||||
|
|
|
||||||
|
|
@ -424,6 +424,33 @@ public function update(Request $request, UpdateServiceApplicationFromApi $update
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Get service application logs',
|
||||||
|
description: 'Get Docker logs for a single compose service container.',
|
||||||
|
path: '/services/{uuid}/applications/{app_uuid}/logs',
|
||||||
|
operationId: 'post-service-application-logs-by-service-and-app-uuid',
|
||||||
|
security: [['bearerAuth' => []]],
|
||||||
|
tags: ['Service applications'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'app_uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'lines', in: 'query', required: false, schema: new OA\Schema(type: 'integer', format: 'int32', default: 100)),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Logs.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [new OA\Property(property: 'logs', type: 'string')],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new OA\Response(response: 400, ref: '#/components/responses/400'),
|
||||||
|
new OA\Response(response: 401, ref: '#/components/responses/401'),
|
||||||
|
new OA\Response(response: 404, ref: '#/components/responses/404'),
|
||||||
|
new OA\Response(response: 501, description: 'Swarm not supported.'),
|
||||||
|
]
|
||||||
|
)]
|
||||||
public function logs_by_uuid(Request $request): JsonResponse
|
public function logs_by_uuid(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
|
|
@ -540,6 +567,34 @@ public function logs_by_uuid(Request $request): JsonResponse
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Start or redeploy service application container',
|
||||||
|
description: 'Runs docker compose up for a single compose service (no-deps), optionally pulling the image and rebuilding.',
|
||||||
|
path: '/services/{uuid}/applications/{app_uuid}/start',
|
||||||
|
operationId: 'post-start-service-application-by-service-and-app-uuid',
|
||||||
|
security: [['bearerAuth' => []]],
|
||||||
|
tags: ['Service applications'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'app_uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'force', in: 'query', required: false, schema: new OA\Schema(type: 'boolean', default: false)),
|
||||||
|
new OA\Parameter(name: 'latest', in: 'query', required: false, schema: new OA\Schema(type: 'boolean', default: false)),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Deploy request queued.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [new OA\Property(property: 'message', type: 'string')],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new OA\Response(response: 400, ref: '#/components/responses/400'),
|
||||||
|
new OA\Response(response: 401, ref: '#/components/responses/401'),
|
||||||
|
new OA\Response(response: 404, ref: '#/components/responses/404'),
|
||||||
|
new OA\Response(response: 501, description: 'Swarm not supported.'),
|
||||||
|
]
|
||||||
|
)]
|
||||||
public function action_start(Request $request): JsonResponse
|
public function action_start(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
|
|
@ -635,6 +690,32 @@ public function action_start(Request $request): JsonResponse
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Restart service application container',
|
||||||
|
description: 'Restarts a single compose service container.',
|
||||||
|
path: '/services/{uuid}/applications/{app_uuid}/restart',
|
||||||
|
operationId: 'post-restart-service-application-by-service-and-app-uuid',
|
||||||
|
security: [['bearerAuth' => []]],
|
||||||
|
tags: ['Service applications'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'app_uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Restart queued.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [new OA\Property(property: 'message', type: 'string')],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new OA\Response(response: 400, ref: '#/components/responses/400'),
|
||||||
|
new OA\Response(response: 401, ref: '#/components/responses/401'),
|
||||||
|
new OA\Response(response: 404, ref: '#/components/responses/404'),
|
||||||
|
new OA\Response(response: 501, description: 'Swarm not supported.'),
|
||||||
|
]
|
||||||
|
)]
|
||||||
public function action_restart(Request $request): JsonResponse
|
public function action_restart(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
|
|
@ -727,6 +808,32 @@ public function action_restart(Request $request): JsonResponse
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Stop service application container',
|
||||||
|
description: 'Stops a single compose service container.',
|
||||||
|
path: '/services/{uuid}/applications/{app_uuid}/stop',
|
||||||
|
operationId: 'post-stop-service-application-by-service-and-app-uuid',
|
||||||
|
security: [['bearerAuth' => []]],
|
||||||
|
tags: ['Service applications'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'app_uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Stop queued.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [new OA\Property(property: 'message', type: 'string')],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new OA\Response(response: 400, ref: '#/components/responses/400'),
|
||||||
|
new OA\Response(response: 401, ref: '#/components/responses/401'),
|
||||||
|
new OA\Response(response: 404, ref: '#/components/responses/404'),
|
||||||
|
new OA\Response(response: 501, description: 'Swarm not supported.'),
|
||||||
|
]
|
||||||
|
)]
|
||||||
public function action_stop(Request $request): JsonResponse
|
public function action_stop(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
|
|
|
||||||
452
app/Http/Controllers/Api/ServiceDatabasesController.php
Normal file
452
app/Http/Controllers/Api/ServiceDatabasesController.php
Normal file
|
|
@ -0,0 +1,452 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Database\StopDatabaseProxy;
|
||||||
|
use App\Actions\Service\DeployServiceApplication;
|
||||||
|
use App\Actions\Service\RestartServiceApplication;
|
||||||
|
use App\Actions\Service\StopServiceApplication;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class ServiceDatabasesController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveData(ServiceDatabase $serviceDatabase): array
|
||||||
|
{
|
||||||
|
$serviceDatabase->makeHidden([
|
||||||
|
'id',
|
||||||
|
'service',
|
||||||
|
'service_id',
|
||||||
|
'resourceable',
|
||||||
|
'resourceable_id',
|
||||||
|
'resourceable_type',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$serialized = serializeApiResponse($serviceDatabase);
|
||||||
|
|
||||||
|
if ($serialized instanceof Collection) {
|
||||||
|
return $serialized->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (array) $serialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveService(Request $request, int $teamId): ?Service
|
||||||
|
{
|
||||||
|
return Service::whereRelation('environment.project.team', 'id', $teamId)
|
||||||
|
->whereUuid($request->route('uuid'))
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveServiceDatabase(Request $request, Service $service): ?ServiceDatabase
|
||||||
|
{
|
||||||
|
return $service->databases()
|
||||||
|
->where('uuid', $request->route('database_uuid'))
|
||||||
|
->with(['service.destination.server'])
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function swarmNotSupportedResponse(): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'This operation is not supported for Swarm servers yet.',
|
||||||
|
], 501);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List service databases',
|
||||||
|
description: 'List compose databases for a single service.',
|
||||||
|
path: '/services/{uuid}/databases',
|
||||||
|
operationId: 'list-service-databases-by-service-uuid',
|
||||||
|
security: [['bearerAuth' => []]],
|
||||||
|
tags: ['Service databases'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', description: 'Service UUID.', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(response: 200, description: 'Service databases.', content: new OA\JsonContent(type: 'array', items: new OA\Items(type: 'object'))),
|
||||||
|
new OA\Response(response: 401, ref: '#/components/responses/401'),
|
||||||
|
new OA\Response(response: 404, ref: '#/components/responses/404'),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function index(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$service = $this->resolveService($request, $teamId);
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->authorize('view', $service);
|
||||||
|
|
||||||
|
$databases = $service->databases()
|
||||||
|
->get()
|
||||||
|
->map(fn (ServiceDatabase $database) => $this->removeSensitiveData($database));
|
||||||
|
|
||||||
|
return response()->json($databases);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get service database',
|
||||||
|
description: 'Get a compose database by service UUID and database UUID.',
|
||||||
|
path: '/services/{uuid}/databases/{database_uuid}',
|
||||||
|
operationId: 'get-service-database-by-service-and-database-uuid',
|
||||||
|
security: [['bearerAuth' => []]],
|
||||||
|
tags: ['Service databases'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', description: 'Service UUID.', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'database_uuid', in: 'path', description: 'Service database UUID.', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(response: 200, description: 'Service database.', content: new OA\JsonContent(type: 'object')),
|
||||||
|
new OA\Response(response: 401, ref: '#/components/responses/401'),
|
||||||
|
new OA\Response(response: 404, ref: '#/components/responses/404'),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function show(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$service = $this->resolveService($request, $teamId);
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$serviceDatabase = $this->resolveServiceDatabase($request, $service);
|
||||||
|
if (! $serviceDatabase) {
|
||||||
|
return response()->json(['message' => 'Service database not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->authorize('view', $serviceDatabase);
|
||||||
|
|
||||||
|
return response()->json($this->removeSensitiveData($serviceDatabase));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Patch(
|
||||||
|
summary: 'Update service database',
|
||||||
|
description: 'Update mutable fields for a compose service database.',
|
||||||
|
path: '/services/{uuid}/databases/{database_uuid}',
|
||||||
|
operationId: 'patch-service-database-by-service-and-database-uuid',
|
||||||
|
security: [['bearerAuth' => []]],
|
||||||
|
tags: ['Service databases'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', description: 'Service UUID.', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'database_uuid', in: 'path', description: 'Service database UUID.', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'human_name', type: 'string', nullable: true),
|
||||||
|
new OA\Property(property: 'description', type: 'string', nullable: true),
|
||||||
|
new OA\Property(property: 'image', type: 'string'),
|
||||||
|
new OA\Property(property: 'exclude_from_status', type: 'boolean'),
|
||||||
|
new OA\Property(property: 'is_log_drain_enabled', type: 'boolean'),
|
||||||
|
new OA\Property(property: 'is_public', type: 'boolean'),
|
||||||
|
new OA\Property(property: 'public_port', type: 'integer', nullable: true, minimum: 1, maximum: 65535),
|
||||||
|
new OA\Property(property: 'public_port_timeout', type: 'integer', nullable: true, minimum: 1),
|
||||||
|
],
|
||||||
|
additionalProperties: false,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(response: 200, description: 'Updated service database.', content: new OA\JsonContent(type: 'object')),
|
||||||
|
new OA\Response(response: 400, ref: '#/components/responses/400'),
|
||||||
|
new OA\Response(response: 401, ref: '#/components/responses/401'),
|
||||||
|
new OA\Response(response: 404, ref: '#/components/responses/404'),
|
||||||
|
new OA\Response(response: 422, ref: '#/components/responses/422'),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function update(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$invalidRequest = validateIncomingRequest($request);
|
||||||
|
if ($invalidRequest instanceof JsonResponse) {
|
||||||
|
return $invalidRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
$service = $this->resolveService($request, $teamId);
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$serviceDatabase = $this->resolveServiceDatabase($request, $service);
|
||||||
|
if (! $serviceDatabase) {
|
||||||
|
return response()->json(['message' => 'Service database not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->authorize('update', $serviceDatabase);
|
||||||
|
|
||||||
|
$payload = $request->json()->all();
|
||||||
|
if (empty($payload)) {
|
||||||
|
$payload = $request->request->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedFields = [
|
||||||
|
'human_name',
|
||||||
|
'description',
|
||||||
|
'image',
|
||||||
|
'exclude_from_status',
|
||||||
|
'is_log_drain_enabled',
|
||||||
|
'is_public',
|
||||||
|
'public_port',
|
||||||
|
'public_port_timeout',
|
||||||
|
];
|
||||||
|
$validator = Validator::make($payload, [
|
||||||
|
'human_name' => 'nullable|string|max:255',
|
||||||
|
'description' => 'nullable|string',
|
||||||
|
'image' => 'sometimes|string',
|
||||||
|
'exclude_from_status' => 'sometimes|boolean',
|
||||||
|
'is_log_drain_enabled' => 'sometimes|boolean',
|
||||||
|
'is_public' => 'sometimes|boolean',
|
||||||
|
'public_port' => 'nullable|integer|min:1|max:65535',
|
||||||
|
'public_port_timeout' => 'nullable|integer|min:1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($payload), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = $serviceDatabase->service->destination->server;
|
||||||
|
if (($payload['is_log_drain_enabled'] ?? false) && ! $server->isLogDrainEnabled()) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => ['is_log_drain_enabled' => ['Log drain is not enabled on the server for this service.']],
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$isPublic = $payload['is_public'] ?? $serviceDatabase->is_public;
|
||||||
|
$publicPort = $payload['public_port'] ?? $serviceDatabase->public_port;
|
||||||
|
if ($isPublic && ! $publicPort) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => ['public_port' => ['A public port is required when the database is public.']],
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
if ($isPublic && isPublicPortAlreadyUsed($server, $publicPort, $serviceDatabase->id)) {
|
||||||
|
return response()->json(['message' => 'Public port already used by another database.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$shouldStartProxy = ($payload['is_public'] ?? null) === true && ! $serviceDatabase->is_public;
|
||||||
|
$shouldStopProxy = ($payload['is_public'] ?? null) === false && $serviceDatabase->is_public;
|
||||||
|
|
||||||
|
$serviceDatabase->fill($payload);
|
||||||
|
$serviceDatabase->save();
|
||||||
|
$serviceDatabase->refresh();
|
||||||
|
updateCompose($serviceDatabase);
|
||||||
|
|
||||||
|
if ($shouldStartProxy) {
|
||||||
|
StartDatabaseProxy::dispatch($serviceDatabase);
|
||||||
|
} elseif ($shouldStopProxy) {
|
||||||
|
StopDatabaseProxy::dispatch($serviceDatabase);
|
||||||
|
}
|
||||||
|
|
||||||
|
auditLog('api.service_database.updated', [
|
||||||
|
'team_id' => $teamId,
|
||||||
|
'service_uuid' => $service->uuid,
|
||||||
|
'service_database_uuid' => $serviceDatabase->uuid,
|
||||||
|
'changed_fields' => array_keys($payload),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json($this->removeSensitiveData($serviceDatabase));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get service database logs',
|
||||||
|
description: 'Get Docker logs for a compose database container.',
|
||||||
|
path: '/services/{uuid}/databases/{database_uuid}/logs',
|
||||||
|
operationId: 'get-service-database-logs-by-service-and-database-uuid',
|
||||||
|
security: [['bearerAuth' => []]],
|
||||||
|
tags: ['Service databases'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'database_uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'lines', in: 'query', required: false, schema: new OA\Schema(type: 'integer', format: 'int32', default: 100)),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(response: 200, description: 'Logs.', content: new OA\JsonContent(type: 'object', properties: [new OA\Property(property: 'logs', type: 'string')])),
|
||||||
|
new OA\Response(response: 400, ref: '#/components/responses/400'),
|
||||||
|
new OA\Response(response: 401, ref: '#/components/responses/401'),
|
||||||
|
new OA\Response(response: 404, ref: '#/components/responses/404'),
|
||||||
|
new OA\Response(response: 501, description: 'Swarm not supported.'),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function logs(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$resolved = $this->resolveDatabaseRequest($request, 'view');
|
||||||
|
if ($resolved instanceof JsonResponse) {
|
||||||
|
return $resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$serviceDatabase, $server] = $resolved;
|
||||||
|
$containerName = $serviceDatabase->name.'-'.$serviceDatabase->service->uuid;
|
||||||
|
if (getContainerStatus($server, $containerName) !== 'running') {
|
||||||
|
return response()->json(['message' => 'Service database container is not running.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = (int) ($request->query('lines', 100) ?: 100);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'logs' => getContainerLogs($server, $containerName, $lines),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Start or redeploy service database container',
|
||||||
|
description: 'Run docker compose up for a single compose database.',
|
||||||
|
path: '/services/{uuid}/databases/{database_uuid}/start',
|
||||||
|
operationId: 'start-service-database-by-service-and-database-uuid',
|
||||||
|
security: [['bearerAuth' => []]],
|
||||||
|
tags: ['Service databases'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'database_uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'force', in: 'query', required: false, schema: new OA\Schema(type: 'boolean', default: false)),
|
||||||
|
new OA\Parameter(name: 'latest', in: 'query', required: false, schema: new OA\Schema(type: 'boolean', default: false)),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(response: 200, description: 'Deploy request queued.', content: new OA\JsonContent(type: 'object', properties: [new OA\Property(property: 'message', type: 'string')])),
|
||||||
|
new OA\Response(response: 400, ref: '#/components/responses/400'),
|
||||||
|
new OA\Response(response: 401, ref: '#/components/responses/401'),
|
||||||
|
new OA\Response(response: 404, ref: '#/components/responses/404'),
|
||||||
|
new OA\Response(response: 501, description: 'Swarm not supported.'),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function start(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$resolved = $this->resolveDatabaseRequest($request, 'deploy');
|
||||||
|
if ($resolved instanceof JsonResponse) {
|
||||||
|
return $resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$serviceDatabase] = $resolved;
|
||||||
|
DeployServiceApplication::dispatch(
|
||||||
|
$serviceDatabase,
|
||||||
|
$request->boolean('latest'),
|
||||||
|
$request->boolean('force'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Service database deploy request queued.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Restart service database container',
|
||||||
|
description: 'Restart a compose database container.',
|
||||||
|
path: '/services/{uuid}/databases/{database_uuid}/restart',
|
||||||
|
operationId: 'restart-service-database-by-service-and-database-uuid',
|
||||||
|
security: [['bearerAuth' => []]],
|
||||||
|
tags: ['Service databases'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'database_uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(response: 200, description: 'Restart queued.', content: new OA\JsonContent(type: 'object', properties: [new OA\Property(property: 'message', type: 'string')])),
|
||||||
|
new OA\Response(response: 400, ref: '#/components/responses/400'),
|
||||||
|
new OA\Response(response: 401, ref: '#/components/responses/401'),
|
||||||
|
new OA\Response(response: 404, ref: '#/components/responses/404'),
|
||||||
|
new OA\Response(response: 501, description: 'Swarm not supported.'),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function restart(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$resolved = $this->resolveDatabaseRequest($request, 'deploy');
|
||||||
|
if ($resolved instanceof JsonResponse) {
|
||||||
|
return $resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$serviceDatabase] = $resolved;
|
||||||
|
RestartServiceApplication::dispatch($serviceDatabase);
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Service database restart request queued.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Stop service database container',
|
||||||
|
description: 'Stop a compose database container.',
|
||||||
|
path: '/services/{uuid}/databases/{database_uuid}/stop',
|
||||||
|
operationId: 'stop-service-database-by-service-and-database-uuid',
|
||||||
|
security: [['bearerAuth' => []]],
|
||||||
|
tags: ['Service databases'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'database_uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(response: 200, description: 'Stop queued.', content: new OA\JsonContent(type: 'object', properties: [new OA\Property(property: 'message', type: 'string')])),
|
||||||
|
new OA\Response(response: 400, ref: '#/components/responses/400'),
|
||||||
|
new OA\Response(response: 401, ref: '#/components/responses/401'),
|
||||||
|
new OA\Response(response: 404, ref: '#/components/responses/404'),
|
||||||
|
new OA\Response(response: 501, description: 'Swarm not supported.'),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function stop(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$resolved = $this->resolveDatabaseRequest($request, 'deploy');
|
||||||
|
if ($resolved instanceof JsonResponse) {
|
||||||
|
return $resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$serviceDatabase] = $resolved;
|
||||||
|
StopServiceApplication::dispatch($serviceDatabase);
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Service database stop request queued.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveDatabaseRequest(Request $request, string $ability): array|JsonResponse
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$service = $this->resolveService($request, $teamId);
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$serviceDatabase = $this->resolveServiceDatabase($request, $service);
|
||||||
|
if (! $serviceDatabase) {
|
||||||
|
return response()->json(['message' => 'Service database not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->authorize($ability, $serviceDatabase);
|
||||||
|
|
||||||
|
$server = $serviceDatabase->service->destination->server;
|
||||||
|
if ($server->isSwarm()) {
|
||||||
|
return $this->swarmNotSupportedResponse();
|
||||||
|
}
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
return response()->json(['message' => 'Server is not functional.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$serviceDatabase, $server];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1053,11 +1053,6 @@ public function delete_by_uuid(Request $request)
|
||||||
properties: [
|
properties: [
|
||||||
'name' => ['type' => 'string', 'description' => 'The service name.'],
|
'name' => ['type' => 'string', 'description' => 'The service name.'],
|
||||||
'description' => ['type' => 'string', 'description' => 'The service description.'],
|
'description' => ['type' => 'string', 'description' => 'The service description.'],
|
||||||
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
|
||||||
'environment_name' => ['type' => 'string', 'description' => 'The environment name.'],
|
|
||||||
'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID.'],
|
|
||||||
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
|
||||||
'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'],
|
|
||||||
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the service should be deployed instantly.'],
|
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the service should be deployed instantly.'],
|
||||||
'connect_to_docker_network' => ['type' => 'boolean', 'default' => false, 'description' => 'Connect the service to the predefined docker network.'],
|
'connect_to_docker_network' => ['type' => 'boolean', 'default' => false, 'description' => 'Connect the service to the predefined docker network.'],
|
||||||
'docker_compose_raw' => ['type' => 'string', 'description' => 'The base64 encoded Docker Compose content.'],
|
'docker_compose_raw' => ['type' => 'string', 'description' => 'The base64 encoded Docker Compose content.'],
|
||||||
|
|
@ -1942,7 +1937,7 @@ public function delete_env_by_uuid(Request $request)
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
public function move_by_uuid(Request $request): \Illuminate\Http\JsonResponse
|
public function move_by_uuid(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@
|
||||||
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
|
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
|
||||||
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
|
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
|
||||||
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
|
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
|
||||||
'settings' => new OA\Property(ref: '#/components/schemas/ApplicationSetting'),
|
new OA\Property(property: 'settings', ref: '#/components/schemas/ApplicationSetting'),
|
||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,13 @@ class ServiceDatabase extends BaseModel
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
'exclude_from_status' => 'boolean',
|
||||||
|
'is_public' => 'boolean',
|
||||||
|
'is_log_drain_enabled' => 'boolean',
|
||||||
|
'is_include_timestamps' => 'boolean',
|
||||||
|
'is_gzip_enabled' => 'boolean',
|
||||||
|
'is_stripprefix_enabled' => 'boolean',
|
||||||
|
'public_port' => 'integer',
|
||||||
'public_port_timeout' => 'integer',
|
'public_port_timeout' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,14 @@ public function update(User $user, ServiceDatabase $serviceDatabase): bool
|
||||||
return Gate::allows('update', $serviceDatabase->service);
|
return Gate::allows('update', $serviceDatabase->service);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can deploy or run lifecycle actions on the parent service stack.
|
||||||
|
*/
|
||||||
|
public function deploy(User $user, ServiceDatabase $serviceDatabase): bool
|
||||||
|
{
|
||||||
|
return Gate::allows('deploy', $serviceDatabase->service);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can delete the model.
|
* Determine whether the user can delete the model.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
778
openapi.json
778
openapi.json
|
|
@ -12731,6 +12731,76 @@
|
||||||
"bearerAuth": []
|
"bearerAuth": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Service applications"
|
||||||
|
],
|
||||||
|
"summary": "Get service application logs",
|
||||||
|
"description": "Get Docker logs for a single compose service container.",
|
||||||
|
"operationId": "post-service-application-logs-by-service-and-app-uuid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "app_uuid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lines",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"default": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Logs.",
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"logs": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#\/components\/responses\/400"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#\/components\/responses\/401"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#\/components\/responses\/404"
|
||||||
|
},
|
||||||
|
"501": {
|
||||||
|
"description": "Swarm not supported."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"\/services\/{uuid}\/applications\/{app_uuid}\/start": {
|
"\/services\/{uuid}\/applications\/{app_uuid}\/start": {
|
||||||
|
|
@ -12812,6 +12882,84 @@
|
||||||
"bearerAuth": []
|
"bearerAuth": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Service applications"
|
||||||
|
],
|
||||||
|
"summary": "Start or redeploy service application container",
|
||||||
|
"description": "Runs docker compose up for a single compose service (no-deps), optionally pulling the image and rebuilding.",
|
||||||
|
"operationId": "post-start-service-application-by-service-and-app-uuid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "app_uuid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "force",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "latest",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Deploy request queued.",
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#\/components\/responses\/400"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#\/components\/responses\/401"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#\/components\/responses\/404"
|
||||||
|
},
|
||||||
|
"501": {
|
||||||
|
"description": "Swarm not supported."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"\/services\/{uuid}\/applications\/{app_uuid}\/restart": {
|
"\/services\/{uuid}\/applications\/{app_uuid}\/restart": {
|
||||||
|
|
@ -12873,6 +13021,66 @@
|
||||||
"bearerAuth": []
|
"bearerAuth": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Service applications"
|
||||||
|
],
|
||||||
|
"summary": "Restart service application container",
|
||||||
|
"description": "Restarts a single compose service container.",
|
||||||
|
"operationId": "post-restart-service-application-by-service-and-app-uuid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "app_uuid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Restart queued.",
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#\/components\/responses\/400"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#\/components\/responses\/401"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#\/components\/responses\/404"
|
||||||
|
},
|
||||||
|
"501": {
|
||||||
|
"description": "Swarm not supported."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"\/services\/{uuid}\/applications\/{app_uuid}\/stop": {
|
"\/services\/{uuid}\/applications\/{app_uuid}\/stop": {
|
||||||
|
|
@ -12934,6 +13142,550 @@
|
||||||
"bearerAuth": []
|
"bearerAuth": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Service applications"
|
||||||
|
],
|
||||||
|
"summary": "Stop service application container",
|
||||||
|
"description": "Stops a single compose service container.",
|
||||||
|
"operationId": "post-stop-service-application-by-service-and-app-uuid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "app_uuid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Stop queued.",
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#\/components\/responses\/400"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#\/components\/responses\/401"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#\/components\/responses\/404"
|
||||||
|
},
|
||||||
|
"501": {
|
||||||
|
"description": "Swarm not supported."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"\/services\/{uuid}\/databases": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Service databases"
|
||||||
|
],
|
||||||
|
"summary": "List service databases",
|
||||||
|
"description": "List compose databases for a single service.",
|
||||||
|
"operationId": "list-service-databases-by-service-uuid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"in": "path",
|
||||||
|
"description": "Service UUID.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Service databases.",
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#\/components\/responses\/401"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#\/components\/responses\/404"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"\/services\/{uuid}\/databases\/{database_uuid}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Service databases"
|
||||||
|
],
|
||||||
|
"summary": "Get service database",
|
||||||
|
"description": "Get a compose database by service UUID and database UUID.",
|
||||||
|
"operationId": "get-service-database-by-service-and-database-uuid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"in": "path",
|
||||||
|
"description": "Service UUID.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "database_uuid",
|
||||||
|
"in": "path",
|
||||||
|
"description": "Service database UUID.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Service database.",
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#\/components\/responses\/401"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#\/components\/responses\/404"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"tags": [
|
||||||
|
"Service databases"
|
||||||
|
],
|
||||||
|
"summary": "Update service database",
|
||||||
|
"description": "Update mutable fields for a compose service database.",
|
||||||
|
"operationId": "patch-service-database-by-service-and-database-uuid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"in": "path",
|
||||||
|
"description": "Service UUID.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "database_uuid",
|
||||||
|
"in": "path",
|
||||||
|
"description": "Service database UUID.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"human_name": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"exclude_from_status": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"is_log_drain_enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"is_public": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"public_port": {
|
||||||
|
"type": [
|
||||||
|
"integer",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"maximum": 65535,
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"public_port_timeout": {
|
||||||
|
"type": [
|
||||||
|
"integer",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"minimum": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Updated service database.",
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#\/components\/responses\/400"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#\/components\/responses\/401"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#\/components\/responses\/404"
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"$ref": "#\/components\/responses\/422"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"\/services\/{uuid}\/databases\/{database_uuid}\/logs": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Service databases"
|
||||||
|
],
|
||||||
|
"summary": "Get service database logs",
|
||||||
|
"description": "Get Docker logs for a compose database container.",
|
||||||
|
"operationId": "get-service-database-logs-by-service-and-database-uuid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "database_uuid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lines",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"default": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Logs.",
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"logs": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#\/components\/responses\/400"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#\/components\/responses\/401"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#\/components\/responses\/404"
|
||||||
|
},
|
||||||
|
"501": {
|
||||||
|
"description": "Swarm not supported."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"\/services\/{uuid}\/databases\/{database_uuid}\/start": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Service databases"
|
||||||
|
],
|
||||||
|
"summary": "Start or redeploy service database container",
|
||||||
|
"description": "Run docker compose up for a single compose database.",
|
||||||
|
"operationId": "start-service-database-by-service-and-database-uuid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "database_uuid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "force",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "latest",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Deploy request queued.",
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#\/components\/responses\/400"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#\/components\/responses\/401"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#\/components\/responses\/404"
|
||||||
|
},
|
||||||
|
"501": {
|
||||||
|
"description": "Swarm not supported."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"\/services\/{uuid}\/databases\/{database_uuid}\/restart": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Service databases"
|
||||||
|
],
|
||||||
|
"summary": "Restart service database container",
|
||||||
|
"description": "Restart a compose database container.",
|
||||||
|
"operationId": "restart-service-database-by-service-and-database-uuid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "database_uuid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Restart queued.",
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#\/components\/responses\/400"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#\/components\/responses\/401"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#\/components\/responses\/404"
|
||||||
|
},
|
||||||
|
"501": {
|
||||||
|
"description": "Swarm not supported."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"\/services\/{uuid}\/databases\/{database_uuid}\/stop": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Service databases"
|
||||||
|
],
|
||||||
|
"summary": "Stop service database container",
|
||||||
|
"description": "Stop a compose database container.",
|
||||||
|
"operationId": "stop-service-database-by-service-and-database-uuid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "database_uuid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Stop queued.",
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#\/components\/responses\/400"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#\/components\/responses\/401"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#\/components\/responses\/404"
|
||||||
|
},
|
||||||
|
"501": {
|
||||||
|
"description": "Swarm not supported."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"\/services": {
|
"\/services": {
|
||||||
|
|
@ -13338,26 +14090,6 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The service description."
|
"description": "The service description."
|
||||||
},
|
},
|
||||||
"project_uuid": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The project UUID."
|
|
||||||
},
|
|
||||||
"environment_name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The environment name."
|
|
||||||
},
|
|
||||||
"environment_uuid": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The environment UUID."
|
|
||||||
},
|
|
||||||
"server_uuid": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The server UUID."
|
|
||||||
},
|
|
||||||
"destination_uuid": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The destination UUID."
|
|
||||||
},
|
|
||||||
"instant_deploy": {
|
"instant_deploy": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "The flag to indicate if the service should be deployed instantly."
|
"description": "The flag to indicate if the service should be deployed instantly."
|
||||||
|
|
@ -15514,7 +16246,7 @@
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"description": "Password for HTTP Basic Authentication"
|
"description": "Password for HTTP Basic Authentication"
|
||||||
},
|
},
|
||||||
"": {
|
"settings": {
|
||||||
"$ref": "#\/components\/schemas\/ApplicationSetting"
|
"$ref": "#\/components\/schemas\/ApplicationSetting"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -16568,6 +17300,10 @@
|
||||||
"name": "Service applications",
|
"name": "Service applications",
|
||||||
"description": "Service applications"
|
"description": "Service applications"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Service databases",
|
||||||
|
"description": "Service databases"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Services",
|
"name": "Services",
|
||||||
"description": "Services"
|
"description": "Services"
|
||||||
|
|
|
||||||
512
openapi.yaml
512
openapi.yaml
|
|
@ -8173,6 +8173,53 @@ paths:
|
||||||
security:
|
security:
|
||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- 'Service applications'
|
||||||
|
summary: 'Get service application logs'
|
||||||
|
description: 'Get Docker logs for a single compose service container.'
|
||||||
|
operationId: post-service-application-logs-by-service-and-app-uuid
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
-
|
||||||
|
name: app_uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
-
|
||||||
|
name: lines
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
default: 100
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Logs.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
logs: { type: string }
|
||||||
|
type: object
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
'501':
|
||||||
|
description: 'Swarm not supported.'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
'/services/{uuid}/applications/{app_uuid}/start':
|
'/services/{uuid}/applications/{app_uuid}/start':
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
|
@ -8229,6 +8276,59 @@ paths:
|
||||||
security:
|
security:
|
||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- 'Service applications'
|
||||||
|
summary: 'Start or redeploy service application container'
|
||||||
|
description: 'Runs docker compose up for a single compose service (no-deps), optionally pulling the image and rebuilding.'
|
||||||
|
operationId: post-start-service-application-by-service-and-app-uuid
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
-
|
||||||
|
name: app_uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
-
|
||||||
|
name: force
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
-
|
||||||
|
name: latest
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 'Deploy request queued.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
message: { type: string }
|
||||||
|
type: object
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
'501':
|
||||||
|
description: 'Swarm not supported.'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
'/services/{uuid}/applications/{app_uuid}/restart':
|
'/services/{uuid}/applications/{app_uuid}/restart':
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
|
@ -8269,6 +8369,45 @@ paths:
|
||||||
security:
|
security:
|
||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- 'Service applications'
|
||||||
|
summary: 'Restart service application container'
|
||||||
|
description: 'Restarts a single compose service container.'
|
||||||
|
operationId: post-restart-service-application-by-service-and-app-uuid
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
-
|
||||||
|
name: app_uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 'Restart queued.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
message: { type: string }
|
||||||
|
type: object
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
'501':
|
||||||
|
description: 'Swarm not supported.'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
'/services/{uuid}/applications/{app_uuid}/stop':
|
'/services/{uuid}/applications/{app_uuid}/stop':
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
|
@ -8309,6 +8448,359 @@ paths:
|
||||||
security:
|
security:
|
||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- 'Service applications'
|
||||||
|
summary: 'Stop service application container'
|
||||||
|
description: 'Stops a single compose service container.'
|
||||||
|
operationId: post-stop-service-application-by-service-and-app-uuid
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
-
|
||||||
|
name: app_uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 'Stop queued.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
message: { type: string }
|
||||||
|
type: object
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
'501':
|
||||||
|
description: 'Swarm not supported.'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
|
'/services/{uuid}/databases':
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- 'Service databases'
|
||||||
|
summary: 'List service databases'
|
||||||
|
description: 'List compose databases for a single service.'
|
||||||
|
operationId: list-service-databases-by-service-uuid
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
description: 'Service UUID.'
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 'Service databases.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
|
'/services/{uuid}/databases/{database_uuid}':
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- 'Service databases'
|
||||||
|
summary: 'Get service database'
|
||||||
|
description: 'Get a compose database by service UUID and database UUID.'
|
||||||
|
operationId: get-service-database-by-service-and-database-uuid
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
description: 'Service UUID.'
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
-
|
||||||
|
name: database_uuid
|
||||||
|
in: path
|
||||||
|
description: 'Service database UUID.'
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 'Service database.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
|
patch:
|
||||||
|
tags:
|
||||||
|
- 'Service databases'
|
||||||
|
summary: 'Update service database'
|
||||||
|
description: 'Update mutable fields for a compose service database.'
|
||||||
|
operationId: patch-service-database-by-service-and-database-uuid
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
description: 'Service UUID.'
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
-
|
||||||
|
name: database_uuid
|
||||||
|
in: path
|
||||||
|
description: 'Service database UUID.'
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
human_name:
|
||||||
|
type: [string, 'null']
|
||||||
|
description:
|
||||||
|
type: [string, 'null']
|
||||||
|
image:
|
||||||
|
type: string
|
||||||
|
exclude_from_status:
|
||||||
|
type: boolean
|
||||||
|
is_log_drain_enabled:
|
||||||
|
type: boolean
|
||||||
|
is_public:
|
||||||
|
type: boolean
|
||||||
|
public_port:
|
||||||
|
type: [integer, 'null']
|
||||||
|
maximum: 65535
|
||||||
|
minimum: 1
|
||||||
|
public_port_timeout:
|
||||||
|
type: [integer, 'null']
|
||||||
|
minimum: 1
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 'Updated service database.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
'422':
|
||||||
|
$ref: '#/components/responses/422'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
|
'/services/{uuid}/databases/{database_uuid}/logs':
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- 'Service databases'
|
||||||
|
summary: 'Get service database logs'
|
||||||
|
description: 'Get Docker logs for a compose database container.'
|
||||||
|
operationId: get-service-database-logs-by-service-and-database-uuid
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
-
|
||||||
|
name: database_uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
-
|
||||||
|
name: lines
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
default: 100
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Logs.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
logs: { type: string }
|
||||||
|
type: object
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
'501':
|
||||||
|
description: 'Swarm not supported.'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
|
'/services/{uuid}/databases/{database_uuid}/start':
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- 'Service databases'
|
||||||
|
summary: 'Start or redeploy service database container'
|
||||||
|
description: 'Run docker compose up for a single compose database.'
|
||||||
|
operationId: start-service-database-by-service-and-database-uuid
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
-
|
||||||
|
name: database_uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
-
|
||||||
|
name: force
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
-
|
||||||
|
name: latest
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 'Deploy request queued.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
message: { type: string }
|
||||||
|
type: object
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
'501':
|
||||||
|
description: 'Swarm not supported.'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
|
'/services/{uuid}/databases/{database_uuid}/restart':
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- 'Service databases'
|
||||||
|
summary: 'Restart service database container'
|
||||||
|
description: 'Restart a compose database container.'
|
||||||
|
operationId: restart-service-database-by-service-and-database-uuid
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
-
|
||||||
|
name: database_uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 'Restart queued.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
message: { type: string }
|
||||||
|
type: object
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
'501':
|
||||||
|
description: 'Swarm not supported.'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
|
'/services/{uuid}/databases/{database_uuid}/stop':
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- 'Service databases'
|
||||||
|
summary: 'Stop service database container'
|
||||||
|
description: 'Stop a compose database container.'
|
||||||
|
operationId: stop-service-database-by-service-and-database-uuid
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
-
|
||||||
|
name: database_uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 'Stop queued.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
message: { type: string }
|
||||||
|
type: object
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
'501':
|
||||||
|
description: 'Swarm not supported.'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
/services:
|
/services:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
|
@ -8550,21 +9042,6 @@ paths:
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
description: 'The service description.'
|
description: 'The service description.'
|
||||||
project_uuid:
|
|
||||||
type: string
|
|
||||||
description: 'The project UUID.'
|
|
||||||
environment_name:
|
|
||||||
type: string
|
|
||||||
description: 'The environment name.'
|
|
||||||
environment_uuid:
|
|
||||||
type: string
|
|
||||||
description: 'The environment UUID.'
|
|
||||||
server_uuid:
|
|
||||||
type: string
|
|
||||||
description: 'The server UUID.'
|
|
||||||
destination_uuid:
|
|
||||||
type: string
|
|
||||||
description: 'The destination UUID.'
|
|
||||||
instant_deploy:
|
instant_deploy:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'The flag to indicate if the service should be deployed instantly.'
|
description: 'The flag to indicate if the service should be deployed instantly.'
|
||||||
|
|
@ -9967,7 +10444,7 @@ components:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
description: 'Password for HTTP Basic Authentication'
|
description: 'Password for HTTP Basic Authentication'
|
||||||
'':
|
settings:
|
||||||
$ref: '#/components/schemas/ApplicationSetting'
|
$ref: '#/components/schemas/ApplicationSetting'
|
||||||
type: object
|
type: object
|
||||||
ApplicationDeploymentQueue:
|
ApplicationDeploymentQueue:
|
||||||
|
|
@ -10700,6 +11177,9 @@ tags:
|
||||||
-
|
-
|
||||||
name: 'Service applications'
|
name: 'Service applications'
|
||||||
description: 'Service applications'
|
description: 'Service applications'
|
||||||
|
-
|
||||||
|
name: 'Service databases'
|
||||||
|
description: 'Service databases'
|
||||||
-
|
-
|
||||||
name: Services
|
name: Services
|
||||||
description: Services
|
description: Services
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
use App\Http\Controllers\Api\SentinelController;
|
use App\Http\Controllers\Api\SentinelController;
|
||||||
use App\Http\Controllers\Api\ServersController;
|
use App\Http\Controllers\Api\ServersController;
|
||||||
use App\Http\Controllers\Api\ServiceApplicationsController;
|
use App\Http\Controllers\Api\ServiceApplicationsController;
|
||||||
|
use App\Http\Controllers\Api\ServiceDatabasesController;
|
||||||
use App\Http\Controllers\Api\ServicesController;
|
use App\Http\Controllers\Api\ServicesController;
|
||||||
use App\Http\Controllers\Api\TagsController;
|
use App\Http\Controllers\Api\TagsController;
|
||||||
use App\Http\Controllers\Api\TeamController;
|
use App\Http\Controllers\Api\TeamController;
|
||||||
|
|
@ -245,6 +246,14 @@
|
||||||
Route::match(['get', 'post'], '/services/{uuid}/applications/{app_uuid}/restart', [ServiceApplicationsController::class, 'action_restart'])->middleware(['api.ability:deploy']);
|
Route::match(['get', 'post'], '/services/{uuid}/applications/{app_uuid}/restart', [ServiceApplicationsController::class, 'action_restart'])->middleware(['api.ability:deploy']);
|
||||||
Route::match(['get', 'post'], '/services/{uuid}/applications/{app_uuid}/stop', [ServiceApplicationsController::class, 'action_stop'])->middleware(['api.ability:deploy']);
|
Route::match(['get', 'post'], '/services/{uuid}/applications/{app_uuid}/stop', [ServiceApplicationsController::class, 'action_stop'])->middleware(['api.ability:deploy']);
|
||||||
|
|
||||||
|
Route::get('/services/{uuid}/databases', [ServiceDatabasesController::class, 'index'])->middleware(['api.ability:read']);
|
||||||
|
Route::get('/services/{uuid}/databases/{database_uuid}', [ServiceDatabasesController::class, 'show'])->middleware(['api.ability:read']);
|
||||||
|
Route::patch('/services/{uuid}/databases/{database_uuid}', [ServiceDatabasesController::class, 'update'])->middleware(['api.ability:write']);
|
||||||
|
Route::get('/services/{uuid}/databases/{database_uuid}/logs', [ServiceDatabasesController::class, 'logs'])->middleware(['api.ability:read']);
|
||||||
|
Route::post('/services/{uuid}/databases/{database_uuid}/start', [ServiceDatabasesController::class, 'start'])->middleware(['api.ability:deploy']);
|
||||||
|
Route::post('/services/{uuid}/databases/{database_uuid}/restart', [ServiceDatabasesController::class, 'restart'])->middleware(['api.ability:deploy']);
|
||||||
|
Route::post('/services/{uuid}/databases/{database_uuid}/stop', [ServiceDatabasesController::class, 'stop'])->middleware(['api.ability:deploy']);
|
||||||
|
|
||||||
Route::get('/applications/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'scheduled_tasks_by_application_uuid'])->middleware(['api.ability:read']);
|
Route::get('/applications/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'scheduled_tasks_by_application_uuid'])->middleware(['api.ability:read']);
|
||||||
Route::post('/applications/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'create_scheduled_task_by_application_uuid'])->middleware(['api.ability:write']);
|
Route::post('/applications/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'create_scheduled_task_by_application_uuid'])->middleware(['api.ability:write']);
|
||||||
Route::patch('/applications/{uuid}/scheduled-tasks/{task_uuid}', [ScheduledTasksController::class, 'update_scheduled_task_by_application_uuid'])->middleware(['api.ability:write']);
|
Route::patch('/applications/{uuid}/scheduled-tasks/{task_uuid}', [ScheduledTasksController::class, 'update_scheduled_task_by_application_uuid'])->middleware(['api.ability:write']);
|
||||||
|
|
|
||||||
165
tests/Feature/ServiceDatabasesApiTest.php
Normal file
165
tests/Feature/ServiceDatabasesApiTest.php
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Actions\Service\DeployServiceApplication;
|
||||||
|
use App\Actions\Service\RestartServiceApplication;
|
||||||
|
use App\Actions\Service\StopServiceApplication;
|
||||||
|
use App\Models\Environment;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneDocker;
|
||||||
|
use App\Models\Team;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Queue;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
Queue::fake();
|
||||||
|
InstanceSettings::forceCreate(['id' => 0, 'is_api_enabled' => true]);
|
||||||
|
|
||||||
|
$this->team = Team::factory()->create();
|
||||||
|
$this->user = User::factory()->create();
|
||||||
|
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||||
|
$this->bearerToken = createBearerTokenForServiceDatabasesApiTest($this->user, $this->team);
|
||||||
|
|
||||||
|
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||||
|
$this->server->settings->update([
|
||||||
|
'is_reachable' => true,
|
||||||
|
'is_usable' => true,
|
||||||
|
'force_disabled' => false,
|
||||||
|
]);
|
||||||
|
$this->destination = StandaloneDocker::where('server_id', $this->server->id)->first();
|
||||||
|
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
|
||||||
|
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
|
||||||
|
});
|
||||||
|
|
||||||
|
function createBearerTokenForServiceDatabasesApiTest(User $user, Team $team, array $abilities = ['*']): string
|
||||||
|
{
|
||||||
|
$plainTextToken = Str::random(40);
|
||||||
|
$token = $user->tokens()->create([
|
||||||
|
'name' => 'test-token',
|
||||||
|
'token' => hash('sha256', $plainTextToken),
|
||||||
|
'abilities' => $abilities,
|
||||||
|
'team_id' => $team->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $token->getKey().'|'.$plainTextToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createServiceWithDatabaseForApiTest(object $context): object
|
||||||
|
{
|
||||||
|
$service = Service::factory()->create([
|
||||||
|
'environment_id' => $context->environment->id,
|
||||||
|
'server_id' => $context->server->id,
|
||||||
|
'destination_id' => $context->destination->id,
|
||||||
|
'destination_type' => $context->destination->getMorphClass(),
|
||||||
|
'docker_compose_raw' => "services:\n postgres:\n image: postgres:17-alpine\n",
|
||||||
|
]);
|
||||||
|
|
||||||
|
$serviceDatabase = ServiceDatabase::create([
|
||||||
|
'uuid' => (string) Str::uuid(),
|
||||||
|
'name' => 'postgres',
|
||||||
|
'service_id' => $service->id,
|
||||||
|
'image' => 'postgres:17-alpine',
|
||||||
|
'is_public' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (object) ['service' => $service, 'serviceDatabase' => $serviceDatabase];
|
||||||
|
}
|
||||||
|
|
||||||
|
test('lists and gets databases belonging to a service', function () {
|
||||||
|
$context = createServiceWithDatabaseForApiTest($this);
|
||||||
|
$headers = ['Authorization' => 'Bearer '.$this->bearerToken];
|
||||||
|
|
||||||
|
$this->withHeaders($headers)
|
||||||
|
->getJson("/api/v1/services/{$context->service->uuid}/databases")
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonFragment(['uuid' => $context->serviceDatabase->uuid]);
|
||||||
|
|
||||||
|
$this->withHeaders($headers)
|
||||||
|
->getJson("/api/v1/services/{$context->service->uuid}/databases/{$context->serviceDatabase->uuid}")
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonFragment(['name' => 'postgres']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not return a database from another service', function () {
|
||||||
|
$context = createServiceWithDatabaseForApiTest($this);
|
||||||
|
$otherContext = createServiceWithDatabaseForApiTest($this);
|
||||||
|
|
||||||
|
$this->withHeaders(['Authorization' => 'Bearer '.$this->bearerToken])
|
||||||
|
->getJson("/api/v1/services/{$context->service->uuid}/databases/{$otherContext->serviceDatabase->uuid}")
|
||||||
|
->assertNotFound()
|
||||||
|
->assertJsonFragment(['message' => 'Service database not found.']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updates supported service database fields and rejects unknown fields', function () {
|
||||||
|
$context = createServiceWithDatabaseForApiTest($this);
|
||||||
|
$url = "/api/v1/services/{$context->service->uuid}/databases/{$context->serviceDatabase->uuid}";
|
||||||
|
$headers = ['Authorization' => 'Bearer '.$this->bearerToken];
|
||||||
|
|
||||||
|
$this->withHeaders($headers)->patchJson($url, [
|
||||||
|
'human_name' => 'Primary database',
|
||||||
|
'description' => 'Stores production data',
|
||||||
|
'public_port' => 5432,
|
||||||
|
'public_port_timeout' => 1800,
|
||||||
|
'exclude_from_status' => true,
|
||||||
|
])->assertOk()->assertJsonFragment([
|
||||||
|
'human_name' => 'Primary database',
|
||||||
|
'public_port' => 5432,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$context->serviceDatabase->refresh();
|
||||||
|
expect($context->serviceDatabase->human_name)->toBe('Primary database')
|
||||||
|
->and($context->serviceDatabase->public_port_timeout)->toBe(1800)
|
||||||
|
->and($context->serviceDatabase->exclude_from_status)->toBeTrue();
|
||||||
|
|
||||||
|
$this->withHeaders($headers)
|
||||||
|
->patchJson($url, ['unsupported' => true])
|
||||||
|
->assertUnprocessable()
|
||||||
|
->assertJsonPath('errors.unsupported.0', 'This field is not allowed.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('queues lifecycle actions for a service database', function () {
|
||||||
|
$context = createServiceWithDatabaseForApiTest($this);
|
||||||
|
$baseUrl = "/api/v1/services/{$context->service->uuid}/databases/{$context->serviceDatabase->uuid}";
|
||||||
|
$headers = ['Authorization' => 'Bearer '.$this->bearerToken];
|
||||||
|
|
||||||
|
$this->withHeaders($headers)->postJson("{$baseUrl}/start?latest=1&force=1")
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonFragment(['message' => 'Service database deploy request queued.']);
|
||||||
|
DeployServiceApplication::assertPushed();
|
||||||
|
|
||||||
|
$this->withHeaders($headers)->postJson("{$baseUrl}/restart")
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonFragment(['message' => 'Service database restart request queued.']);
|
||||||
|
RestartServiceApplication::assertPushed();
|
||||||
|
|
||||||
|
$this->withHeaders($headers)->postJson("{$baseUrl}/stop")
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonFragment(['message' => 'Service database stop request queued.']);
|
||||||
|
StopServiceApplication::assertPushed();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('requires deploy ability for service database lifecycle actions', function () {
|
||||||
|
$context = createServiceWithDatabaseForApiTest($this);
|
||||||
|
$writeToken = createBearerTokenForServiceDatabasesApiTest($this->user, $this->team, ['write']);
|
||||||
|
|
||||||
|
$this->withHeaders(['Authorization' => 'Bearer '.$writeToken])
|
||||||
|
->postJson("/api/v1/services/{$context->service->uuid}/databases/{$context->serviceDatabase->uuid}/restart")
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns a clear error when logs cannot be read from the server', function () {
|
||||||
|
$context = createServiceWithDatabaseForApiTest($this);
|
||||||
|
$this->server->settings->update(['is_reachable' => false]);
|
||||||
|
|
||||||
|
$this->withHeaders(['Authorization' => 'Bearer '.$this->bearerToken])
|
||||||
|
->getJson("/api/v1/services/{$context->service->uuid}/databases/{$context->serviceDatabase->uuid}/logs")
|
||||||
|
->assertBadRequest()
|
||||||
|
->assertJsonFragment(['message' => 'Server is not functional.']);
|
||||||
|
});
|
||||||
27
tests/Unit/ApplicationOpenApiTest.php
Normal file
27
tests/Unit/ApplicationOpenApiTest.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use OpenApi\Attributes\Property;
|
||||||
|
use OpenApi\Attributes\Schema;
|
||||||
|
|
||||||
|
it('documents application settings under the settings property', function () {
|
||||||
|
$schema = (new ReflectionClass(Application::class))
|
||||||
|
->getAttributes(Schema::class)[0]
|
||||||
|
->newInstance();
|
||||||
|
|
||||||
|
$openApi = json_decode(
|
||||||
|
file_get_contents(__DIR__.'/../../openapi.json'),
|
||||||
|
true,
|
||||||
|
flags: JSON_THROW_ON_ERROR,
|
||||||
|
);
|
||||||
|
|
||||||
|
$settingsProperty = collect($schema->properties)
|
||||||
|
->first(fn (mixed $property): bool => $property instanceof Property
|
||||||
|
&& $property->property === 'settings');
|
||||||
|
|
||||||
|
expect($settingsProperty)
|
||||||
|
->not->toBeNull()
|
||||||
|
->and($openApi['components']['schemas']['Application']['properties'])
|
||||||
|
->toHaveKey('settings')
|
||||||
|
->not->toHaveKey('');
|
||||||
|
});
|
||||||
32
tests/Unit/ServiceApplicationsOpenApiTest.php
Normal file
32
tests/Unit/ServiceApplicationsOpenApiTest.php
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
it('documents both supported methods for service application actions', function () {
|
||||||
|
$openApi = json_decode(
|
||||||
|
file_get_contents(__DIR__.'/../../openapi.json'),
|
||||||
|
true,
|
||||||
|
flags: JSON_THROW_ON_ERROR,
|
||||||
|
);
|
||||||
|
|
||||||
|
$actionPaths = [
|
||||||
|
'/services/{uuid}/applications/{app_uuid}/logs',
|
||||||
|
'/services/{uuid}/applications/{app_uuid}/start',
|
||||||
|
'/services/{uuid}/applications/{app_uuid}/restart',
|
||||||
|
'/services/{uuid}/applications/{app_uuid}/stop',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($actionPaths as $path) {
|
||||||
|
expect($openApi['paths'][$path])
|
||||||
|
->toHaveKeys(['get', 'post'])
|
||||||
|
->and($openApi['paths'][$path]['post']['responses'])
|
||||||
|
->toHaveKeys(['200', '400', '401', '404', '501']);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect($openApi['paths'][$actionPaths[0]]['post']['responses']['200']['content']['application/json']['schema']['properties'])
|
||||||
|
->toHaveKey('logs')
|
||||||
|
->and($openApi['paths'][$actionPaths[1]]['post']['responses']['200']['content']['application/json']['schema']['properties'])
|
||||||
|
->toHaveKey('message')
|
||||||
|
->and($openApi['paths'][$actionPaths[2]]['post']['responses']['200']['content']['application/json']['schema']['properties'])
|
||||||
|
->toHaveKey('message')
|
||||||
|
->and($openApi['paths'][$actionPaths[3]]['post']['responses']['200']['content']['application/json']['schema']['properties'])
|
||||||
|
->toHaveKey('message');
|
||||||
|
});
|
||||||
29
tests/Unit/ServiceDatabasesOpenApiTest.php
Normal file
29
tests/Unit/ServiceDatabasesOpenApiTest.php
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
it('documents service database management endpoints', function () {
|
||||||
|
$openApi = json_decode(
|
||||||
|
file_get_contents(__DIR__.'/../../openapi.json'),
|
||||||
|
true,
|
||||||
|
flags: JSON_THROW_ON_ERROR,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect($openApi['paths'])
|
||||||
|
->toHaveKey('/services/{uuid}/databases')
|
||||||
|
->toHaveKey('/services/{uuid}/databases/{database_uuid}')
|
||||||
|
->toHaveKey('/services/{uuid}/databases/{database_uuid}/logs')
|
||||||
|
->toHaveKey('/services/{uuid}/databases/{database_uuid}/start')
|
||||||
|
->toHaveKey('/services/{uuid}/databases/{database_uuid}/restart')
|
||||||
|
->toHaveKey('/services/{uuid}/databases/{database_uuid}/stop')
|
||||||
|
->and($openApi['paths']['/services/{uuid}/databases']['get'])
|
||||||
|
->toBeArray()
|
||||||
|
->and($openApi['paths']['/services/{uuid}/databases/{database_uuid}'])
|
||||||
|
->toHaveKeys(['get', 'patch'])
|
||||||
|
->and($openApi['paths']['/services/{uuid}/databases/{database_uuid}/logs']['get'])
|
||||||
|
->toBeArray()
|
||||||
|
->and($openApi['paths']['/services/{uuid}/databases/{database_uuid}/start']['post'])
|
||||||
|
->toBeArray()
|
||||||
|
->and($openApi['paths']['/services/{uuid}/databases/{database_uuid}/restart']['post'])
|
||||||
|
->toBeArray()
|
||||||
|
->and($openApi['paths']['/services/{uuid}/databases/{database_uuid}/stop']['post'])
|
||||||
|
->toBeArray();
|
||||||
|
});
|
||||||
28
tests/Unit/ServicesOpenApiTest.php
Normal file
28
tests/Unit/ServicesOpenApiTest.php
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\Api\ServicesController;
|
||||||
|
use OpenApi\Attributes\Patch;
|
||||||
|
|
||||||
|
it('only documents fields supported by the update service endpoint', function () {
|
||||||
|
$method = new ReflectionMethod(ServicesController::class, 'update_by_uuid');
|
||||||
|
$patch = $method->getAttributes(Patch::class)[0]->newInstance();
|
||||||
|
$documentedProperties = $patch->requestBody->content[0]->schema->properties;
|
||||||
|
|
||||||
|
$openApi = json_decode(
|
||||||
|
file_get_contents(__DIR__.'/../../openapi.json'),
|
||||||
|
true,
|
||||||
|
flags: JSON_THROW_ON_ERROR,
|
||||||
|
);
|
||||||
|
$generatedProperties = $openApi['paths']['/services/{uuid}']['patch']['requestBody']['content']['application/json']['schema']['properties'];
|
||||||
|
|
||||||
|
$unsupportedFields = [
|
||||||
|
'project_uuid',
|
||||||
|
'environment_name',
|
||||||
|
'environment_uuid',
|
||||||
|
'server_uuid',
|
||||||
|
'destination_uuid',
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(array_keys($generatedProperties))->toBe(array_keys($documentedProperties))
|
||||||
|
->and(array_intersect($unsupportedFields, array_keys($documentedProperties)))->toBeEmpty();
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue