mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-04 10:42:23 +00:00
fix(github): check clock skew only from failed GitHub App responses
Stop calling /zen before each installation token request. A short network problem to GitHub failed deployments with a cURL timeout on /zen. assertGithubClockInSync now takes the failed response and reads its Date header, so the "System time is out of sync" error still shows when GitHub rejects the JWT. Call it on failed JWT requests in the webhook installation check, the permission job, the source connection test, and the server transfer checklist. Remove the /zen fakes from the tests.
This commit is contained in:
1 parent
7f521e496d
commit
b7a2c75739
18 files changed
+54
-80
No files matched your search
+4
-3
@@ -21,8 +21,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
### Fixed
|
||||
|
||||
- Fix the "Network error" when you sign in with email in AppFlowy Web. AppFlowy Web runs on its own domain and sends a `Redirect_to` header to GoTrue on the admin domain, and GoTrue blocked the CORS preflight because it did not allow that header. The AppFlowy service template now sets `GOTRUE_CORS_ALLOWED_HEADERS=redirect_to`. For an existing AppFlowy service, add this variable to the `gotrue` service in the Docker Compose file and restart the service ([#10545](https://github.com/coollabsio/coolify/issues/10545))
|
||||
- Use PostgreSQL 17 instead of MySQL in the Bugsink service template, as the Bugsink Docker Compose sample does, and pin Bugsink to version 2. Before, the template used `mysql:latest` with binary logging on, so the binary logs filled the disk after some weeks and Bugsink stopped. Only new Bugsink services get PostgreSQL. To stop the growth in an existing service, run `PURGE BINARY LOGS BEFORE NOW();` in MySQL, then add `command: ['--disable-log-bin']` to the `mysql` service and restart it ([#10626](https://github.com/coollabsio/coolify/issues/10626))
|
||||
- List the commit images of Docker Compose applications on the **Rollback** page and in `GET /api/v1/applications/{uuid}/rollback-images`. Since v4.5.0 the list was always empty, because a Compose build names each service image `<uuid>_<service>`. The page also shows the running commit when the Compose file has services from other images, for example a database ([#12446](https://github.com/coollabsio/coolify/issues/12446))
|
||||
- Use PostgreSQL 17 instead of MySQL in the Bugsink service template, as the Bugsink Docker Compose sample does, and pin Bugsink to version 2. Before, the template used `mysql:latest` with binary logging on, so the binary logs filled the disk after some weeks and Bugsink stopped. Only new Bugsink services get PostgreSQL. To stop the growth in an existing service, run `PURGE BINARY LOGS BEFORE NOW();` in MySQL, then add `command: ['--disable-log-bin']` to the `mysql` service and restart it ([#10626](https://github.com/coollabsio/coolify/issues/10626))
|
||||
- Finish the startup and the nightly cleanup of stuck resources on instances with many large deployment logs. Before, the cleanup loaded the logs of all deployments and stopped with "Allowed memory size exhausted", so stuck deployments, tasks, and backups were not marked as failed and the proxy configuration was not set up ([#12435](https://github.com/coollabsio/coolify/issues/12435))
|
||||
- Open the database backup pages and **Settings > Backup** faster when a backup has many executions. Before, these pages loaded all executions of the backup on each request, but they did not show them
|
||||
- Queue a deployment from the MCP `deploy` tool and the MCP `control` start action while a deployment of the same application is in progress, as the API and the **Deploy** button do. Before, both started at once, so two rolling updates could remove the new container of each other and stop the application. A deployment of the same commit is now skipped, and the response gives the UUID of the deployment in progress. A rolling update now also removes only the containers that ran before it started ([#12442](https://github.com/coollabsio/coolify/issues/12442))
|
||||
@@ -38,10 +38,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
- Keep the dashboard proxy configuration of a server with the IP `host.docker.internal` that is not the default localhost server. Before, Coolify did not see this server as localhost after it saved the server, for example after a proxy restart, and deleted `coolify.yaml`, so the dashboard was not reachable at its domain ([#12444](https://github.com/coollabsio/coolify/issues/12444))
|
||||
- Show the disk usage of a server on the **Metrics** tab, on the **Fleet** page, and in the MCP `get_server_metrics` tool when Sentinel reports the Coolify data disk under a different mount point than `/`, `/data`, or `/data/coolify`. For example, this occurs on a NAS where `/data` is a symlink into a data volume. Before, the disk usage was always empty on these servers ([#12448](https://github.com/coollabsio/coolify/issues/12448))
|
||||
- Pin the Usesend service template to `usesend/usesend:v1.10.0`, which has the Next.js, Node.js 24, and nodemailer security updates. Before, the template used `latest`, so a server kept an old cached image until you pulled the images again. The template does not use MinIO, so the MinIO-to-RustFS change of v1.10.0 does not affect it ([#12443](https://github.com/coollabsio/coolify/issues/12443))
|
||||
- Use PostgreSQL 16 in the Outline service template. New Outline versions need PostgreSQL 14 or later, so a new Outline service restarted in a loop on `postgres:12-alpine` with `syntax error at or near "TRIGGER"`. An existing Outline service keeps PostgreSQL 12. To upgrade it, follow the steps in the Outline service documentation ([#12345](https://github.com/coollabsio/coolify/issues/12345))
|
||||
- Collect only the logs of its own stack in the Supabase service template. Before, the Vector container read the logs of all containers on the server, so with two Supabase services on one server (for example a clone for production), Supabase Studio showed the API logs of the other stack. An existing Supabase service keeps its `vector.yml`. To fix it, on the server, add `include_labels: ['com.docker.compose.project=${COOLIFY_RESOURCE_UUID}']` under `sources.docker_host` in `/data/coolify/services/<service-uuid>/volumes/logs/vector.yml`, then restart the service ([#10324](https://github.com/coollabsio/coolify/issues/10324))
|
||||
- Keep an application that uses a server only as an additional destination when you delete that server with **Delete all resources** or with `DELETE /api/v1/servers/{uuid}?force=true`. Before, Coolify deleted the application on all its servers, also after **Promote** made another server its main server. Coolify now removes the application only from the deleted server and stops its containers there. A server that has only such applications can be deleted without **Delete all resources**. The delete confirmation lists the resources that it deletes and the applications that keep running on another server
|
||||
- Use PostgreSQL 16 in the Outline service template. New Outline versions need PostgreSQL 14 or later, so a new Outline service restarted in a loop on `postgres:12-alpine` with `syntax error at or near "TRIGGER"`. An existing Outline service keeps PostgreSQL 12. To upgrade it, follow the steps in the Outline service documentation ([#12345](https://github.com/coollabsio/coolify/issues/12345))
|
||||
- Fix the LiteLLM service template for new services. The master key now starts with `sk-`, as LiteLLM requires: use `sk-` followed by the value of `SERVICE_PASSWORD_MASTERKEY` as the admin API key. The template also sets a separate `LITELLM_SALT_KEY`, so stored model credentials stay readable when you change the master key, and sets `STORE_MODEL_IN_DB` to `True` by default, so you can add models in the LiteLLM UI ([#10521](https://github.com/coollabsio/coolify/issues/10521))
|
||||
- Send one request less to GitHub before each deployment and each list of repositories of a GitHub App. Before, Coolify always called `/zen` to compare its clock with GitHub before it requested an installation token, so a short network problem between the Coolify instance and GitHub failed the deployment with `cURL error 28: Connection timed out ... for https://api.github.com/zen`. Coolify now compares the clocks only when GitHub rejects a request, from the `Date` header of that response, and still shows "System time is out of sync with GitHub API time" ([#9785](https://github.com/coollabsio/coolify/issues/9785))
|
||||
- Keep an application that uses a server only as an additional destination when you delete that server with **Delete all resources** or with `DELETE /api/v1/servers/{uuid}?force=true`. Before, Coolify deleted the application on all its servers, also after **Promote** made another server its main server. Coolify now removes the application only from the deleted server and stops its containers there. A server that has only such applications can be deleted without **Delete all resources**. The delete confirmation lists the resources that it deletes and the applications that keep running on another server
|
||||
|
||||
## [v4.6.0] - 2026-11-02
|
||||
|
||||
|
||||
@@ -841,6 +841,9 @@ class Github extends Controller
|
||||
])
|
||||
->timeout(10)
|
||||
->get("{$github_app->api_url}/app/installations/{$installation_id}");
|
||||
if (! $response->successful()) {
|
||||
assertGithubClockInSync($response);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return strip_tags(str_replace('<br>', ' ', $e->getMessage()));
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ class GithubAppPermissionJob implements ShouldBeEncrypted, ShouldQueue
|
||||
])->get("{$this->github_app->api_url}/app");
|
||||
|
||||
if (! $response->successful()) {
|
||||
assertGithubClockInSync($response);
|
||||
throw new \RuntimeException('Failed to fetch GitHub app permissions: '.$response->body());
|
||||
}
|
||||
|
||||
|
||||
@@ -272,6 +272,7 @@ class Change extends Component
|
||||
])->timeout(10)->get("{$this->github_app->api_url}/app");
|
||||
|
||||
if (! $appResponse->successful()) {
|
||||
assertGithubClockInSync($appResponse);
|
||||
$error = data_get($appResponse->json(), 'message', 'Unknown error');
|
||||
$this->dispatch('error', "Connection failed: {$error}");
|
||||
|
||||
|
||||
@@ -464,6 +464,7 @@ class TransferFinishChecklist
|
||||
->withHeaders(['Authorization' => 'Bearer '.generateGithubJwt($githubApp), 'Accept' => 'application/vnd.github+json'])
|
||||
->{$method}(rtrim($githubApp->api_url, '/').$path, $data);
|
||||
if (! $response->successful()) {
|
||||
assertGithubClockInSync($response);
|
||||
throw new RuntimeException('GitHub answered with HTTP '.$response->status().': '.data_get($response->json(), 'message', 'no details'));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use Carbon\Carbon;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use Illuminate\Http\Client\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -150,11 +151,25 @@ function encodeGithubPathSegment(string $segment): string
|
||||
return rawurlencode($segment);
|
||||
}
|
||||
|
||||
function assertGithubClockInSync(string $apiUrl): void
|
||||
/**
|
||||
* GitHub rejects an App JWT when the clocks of Coolify and GitHub differ.
|
||||
* Call this with the failed response of a JWT request: its Date header shows the difference without an extra request.
|
||||
*/
|
||||
function assertGithubClockInSync(Response $response): void
|
||||
{
|
||||
$response = Http::GitSource($apiUrl)->get("{$apiUrl}/zen");
|
||||
$date = $response->header('Date');
|
||||
|
||||
if (blank($date)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$githubTime = Carbon::parse($date);
|
||||
} catch (Throwable) {
|
||||
return;
|
||||
}
|
||||
|
||||
$serverTime = CarbonImmutable::now()->setTimezone('UTC');
|
||||
$githubTime = Carbon::parse($response->header('date'));
|
||||
$timeDiff = abs($serverTime->diffInSeconds($githubTime));
|
||||
|
||||
if ($timeDiff > 50) {
|
||||
@@ -179,8 +194,6 @@ function generateGithubToken(GithubApp $source, string $type)
|
||||
{
|
||||
$privateKey = githubAppPrivateKey($source) ?? throw new RuntimeException('Private key not found for this GitHub App.');
|
||||
|
||||
assertGithubClockInSync($source->api_url);
|
||||
|
||||
$signingKey = InMemory::plainText($privateKey->private_key);
|
||||
$algorithm = new Sha256;
|
||||
$tokenBuilder = (new Builder(new JoseEncoder, ChainedFormatter::default()));
|
||||
@@ -210,6 +223,7 @@ function generateGithubToken(GithubApp $source, string $type)
|
||||
)->post("{$source->api_url}/app/installations/{$source->installation_id}/access_tokens");
|
||||
|
||||
if (! $response->successful()) {
|
||||
assertGithubClockInSync($response);
|
||||
$error = data_get($response->json(), 'message', "HTTP {$response->status()}");
|
||||
if ($error === 'Not Found') {
|
||||
$error = "GitHub App installation {$source->installation_id} was not found. Install the GitHub App again or update its installation ID.";
|
||||
@@ -303,8 +317,6 @@ function syncGithubAppName(GithubApp $source, bool $throw = false): ?string
|
||||
return null;
|
||||
}
|
||||
|
||||
assertGithubClockInSync($source->api_url);
|
||||
|
||||
$jwt = generateGithubAppJwt($privateKey->private_key, $source->app_id);
|
||||
|
||||
$response = Http::GitSource($source->api_url)->withHeaders([
|
||||
@@ -314,6 +326,7 @@ function syncGithubAppName(GithubApp $source, bool $throw = false): ?string
|
||||
])->get("{$source->api_url}/app");
|
||||
|
||||
if (! $response->successful()) {
|
||||
assertGithubClockInSync($response);
|
||||
throw new RuntimeException(data_get($response->json(), 'message', 'Failed to fetch GitHub App information.'));
|
||||
}
|
||||
|
||||
|
||||
@@ -224,9 +224,6 @@ describe('other application creation endpoints use_build_secrets', function () {
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
|
||||
'Date' => now()->toRfc7231String(),
|
||||
]),
|
||||
'https://api.github.com/app/installations/67890/access_tokens' => Http::response([
|
||||
'token' => 'github-installation-token',
|
||||
], 201),
|
||||
@@ -278,9 +275,6 @@ describe('other application creation endpoints use_build_secrets', function () {
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
|
||||
'Date' => now()->toRfc7231String(),
|
||||
]),
|
||||
'https://api.github.com/app/installations/9876/access_tokens' => Http::response([
|
||||
'token' => 'github-installation-token',
|
||||
], 201),
|
||||
|
||||
@@ -469,9 +469,6 @@ describe('GitHub app API url normalization', function () {
|
||||
|
||||
Http::preventStrayRequests();
|
||||
Http::fake([
|
||||
'https://api.octocorp.ghe.com/zen' => Http::response('Keep it logically awesome.', 200, [
|
||||
'Date' => now()->toRfc7231String(),
|
||||
]),
|
||||
'https://api.octocorp.ghe.com/app/installations/67890/access_tokens' => Http::response([
|
||||
'token' => 'installation-token',
|
||||
]),
|
||||
|
||||
@@ -231,7 +231,6 @@ describe('GitHub Source Change Component', function () {
|
||||
test('syncGithubAppName persists the github slug and renames the private key', function () {
|
||||
Http::fake([
|
||||
'*/app' => Http::response(['slug' => 'actual-github-slug']),
|
||||
'*/zen' => Http::response('Keep it logically awesome.'),
|
||||
]);
|
||||
|
||||
$privateKey = PrivateKey::create([
|
||||
@@ -680,9 +679,6 @@ describe('GitHub Source Change Component', function () {
|
||||
|
||||
Http::preventStrayRequests();
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
|
||||
'date' => now()->toRfc7231String(),
|
||||
]),
|
||||
'https://api.github.com/app' => Http::response([
|
||||
'permissions' => [
|
||||
'contents' => 'read',
|
||||
@@ -735,9 +731,6 @@ describe('GitHub Source Change Component', function () {
|
||||
|
||||
Http::preventStrayRequests();
|
||||
Http::fake([
|
||||
'https://api.github.ghe.com/zen' => Http::response('Keep it logically awesome.', 200, [
|
||||
'date' => now()->toRfc7231String(),
|
||||
]),
|
||||
'https://api.github.ghe.com/app' => Http::response([
|
||||
'slug' => 'octocorp-app',
|
||||
]),
|
||||
@@ -837,9 +830,6 @@ describe('GitHub Source Change Component', function () {
|
||||
|
||||
Http::preventStrayRequests();
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
|
||||
'date' => now()->toRfc7231String(),
|
||||
]),
|
||||
'https://api.github.com/app' => Http::response([
|
||||
'name' => 'Coolify GitHub App',
|
||||
'slug' => 'coolify-github-app',
|
||||
@@ -932,7 +922,6 @@ describe('GitHub App name sync after the user switches the session team', functi
|
||||
|
||||
Http::preventStrayRequests();
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, ['date' => now()->toRfc7231String()]),
|
||||
'https://api.github.com/app' => Http::response(['slug' => 'synced-app']),
|
||||
]);
|
||||
|
||||
|
||||
@@ -35,11 +35,7 @@ beforeEach(function () {
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => fn () => Http::response('Keep it logically awesome.', 200, [
|
||||
'Date' => now()->toRfc7231String(),
|
||||
]),
|
||||
]);
|
||||
Http::preventStrayRequests();
|
||||
});
|
||||
|
||||
function fakeGithubInstallationTokenResponses(mixed $response): void
|
||||
@@ -119,3 +115,21 @@ it('reports the HTTP status when GitHub keeps failing without an error message',
|
||||
->toThrow(RuntimeException::class, 'Failed to get installation token for Private GitHub App with error: HTTP 503')
|
||||
->and(githubInstallationTokenRequests())->toBe(3);
|
||||
});
|
||||
|
||||
it('requests the installation token without a separate GitHub clock request', function () {
|
||||
fakeGithubInstallationTokenResponses(Http::response(['token' => 'only-token'], 201));
|
||||
|
||||
expect(generateGithubInstallationToken($this->githubApp))->toBe('only-token')
|
||||
->and(Http::recorded())->toHaveCount(1);
|
||||
});
|
||||
|
||||
it('reports the clock difference when GitHub rejects the token request of an out-of-sync server', function () {
|
||||
fakeGithubInstallationTokenResponses(Http::response(
|
||||
['message' => "'Issued at' claim ('iat') must be an Integer representing a time in the past"],
|
||||
401,
|
||||
['Date' => now()->addMinutes(5)->toRfc7231String()],
|
||||
));
|
||||
|
||||
expect(fn () => generateGithubInstallationToken($this->githubApp))
|
||||
->toThrow(Exception::class, 'System time is out of sync with GitHub API time');
|
||||
});
|
||||
@@ -105,9 +105,6 @@ function sendGithubAppMergePush(TestCase $test, string $before = PUSH_WATCH_PATH
|
||||
function fakeGithubAppPushApi(array $compareResponse): void
|
||||
{
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
|
||||
'Date' => now()->toRfc7231String(),
|
||||
]),
|
||||
'https://api.github.com/app/installations/67890/access_tokens' => Http::response([
|
||||
'token' => 'fake-installation-token',
|
||||
], 201),
|
||||
|
||||
@@ -53,9 +53,6 @@ beforeEach(function () {
|
||||
function fakeGithubHttp(array $repositories): void
|
||||
{
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
|
||||
'Date' => now()->toRfc7231String(),
|
||||
]),
|
||||
'https://api.github.com/app/installations/67890/access_tokens' => Http::response([
|
||||
'token' => 'fake-installation-token',
|
||||
], 201),
|
||||
@@ -129,9 +126,6 @@ describe('GitHub Private Repository Component', function () {
|
||||
|
||||
fakeGithubHttp([$repository]);
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
|
||||
'Date' => now()->toRfc7231String(),
|
||||
]),
|
||||
'https://api.github.com/app/installations/67890/access_tokens' => Http::response([
|
||||
'token' => 'fake-installation-token',
|
||||
], 201),
|
||||
@@ -281,9 +275,6 @@ describe('GitHub Private Repository Component', function () {
|
||||
|
||||
$callCount = 0;
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
|
||||
'Date' => now()->toRfc7231String(),
|
||||
]),
|
||||
'https://api.github.com/app/installations/67890/access_tokens' => Http::response([
|
||||
'token' => 'fake-installation-token',
|
||||
], 201),
|
||||
@@ -346,9 +337,6 @@ describe('GitHub Private Repository Component', function () {
|
||||
|
||||
test('loadRepositories shows an error when the installation is not found', function () {
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
|
||||
'Date' => now()->toRfc7231String(),
|
||||
]),
|
||||
'https://api.github.com/app/installations/67890/access_tokens' => Http::response(['message' => 'Not Found'], 404),
|
||||
]);
|
||||
|
||||
@@ -360,9 +348,6 @@ describe('GitHub Private Repository Component', function () {
|
||||
|
||||
test('loadRepositories shows the GitHub error when listing repositories fails', function () {
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
|
||||
'Date' => now()->toRfc7231String(),
|
||||
]),
|
||||
'https://api.github.com/app/installations/67890/access_tokens' => Http::response([
|
||||
'token' => 'fake-installation-token',
|
||||
], 201),
|
||||
|
||||
@@ -127,7 +127,6 @@ function sendWorkflowJobWebhook($test, GithubApp $githubApp, string $secret, str
|
||||
function fakeRunnerGithubApi(GithubApp $githubApp): void
|
||||
{
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('ok', 200, ['Date' => now()->toRfc7231String()]),
|
||||
"https://api.github.com/app/installations/{$githubApp->installation_id}/access_tokens" => Http::response(['token' => 'installation-token'], 201),
|
||||
'https://api.github.com/orgs/acme/actions/runners/generate-jitconfig' => Http::response([
|
||||
'runner' => ['id' => 42, 'name' => 'runner'],
|
||||
@@ -140,7 +139,6 @@ function fakeRunnerGithubApi(GithubApp $githubApp): void
|
||||
function fakeWorkflowRunApi(GithubApp $githubApp, mixed $runResponse): void
|
||||
{
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('ok', 200, ['Date' => now()->toRfc7231String()]),
|
||||
"https://api.github.com/app/installations/{$githubApp->installation_id}/access_tokens" => Http::response(['token' => 'installation-token'], 201),
|
||||
'https://api.github.com/repos/acme/api/actions/runs/321' => $runResponse,
|
||||
]);
|
||||
@@ -584,7 +582,6 @@ describe('provisioning', function () {
|
||||
runnerTestConfig($server, $this->githubApp);
|
||||
$execution = runnerTestExecution($this->githubApp);
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('ok', 200, ['Date' => now()->toRfc7231String()]),
|
||||
'https://api.github.com/app/installations/*' => Http::response(['token' => 'installation-token'], 201),
|
||||
'https://api.github.com/orgs/acme/actions/runners/generate-jitconfig' => function () use ($server) {
|
||||
DeleteServer::run($server->id);
|
||||
@@ -659,7 +656,6 @@ describe('provisioning', function () {
|
||||
runnerTestConfig(runnerTestServer($this->team), $this->githubApp);
|
||||
$execution = runnerTestExecution($this->githubApp, ['provision_attempts' => $attempts]);
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('ok', 200, ['Date' => now()->toRfc7231String()]),
|
||||
'https://api.github.com/app/installations/*' => Http::response(['token' => 'installation-token'], 201),
|
||||
'https://api.github.com/orgs/acme/actions/runners/42' => Http::response(null, 204),
|
||||
'https://api.github.com/orgs/acme/actions/runners/generate-jitconfig' => function () use ($execution, $status) {
|
||||
@@ -687,7 +683,6 @@ describe('provisioning', function () {
|
||||
runnerTestConfig(runnerTestServer($this->team), $this->githubApp);
|
||||
$execution = runnerTestExecution($this->githubApp);
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('ok', 200, ['Date' => now()->toRfc7231String()]),
|
||||
'https://api.github.com/app/installations/*' => Http::response(['token' => 'installation-token'], 201),
|
||||
'https://api.github.com/orgs/acme/actions/runners/42' => Http::response(null, 204),
|
||||
'https://api.github.com/orgs/acme/actions/runners/generate-jitconfig' => function () use ($execution) {
|
||||
@@ -712,7 +707,6 @@ describe('provisioning', function () {
|
||||
runnerTestConfig($server, $this->githubApp);
|
||||
$execution = runnerTestExecution($this->githubApp);
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('ok', 200, ['Date' => now()->toRfc7231String()]),
|
||||
'https://api.github.com/app/installations/*' => Http::response(['token' => 'installation-token'], 201),
|
||||
'https://api.github.com/orgs/acme/actions/runners/42' => Http::response(null, 204),
|
||||
'https://api.github.com/orgs/acme/actions/runners/generate-jitconfig' => function () use ($server, $execution, $cancelled) {
|
||||
@@ -942,7 +936,6 @@ describe('server deletion', function () {
|
||||
|
||||
it('removes the runner from GitHub and ignores GitHub errors', function (int $status) {
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('ok', 200, ['Date' => now()->toRfc7231String()]),
|
||||
'https://api.github.com/app/installations/*' => Http::response(['token' => 'installation-token'], 201),
|
||||
'https://api.github.com/orgs/acme/actions/runners/42' => Http::response(null, $status),
|
||||
]);
|
||||
@@ -977,7 +970,6 @@ describe('server deletion', function () {
|
||||
function fakeRejectedJitConfig(): void
|
||||
{
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('ok', 200, ['Date' => now()->toRfc7231String()]),
|
||||
'https://api.github.com/app/installations/*' => Http::response(['token' => 'installation-token'], 201),
|
||||
'https://api.github.com/orgs/acme/actions/runners/generate-jitconfig' => Http::response(['message' => 'Resource not accessible by integration'], 403),
|
||||
]);
|
||||
|
||||
@@ -99,7 +99,6 @@ function unreadyRunnerApp(): array
|
||||
function fakeRunnerGroupApi(GithubApp $githubApp, int $createStatus = 201): void
|
||||
{
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('ok', 200, ['Date' => now()->toRfc7231String()]),
|
||||
"https://api.github.com/app/installations/{$githubApp->installation_id}/access_tokens" => Http::response(['token' => 'installation-token'], 201),
|
||||
'https://api.github.com/orgs/acme/actions/runner-groups' => fn ($request) => $request->method() === 'POST'
|
||||
? Http::response($createStatus === 201 ? ['id' => 55, 'default' => false] : ['message' => 'Upgrade to create runner groups'], $createStatus)
|
||||
@@ -137,7 +136,6 @@ describe('runner settings page', function () {
|
||||
it('keeps the restrictions an organization admin set on an existing runner group', function () {
|
||||
$this->githubApp->update(['runner_group_id' => 77]);
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('ok', 200, ['Date' => now()->toRfc7231String()]),
|
||||
"https://api.github.com/app/installations/{$this->githubApp->installation_id}/access_tokens" => Http::response(['token' => 'installation-token'], 201),
|
||||
'https://api.github.com/orgs/acme/actions/runner-groups/77' => Http::response(['id' => 77, 'default' => false, 'visibility' => 'selected', 'allows_public_repositories' => false]),
|
||||
]);
|
||||
@@ -625,7 +623,6 @@ describe('build server role', function () {
|
||||
describe('GitHub App runner permissions', function () {
|
||||
it('stores runner permissions and webhook events from GitHub', function () {
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('ok', 200, ['Date' => now()->toRfc7231String()]),
|
||||
'https://api.github.com/app' => Http::response([
|
||||
'permissions' => ['contents' => 'read', 'metadata' => 'read', 'organization_self_hosted_runners' => 'write', 'actions' => 'read'],
|
||||
'events' => ['push', 'workflow_job'],
|
||||
|
||||
@@ -81,7 +81,6 @@ function githubAppRepositoryFlow(): Testable
|
||||
'is_system_wide' => false,
|
||||
]);
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, ['Date' => now()->toRfc7231String()]),
|
||||
'https://api.github.com/app/installations/67890/access_tokens' => Http::response(['token' => 'installation-token'], 201),
|
||||
'https://api.github.com/installation/repositories*' => Http::response([
|
||||
'total_count' => 1,
|
||||
|
||||
@@ -383,9 +383,6 @@ it('uses one GitHub App installation token for all GitHub API calls of a pull re
|
||||
$firstPage = collect(range(1, 100))->map(fn (int $i): array => ['filename' => ".github/workflows/workflow-{$i}.yml"])->all();
|
||||
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
|
||||
'Date' => now()->toRfc7231String(),
|
||||
]),
|
||||
'https://api.github.com/app/installations/67890/access_tokens' => Http::response([
|
||||
'token' => 'fake-installation-token',
|
||||
], 201),
|
||||
|
||||
@@ -96,9 +96,6 @@ function fakeGithubInstallationVerification(int $appId, string $apiUrl = 'https:
|
||||
{
|
||||
Http::preventStrayRequests();
|
||||
Http::fake([
|
||||
"{$apiUrl}/zen" => Http::response('Keep it logically awesome.', 200, [
|
||||
'Date' => now()->toRfc7231String(),
|
||||
]),
|
||||
"{$apiUrl}/app/installations/*" => Http::response([
|
||||
'id' => 555,
|
||||
'app_id' => $appId,
|
||||
@@ -110,9 +107,6 @@ function fakeGithubInstallationVerificationFailure(): void
|
||||
{
|
||||
Http::preventStrayRequests();
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
|
||||
'Date' => now()->toRfc7231String(),
|
||||
]),
|
||||
'https://api.github.com/app/installations/*' => Http::response(['message' => 'Not Found'], 404),
|
||||
]);
|
||||
}
|
||||
@@ -367,7 +361,6 @@ it('verifies ghe dot com installations through the data residency api host', fun
|
||||
$this->get('/webhooks/source/github/install?state=valid-install-state&setup_action=install&installation_id=123456')
|
||||
->assertRedirect(route('source.github.show', ['github_app_uuid' => $this->githubApp->uuid]));
|
||||
|
||||
Http::assertSent(fn ($request) => $request->url() === 'https://api.octocorp.ghe.com/zen');
|
||||
Http::assertSent(fn ($request) => $request->url() === 'https://api.octocorp.ghe.com/app/installations/123456');
|
||||
});
|
||||
|
||||
@@ -420,9 +413,11 @@ it('redirects to the github app with the reason when the server clock is out of
|
||||
configureGithubAppCredentials($this->githubApp);
|
||||
Http::preventStrayRequests();
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
|
||||
'Date' => now()->addMinutes(5)->toRfc7231String(),
|
||||
]),
|
||||
'https://api.github.com/app/installations/*' => Http::response(
|
||||
['message' => "'Issued at' claim ('iat') must be an Integer representing a time in the past"],
|
||||
401,
|
||||
['Date' => now()->addMinutes(5)->toRfc7231String()],
|
||||
),
|
||||
]);
|
||||
cacheGithubAppSetupState('valid-install-state', 'install', $this->githubApp);
|
||||
|
||||
|
||||
@@ -101,7 +101,6 @@ test('the GitHub App webhook step checks the URL at GitHub and sets it with one
|
||||
$key = "github_app_webhook:{$this->githubApp->uuid}";
|
||||
$hookUrl = 'https://coolify-a.test/webhooks/source/github/events';
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('ok', 200, ['Date' => now()->toRfc7231String()]),
|
||||
'https://api.github.com/app/hook/config' => function ($request) use (&$hookUrl) {
|
||||
if ($request->method() === 'PATCH') {
|
||||
$hookUrl = $request['url'];
|
||||
@@ -195,7 +194,6 @@ test('a failed check shows a short message and keeps the technical reason as det
|
||||
finishTestApplication($this, ['source_type' => GithubApp::class, 'source_id' => $this->githubApp->id]);
|
||||
$this->checklist->start($this->server);
|
||||
Http::fake([
|
||||
'https://api.github.com/zen' => Http::response('ok', 200, ['Date' => now()->toRfc7231String()]),
|
||||
'https://api.github.com/app/hook/config' => Http::response(['message' => 'Bad credentials'], 401),
|
||||
]);
|
||||
|
||||
|
||||
Reference in new issue
Block a user