mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
@@ -3258,11 +3258,12 @@ class ApplicationsController extends Controller
|
||||
$dnsCleanup = app(ManagedDnsRecordCleanup::class);
|
||||
$previousDnsHostnames = $dnsCleanup->hostnamesOf($application);
|
||||
$server = $application->destination->server;
|
||||
$allowedFields = ['name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', 'is_preview_deployments_enabled', 'domains', 'noindex_domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'custom_network_aliases', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_type', 'health_check_command', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'dockerfile_location', 'dockerfile_target_build', 'docker_compose_location', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'use_build_secrets', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override', 'is_container_label_escape_enabled', 'is_preserve_repository_enabled', 'preview_url_template', 'max_restart_count', 'github_app_uuid', ...self::APPLICATION_SETTING_FIELDS];
|
||||
$allowedFields = ['name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', 'is_preview_deployments_enabled', 'domains', 'noindex_domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'custom_network_aliases', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_type', 'health_check_command', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'dockerfile_location', 'dockerfile_target_build', 'docker_compose_location', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'use_build_secrets', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override', 'is_container_label_escape_enabled', 'is_preserve_repository_enabled', 'preview_url_template', 'max_restart_count', 'github_app_uuid', 'dockerfile', ...self::APPLICATION_SETTING_FIELDS];
|
||||
|
||||
$validationRules = [
|
||||
'name' => 'string|max:255',
|
||||
'github_app_uuid' => 'string',
|
||||
'dockerfile' => 'string',
|
||||
'description' => 'string|nullable',
|
||||
'watch_paths' => 'string|nullable',
|
||||
'docker_compose_domains' => 'array|nullable',
|
||||
@@ -3320,6 +3321,28 @@ class ApplicationsController extends Controller
|
||||
'custom_nginx_configuration' => $customNginxConfiguration,
|
||||
]);
|
||||
}
|
||||
if ($request->has('dockerfile')) {
|
||||
if (blank($application->dockerfile)) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'dockerfile' => 'The dockerfile can only be updated for applications created from a Dockerfile.',
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
$dockerfile = is_string($request->dockerfile) && isBase64Encoded($request->dockerfile) ? base64_decode($request->dockerfile) : false;
|
||||
if ($dockerfile === false || blank($dockerfile) || mb_detect_encoding($dockerfile, 'UTF-8', true) === false) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'dockerfile' => 'The dockerfile should be base64 encoded.',
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
$request->merge([
|
||||
'dockerfile' => $dockerfile,
|
||||
]);
|
||||
}
|
||||
$return = $this->validateDataApplications($request, $server);
|
||||
if ($return instanceof JsonResponse) {
|
||||
return $return;
|
||||
@@ -3341,7 +3364,7 @@ class ApplicationsController extends Controller
|
||||
|
||||
$githubApp = null;
|
||||
if ($request->has('github_app_uuid')) {
|
||||
if ($application->build_pack === 'dockerimage' || $application->deploymentType() === 'deploy_key') {
|
||||
if ($application->build_pack === 'dockerimage' || filled($application->dockerfile) || $application->deploymentType() === 'deploy_key') {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
@@ -3624,6 +3647,9 @@ class ApplicationsController extends Controller
|
||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||
}
|
||||
$application->withoutAuditLogging(fn () => $application->save());
|
||||
if ($application->wasChanged('dockerfile')) {
|
||||
$application->parseHealthcheckFromDockerfile($application->dockerfile);
|
||||
}
|
||||
$dnsCleanup->queueReleaseOfRemovedHostnames($application, $previousDnsHostnames, (int) $teamId);
|
||||
|
||||
auditLog('api.application.updated', [
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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;
|
||||
|
||||
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]);
|
||||
$this->token = $this->user->createToken('dockerfile-update-test', ['*'])->plainTextToken;
|
||||
|
||||
$server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||
$destination = StandaloneDocker::query()->where('server_id', $server->id)->firstOrFail();
|
||||
$project = Project::factory()->create(['team_id' => $this->team->id]);
|
||||
$environment = Environment::factory()->create(['project_id' => $project->id]);
|
||||
|
||||
$this->application = Application::factory()->create([
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
'build_pack' => 'dockerfile',
|
||||
'dockerfile' => "FROM nginx:alpine\n",
|
||||
'custom_healthcheck_found' => true,
|
||||
'health_check_interval' => 30,
|
||||
]);
|
||||
});
|
||||
|
||||
function patchApplicationDockerfile(array $payload)
|
||||
{
|
||||
return test()->withHeaders(['Authorization' => 'Bearer '.test()->token])
|
||||
->patchJson('/api/v1/applications/'.test()->application->uuid, $payload);
|
||||
}
|
||||
|
||||
test('PATCH updates the Dockerfile of a Dockerfile application', function () {
|
||||
$dockerfile = "FROM nginx:1.27-alpine\n";
|
||||
|
||||
patchApplicationDockerfile(['dockerfile' => base64_encode($dockerfile)])->assertOk();
|
||||
|
||||
$application = $this->application->refresh();
|
||||
expect($application->dockerfile)->toBe($dockerfile)
|
||||
->and($application->custom_healthcheck_found)->toBeFalsy()
|
||||
->and($application->health_check_interval)->toBe(5);
|
||||
});
|
||||
|
||||
test('PATCH rejects a Dockerfile that is not base64 encoded', function () {
|
||||
patchApplicationDockerfile(['dockerfile' => "FROM nginx:1.27-alpine\n"])
|
||||
->assertUnprocessable()
|
||||
->assertJsonPath('errors.dockerfile', 'The dockerfile should be base64 encoded.');
|
||||
|
||||
expect($this->application->refresh()->dockerfile)->toBe("FROM nginx:alpine\n");
|
||||
});
|
||||
|
||||
test('PATCH rejects a Dockerfile for applications built from a git repository', function () {
|
||||
$this->application->forceFill(['dockerfile' => null])->saveQuietly();
|
||||
|
||||
patchApplicationDockerfile(['dockerfile' => base64_encode("FROM nginx:1.27-alpine\n")])
|
||||
->assertUnprocessable()
|
||||
->assertInvalid(['dockerfile']);
|
||||
|
||||
expect($this->application->refresh()->dockerfile)->toBeNull();
|
||||
});
|
||||
@@ -101,3 +101,16 @@ test('PATCH rejects github_app_uuid for deploy key applications', function () {
|
||||
|
||||
expect($this->application->refresh()->source_id)->toBe($this->oldGithubApp->id);
|
||||
});
|
||||
|
||||
test('PATCH rejects github_app_uuid for applications without a git repository', function (array $attributes) {
|
||||
$this->application->forceFill($attributes)->saveQuietly();
|
||||
|
||||
patchApplicationSource(['github_app_uuid' => $this->newGithubApp->uuid])
|
||||
->assertUnprocessable()
|
||||
->assertInvalid(['github_app_uuid']);
|
||||
|
||||
expect($this->application->refresh()->source_id)->toBe($this->oldGithubApp->id);
|
||||
})->with([
|
||||
'inline dockerfile' => [['build_pack' => 'dockerfile', 'dockerfile' => "FROM nginx:alpine\n"]],
|
||||
'docker image' => [['build_pack' => 'dockerimage']],
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user