fix(applications): store custom nginx config from API correctly
Decode base64 custom_nginx_configuration before model assignment so it is not double-encoded, and allow null values when clearing the setting. Add API coverage for create, update, invalid input, and clearing behavior.
This commit is contained in:
parent
27b44fce4d
commit
a42613168d
5 changed files with 187 additions and 35 deletions
|
|
@ -979,6 +979,9 @@ private function create_application(Request $request, $type)
|
|||
],
|
||||
], 422);
|
||||
}
|
||||
$request->merge([
|
||||
'custom_nginx_configuration' => $customNginxConfiguration,
|
||||
]);
|
||||
}
|
||||
|
||||
$project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
|
||||
|
|
@ -2397,7 +2400,7 @@ public function update_by_uuid(Request $request)
|
|||
}
|
||||
}
|
||||
}
|
||||
if ($request->has('custom_nginx_configuration')) {
|
||||
if ($request->has('custom_nginx_configuration') && ! is_null($request->custom_nginx_configuration)) {
|
||||
if (! isBase64Encoded($request->custom_nginx_configuration)) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
|
|
@ -2415,6 +2418,9 @@ public function update_by_uuid(Request $request)
|
|||
],
|
||||
], 422);
|
||||
}
|
||||
$request->merge([
|
||||
'custom_nginx_configuration' => $customNginxConfiguration,
|
||||
]);
|
||||
}
|
||||
$return = $this->validateDataApplications($request, $server);
|
||||
if ($return instanceof JsonResponse) {
|
||||
|
|
|
|||
|
|
@ -886,8 +886,8 @@ public function status(): Attribute
|
|||
public function customNginxConfiguration(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => base64_encode($value),
|
||||
get: fn ($value) => base64_decode($value),
|
||||
set: fn ($value) => is_null($value) ? null : base64_encode($value),
|
||||
get: fn ($value) => is_null($value) ? null : base64_decode($value),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1634,6 +1634,20 @@
|
|||
"minversion": "0.0.0",
|
||||
"port": "2368"
|
||||
},
|
||||
"gitea-runner": {
|
||||
"documentation": "https://github.com/go-gitea/gitea?utm_source=coolify.io",
|
||||
"slogan": "Gitea Actions runner for docker",
|
||||
"compose": "c2VydmljZXM6CiAgcnVubmVyOgogICAgaW1hZ2U6ICdkb2NrZXIuaW8vZ2l0ZWEvcnVubmVyOjEuMC4wJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0dJVEVBX0lOU1RBTkNFX1VSTD0ke0dJVEVBX0lOU1RBTkNFX1VSTH0nCiAgICAgIC0gJ0dJVEVBX1JVTk5FUl9SRUdJU1RSQVRJT05fVE9LRU49JHtHSVRFQV9SVU5ORVJfUkVHSVNUUkFUSU9OX1RPS0VOfScKICAgICAgLSAnR0lURUFfUlVOTkVSX05BTUU9JHtHSVRFQV9SVU5ORVJfTkFNRTotZ2l0ZWEtcnVubmVyfScKICAgICAgLSAnR0lURUFfUlVOTkVSX0xBQkVMUz0ke0dJVEVBX1JVTk5FUl9MQUJFTFM6LXVidW50dS1sYXRlc3Q6ZG9ja2VyOi8vbm9kZToyMn0nCiAgICAgIC0gJ0dJVEVBX1RPS0VOPSR7R0lURUFfVE9LRU59JwogICAgd29ya2luZ19kaXI6IC9kYXRhCiAgICB2b2x1bWVzOgogICAgICAtICdydW5uZXItZGF0YTovZGF0YScKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gInBzIGF1eCB8IGdyZXAgJ1tSXXVubmVyJyA+IC9kZXYvbnVsbCB8fCBleGl0IDEiCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK",
|
||||
"tags": [
|
||||
"gitea",
|
||||
"actions",
|
||||
"runner",
|
||||
"docker"
|
||||
],
|
||||
"category": "devtools",
|
||||
"logo": "svgs/gitea.svg",
|
||||
"minversion": "0.0.0"
|
||||
},
|
||||
"gitea-with-mariadb": {
|
||||
"documentation": "https://docs.gitea.com?utm_source=coolify.io",
|
||||
"slogan": "Gitea is a self-hosted, lightweight Git service, offering version control, collaboration, and code hosting.",
|
||||
|
|
@ -2587,22 +2601,6 @@
|
|||
"minversion": "0.0.0",
|
||||
"port": "4000"
|
||||
},
|
||||
"litequeen": {
|
||||
"documentation": "https://litequeen.com/?utm_source=coolify.io",
|
||||
"slogan": "Lite Queen is an open-source SQLite database management software that runs on your server.",
|
||||
"compose": "c2VydmljZXM6CiAgbGl0ZXF1ZWVuOgogICAgaW1hZ2U6ICdraXZzZWdyb2IvbGl0ZS1xdWVlbjpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9MSVRFUVVFRU5fODAwMAogICAgdm9sdW1lczoKICAgICAgLSAnbGl0ZXF1ZWVuLWRhdGE6L2hvbWUvbGl0ZXF1ZWVuL2RhdGEnCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2RhdGFiYXNlcwogICAgICAgIHRhcmdldDogL3NydgogICAgICAgIGlzX2RpcmVjdG9yeTogdHJ1ZQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICJiYXNoIC1jICc6PiAvZGV2L3RjcC8xMjcuMC4wLjEvODAwMCcgfHwgZXhpdCAxIgogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMwo=",
|
||||
"tags": [
|
||||
"sqlite",
|
||||
"sqlite-database-management",
|
||||
"self-hosted",
|
||||
"vps",
|
||||
"database"
|
||||
],
|
||||
"category": "database",
|
||||
"logo": "svgs/litequeen.svg",
|
||||
"minversion": "0.0.0",
|
||||
"port": "8000"
|
||||
},
|
||||
"lobe-chat": {
|
||||
"documentation": "https://github.com/lobehub/lobe-chat?tab=readme-ov-file#b-deploying-with-docker?utm_source=coolify.io",
|
||||
"slogan": "An open-source, modern-design AI chat framework.",
|
||||
|
|
|
|||
|
|
@ -1634,6 +1634,20 @@
|
|||
"minversion": "0.0.0",
|
||||
"port": "2368"
|
||||
},
|
||||
"gitea-runner": {
|
||||
"documentation": "https://github.com/go-gitea/gitea?utm_source=coolify.io",
|
||||
"slogan": "Gitea Actions runner for docker",
|
||||
"compose": "c2VydmljZXM6CiAgcnVubmVyOgogICAgaW1hZ2U6ICdkb2NrZXIuaW8vZ2l0ZWEvcnVubmVyOjEuMC4wJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0dJVEVBX0lOU1RBTkNFX1VSTD0ke0dJVEVBX0lOU1RBTkNFX1VSTH0nCiAgICAgIC0gJ0dJVEVBX1JVTk5FUl9SRUdJU1RSQVRJT05fVE9LRU49JHtHSVRFQV9SVU5ORVJfUkVHSVNUUkFUSU9OX1RPS0VOfScKICAgICAgLSAnR0lURUFfUlVOTkVSX05BTUU9JHtHSVRFQV9SVU5ORVJfTkFNRTotZ2l0ZWEtcnVubmVyfScKICAgICAgLSAnR0lURUFfUlVOTkVSX0xBQkVMUz0ke0dJVEVBX1JVTk5FUl9MQUJFTFM6LXVidW50dS1sYXRlc3Q6ZG9ja2VyOi8vbm9kZToyMn0nCiAgICAgIC0gJ0dJVEVBX1RPS0VOPSR7R0lURUFfVE9LRU59JwogICAgd29ya2luZ19kaXI6IC9kYXRhCiAgICB2b2x1bWVzOgogICAgICAtICdydW5uZXItZGF0YTovZGF0YScKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gInBzIGF1eCB8IGdyZXAgJ1tSXXVubmVyJyA+IC9kZXYvbnVsbCB8fCBleGl0IDEiCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK",
|
||||
"tags": [
|
||||
"gitea",
|
||||
"actions",
|
||||
"runner",
|
||||
"docker"
|
||||
],
|
||||
"category": "devtools",
|
||||
"logo": "svgs/gitea.svg",
|
||||
"minversion": "0.0.0"
|
||||
},
|
||||
"gitea-with-mariadb": {
|
||||
"documentation": "https://docs.gitea.com?utm_source=coolify.io",
|
||||
"slogan": "Gitea is a self-hosted, lightweight Git service, offering version control, collaboration, and code hosting.",
|
||||
|
|
@ -2587,22 +2601,6 @@
|
|||
"minversion": "0.0.0",
|
||||
"port": "4000"
|
||||
},
|
||||
"litequeen": {
|
||||
"documentation": "https://litequeen.com/?utm_source=coolify.io",
|
||||
"slogan": "Lite Queen is an open-source SQLite database management software that runs on your server.",
|
||||
"compose": "c2VydmljZXM6CiAgbGl0ZXF1ZWVuOgogICAgaW1hZ2U6ICdraXZzZWdyb2IvbGl0ZS1xdWVlbjpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTElURVFVRUVOXzgwMDAKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2xpdGVxdWVlbi1kYXRhOi9ob21lL2xpdGVxdWVlbi9kYXRhJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9kYXRhYmFzZXMKICAgICAgICB0YXJnZXQ6IC9zcnYKICAgICAgICBpc19kaXJlY3Rvcnk6IHRydWUKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAiYmFzaCAtYyAnOj4gL2Rldi90Y3AvMTI3LjAuMC4xLzgwMDAnIHx8IGV4aXQgMSIKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDMK",
|
||||
"tags": [
|
||||
"sqlite",
|
||||
"sqlite-database-management",
|
||||
"self-hosted",
|
||||
"vps",
|
||||
"database"
|
||||
],
|
||||
"category": "database",
|
||||
"logo": "svgs/litequeen.svg",
|
||||
"minversion": "0.0.0",
|
||||
"port": "8000"
|
||||
},
|
||||
"lobe-chat": {
|
||||
"documentation": "https://github.com/lobehub/lobe-chat?tab=readme-ov-file#b-deploying-with-docker?utm_source=coolify.io",
|
||||
"slogan": "An open-source, modern-design AI chat framework.",
|
||||
|
|
|
|||
150
tests/Feature/ApplicationCustomNginxConfigurationApiTest.php
Normal file
150
tests/Feature/ApplicationCustomNginxConfigurationApiTest.php
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Environment;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::unguarded(fn () => InstanceSettings::firstOrCreate(['id' => 0]));
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
$this->user = User::factory()->create();
|
||||
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$plainTextToken = Str::random(40);
|
||||
$token = $this->user->tokens()->create([
|
||||
'name' => 'custom-nginx-api-test-'.Str::random(6),
|
||||
'token' => hash('sha256', $plainTextToken),
|
||||
'abilities' => ['*'],
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
$this->bearerToken = $token->getKey().'|'.$plainTextToken;
|
||||
|
||||
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||
$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 customNginxApiHeaders(string $bearerToken): array
|
||||
{
|
||||
return [
|
||||
'Authorization' => 'Bearer '.$bearerToken,
|
||||
'Content-Type' => 'application/json',
|
||||
];
|
||||
}
|
||||
|
||||
function customNginxConfig(): string
|
||||
{
|
||||
return <<<'NGINX'
|
||||
server {
|
||||
listen 80;
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
NGINX;
|
||||
}
|
||||
|
||||
function makeCustomNginxApplication(array $overrides = []): Application
|
||||
{
|
||||
return Application::factory()->create(array_merge([
|
||||
'environment_id' => test()->environment->id,
|
||||
'destination_id' => test()->destination->id,
|
||||
'destination_type' => test()->destination->getMorphClass(),
|
||||
'build_pack' => 'static',
|
||||
], $overrides));
|
||||
}
|
||||
|
||||
describe('PATCH /api/v1/applications/{uuid} custom_nginx_configuration', function () {
|
||||
test('decodes base64 custom nginx configuration before storing it', function () {
|
||||
$application = makeCustomNginxApplication();
|
||||
$configuration = customNginxConfig();
|
||||
$encodedConfiguration = base64_encode($configuration);
|
||||
|
||||
$response = $this->withHeaders(customNginxApiHeaders($this->bearerToken))
|
||||
->patchJson("/api/v1/applications/{$application->uuid}", [
|
||||
'custom_nginx_configuration' => $encodedConfiguration,
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
|
||||
$application->refresh();
|
||||
expect($application->custom_nginx_configuration)->toBe($configuration);
|
||||
|
||||
$storedConfiguration = DB::table('applications')
|
||||
->where('id', $application->id)
|
||||
->value('custom_nginx_configuration');
|
||||
|
||||
expect($storedConfiguration)->toBe(base64_encode($configuration));
|
||||
|
||||
$this->withHeaders(customNginxApiHeaders($this->bearerToken))
|
||||
->getJson("/api/v1/applications/{$application->uuid}")
|
||||
->assertOk()
|
||||
->assertJsonPath('custom_nginx_configuration', $configuration);
|
||||
});
|
||||
|
||||
test('rejects custom nginx configuration that is not base64 encoded', function () {
|
||||
$application = makeCustomNginxApplication();
|
||||
|
||||
$response = $this->withHeaders(customNginxApiHeaders($this->bearerToken))
|
||||
->patchJson("/api/v1/applications/{$application->uuid}", [
|
||||
'custom_nginx_configuration' => customNginxConfig(),
|
||||
]);
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonPath('errors.custom_nginx_configuration', 'The custom_nginx_configuration should be base64 encoded.');
|
||||
});
|
||||
|
||||
test('can clear custom nginx configuration with null', function () {
|
||||
$application = makeCustomNginxApplication([
|
||||
'custom_nginx_configuration' => customNginxConfig(),
|
||||
]);
|
||||
|
||||
$response = $this->withHeaders(customNginxApiHeaders($this->bearerToken))
|
||||
->patchJson("/api/v1/applications/{$application->uuid}", [
|
||||
'custom_nginx_configuration' => null,
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
|
||||
$application->refresh();
|
||||
expect($application->custom_nginx_configuration)->toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /api/v1/applications/public custom_nginx_configuration', function () {
|
||||
test('decodes base64 custom nginx configuration before storing it on create', function () {
|
||||
$configuration = customNginxConfig();
|
||||
|
||||
$response = $this->withHeaders(customNginxApiHeaders($this->bearerToken))
|
||||
->postJson('/api/v1/applications/public', [
|
||||
'project_uuid' => $this->project->uuid,
|
||||
'environment_uuid' => $this->environment->uuid,
|
||||
'server_uuid' => $this->server->uuid,
|
||||
'git_repository' => 'https://gitlab.com/coolify/test-static-app',
|
||||
'git_branch' => 'main',
|
||||
'build_pack' => 'static',
|
||||
'ports_exposes' => '80',
|
||||
'custom_nginx_configuration' => base64_encode($configuration),
|
||||
'autogenerate_domain' => false,
|
||||
]);
|
||||
|
||||
$response->assertCreated();
|
||||
|
||||
$application = Application::where('uuid', $response->json('uuid'))->firstOrFail();
|
||||
|
||||
expect($application->custom_nginx_configuration)->toBe($configuration);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue