mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(runners): limit stored workflow and job names to the column length
This commit is contained in:
@@ -260,6 +260,14 @@ class Github extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cuts a webhook string to the varchar(255) column of the runner tables.
|
||||
*/
|
||||
private function limitRunnerColumn(mixed $value): ?string
|
||||
{
|
||||
return is_scalar($value) ? mb_substr((string) $value, 0, 255) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles GitHub Actions runner demand. Every lookup is scoped to the App whose signature was verified.
|
||||
* GitHub can give a job to any idle runner with matching labels, so progress is matched by runner name.
|
||||
@@ -278,12 +286,14 @@ class Github extends Controller
|
||||
return response('Nothing to do. The job belongs to another installation of the GitHub App.');
|
||||
}
|
||||
|
||||
// The columns are varchar(255). A cut URL would be a broken link, so a longer one is not stored.
|
||||
$htmlUrl = data_get($payload, 'workflow_job.html_url');
|
||||
$jobDetails = array_filter([
|
||||
'workflow_job_id' => $jobId,
|
||||
'workflow_job_html_url' => data_get($payload, 'workflow_job.html_url'),
|
||||
'workflow_name' => data_get($payload, 'workflow_job.workflow_name'),
|
||||
'job_name' => data_get($payload, 'workflow_job.name'),
|
||||
'repository_full_name' => data_get($payload, 'repository.full_name'),
|
||||
'workflow_job_html_url' => is_string($htmlUrl) && mb_strlen($htmlUrl) <= 255 ? $htmlUrl : null,
|
||||
'workflow_name' => $this->limitRunnerColumn(data_get($payload, 'workflow_job.workflow_name')),
|
||||
'job_name' => $this->limitRunnerColumn(data_get($payload, 'workflow_job.name')),
|
||||
'repository_full_name' => $this->limitRunnerColumn(data_get($payload, 'repository.full_name')),
|
||||
], fn ($value) => filled($value));
|
||||
|
||||
if ($action === 'queued') {
|
||||
@@ -329,7 +339,7 @@ class Github extends Controller
|
||||
|
||||
if ($action === 'completed') {
|
||||
if ($execution) {
|
||||
$execution->update([...$jobDetails, 'conclusion' => data_get($payload, 'workflow_job.conclusion')]);
|
||||
$execution->update([...$jobDetails, 'conclusion' => $this->limitRunnerColumn(data_get($payload, 'workflow_job.conclusion'))]);
|
||||
if ($execution->isActive() || $execution->status === GithubRunnerStatus::Failed) {
|
||||
$execution->finish(GithubRunnerStatus::Completed);
|
||||
CleanupGithubRunnerJob::dispatch($execution->id);
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\GithubRunnerStatus;
|
||||
use App\Jobs\ProvisionGithubRunnerJob;
|
||||
use App\Models\GithubApp;
|
||||
use App\Models\GithubRunnerConfig;
|
||||
use App\Models\GithubRunnerExecution;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::forceCreate(['id' => 0]);
|
||||
Server::flushIdentityMap();
|
||||
Queue::fake();
|
||||
|
||||
$team = Team::factory()->create();
|
||||
openssl_pkey_export(openssl_pkey_new(['private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA]), $pemKey);
|
||||
$privateKey = PrivateKey::create(['name' => 'Runner key', 'private_key' => $pemKey, 'team_id' => $team->id]);
|
||||
$this->longNamesApp = GithubApp::create([
|
||||
'name' => 'runner-app',
|
||||
'organization' => 'acme',
|
||||
'api_url' => 'https://api.github.com',
|
||||
'html_url' => 'https://github.com',
|
||||
'custom_user' => 'git',
|
||||
'custom_port' => 22,
|
||||
'app_id' => 2222,
|
||||
'installation_id' => 2223,
|
||||
'webhook_secret' => 'runner-secret',
|
||||
'private_key_id' => $privateKey->id,
|
||||
'team_id' => $team->id,
|
||||
'is_system_wide' => false,
|
||||
]);
|
||||
$server = Server::factory()->create(['team_id' => $team->id]);
|
||||
GithubRunnerConfig::create([
|
||||
'server_id' => $server->id,
|
||||
'github_app_id' => $this->longNamesApp->id,
|
||||
'labels' => ['coolify'],
|
||||
'max_runners' => 2,
|
||||
'docker_mode' => 'dind',
|
||||
'allow_pull_requests' => true,
|
||||
]);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Server::flushIdentityMap();
|
||||
});
|
||||
|
||||
function sendLongNamesWorkflowJobWebhook($test, GithubApp $githubApp, string $action, array $job)
|
||||
{
|
||||
$body = json_encode([
|
||||
'action' => $action,
|
||||
'workflow_job' => [
|
||||
'id' => 5001,
|
||||
'labels' => ['self-hosted', 'coolify'],
|
||||
'html_url' => 'https://github.com/acme/api/actions/runs/1/job/5001',
|
||||
'workflow_name' => str_repeat('w', 300),
|
||||
'name' => str_repeat('ö', 300),
|
||||
'runner_name' => null,
|
||||
...$job,
|
||||
],
|
||||
'repository' => ['id' => 99, 'full_name' => 'acme/'.str_repeat('r', 300)],
|
||||
'organization' => ['login' => 'acme'],
|
||||
'installation' => ['id' => $githubApp->installation_id],
|
||||
], JSON_THROW_ON_ERROR);
|
||||
|
||||
return $test->call('POST', '/webhooks/source/github/events', [], [], [], [
|
||||
'HTTP_X-GitHub-Event' => 'workflow_job',
|
||||
'HTTP_X-GitHub-Hook-Installation-Target-Id' => (string) $githubApp->app_id,
|
||||
'HTTP_X-Hub-Signature-256' => 'sha256='.hash_hmac('sha256', $body, 'runner-secret'),
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
], $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLite does not enforce varchar(255), so the tests assert the stored lengths that Postgres would reject.
|
||||
*/
|
||||
it('stores over-long workflow, job, and repository names of a queued job truncated to the column length', function () {
|
||||
sendLongNamesWorkflowJobWebhook($this, $this->longNamesApp, 'queued', [
|
||||
'html_url' => 'https://github.com/acme/api/actions/runs/1/job/'.str_repeat('1', 300),
|
||||
])->assertOk()->assertSee('Runner queued.');
|
||||
|
||||
$execution = GithubRunnerExecution::query()->sole();
|
||||
|
||||
expect(mb_strlen($execution->workflow_name))->toBe(255)
|
||||
->and($execution->workflow_name)->toBe(str_repeat('w', 255))
|
||||
->and($execution->job_name)->toBe(str_repeat('ö', 255))
|
||||
->and(mb_strlen($execution->repository_full_name))->toBe(255)
|
||||
->and($execution->workflow_job_html_url)->toBeNull();
|
||||
Queue::assertPushed(ProvisionGithubRunnerJob::class);
|
||||
});
|
||||
|
||||
it('stores over-long names and conclusion of a completed job truncated to the column length', function () {
|
||||
$execution = GithubRunnerExecution::create([
|
||||
'github_app_id' => $this->longNamesApp->id,
|
||||
'trigger_workflow_job_id' => 5001,
|
||||
'labels' => ['self-hosted', 'coolify'],
|
||||
'status' => GithubRunnerStatus::Running,
|
||||
'runner_name' => 'coolify-runner-long',
|
||||
'queued_at' => now(),
|
||||
]);
|
||||
|
||||
sendLongNamesWorkflowJobWebhook($this, $this->longNamesApp, 'completed', [
|
||||
'runner_name' => 'coolify-runner-long',
|
||||
'conclusion' => str_repeat('c', 300),
|
||||
])->assertOk();
|
||||
|
||||
$execution->refresh();
|
||||
expect(mb_strlen($execution->workflow_name))->toBe(255)
|
||||
->and(mb_strlen($execution->job_name))->toBe(255)
|
||||
->and(mb_strlen($execution->repository_full_name))->toBe(255)
|
||||
->and(mb_strlen($execution->conclusion))->toBe(255);
|
||||
});
|
||||
Reference in New Issue
Block a user