diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 84f362519..b593ea836 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -175,6 +175,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue private Collection|string $build_secrets; + private ?string $github_access_token = null; + public function tags() { // Do not remove this one, it needs to properly identify which worker is running the job @@ -212,6 +214,16 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue if ($source) { $this->source = $source->getMorphClass()::where('id', $this->application->source->id)->first(); } + + // Pre-generate GitHub installation token once to avoid multiple API calls during deployment + if ($this->source instanceof GithubApp && ! $this->source->is_public) { + try { + $this->github_access_token = generateGithubInstallationToken($this->source); + } catch (\Exception $e) { + // Token generation will be retried later if needed + $this->github_access_token = null; + } + } $this->server = Server::find($this->application_deployment_queue->server_id); $this->timeout = $this->server->settings->dynamic_timeout; $this->destination = $this->server->destinations()->where('id', $this->application_deployment_queue->destination_id)->first(); @@ -2065,7 +2077,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue private function check_git_if_build_needed() { if (is_object($this->source) && $this->source->getMorphClass() === \App\Models\GithubApp::class && $this->source->is_public === false) { - $repository = githubApi($this->source, "repos/{$this->customRepository}"); + $repository = githubApi($this->source, "repos/{$this->customRepository}", token: $this->github_access_token); $data = data_get($repository, 'data'); $repository_project_id = data_get($data, 'id'); if (isset($repository_project_id)) { @@ -2190,7 +2202,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue deployment_uuid: $this->deployment_uuid, pull_request_id: $this->pull_request_id, git_type: $this->git_type, - commit: $this->commit + commit: $this->commit, + github_access_token: $this->github_access_token ); return $commands; diff --git a/app/Models/Application.php b/app/Models/Application.php index 2eab8df20..a72d2e2b8 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -1143,7 +1143,7 @@ class Application extends BaseModel } } - public function generateGitLsRemoteCommands(string $deployment_uuid, bool $exec_in_docker = true) + public function generateGitLsRemoteCommands(string $deployment_uuid, bool $exec_in_docker = true, ?string $github_access_token = null) { $branch = $this->git_branch; ['repository' => $customRepository, 'port' => $customPort] = $this->customRepository(); @@ -1163,7 +1163,8 @@ class Application extends BaseModel $fullRepoUrl = "{$this->source->html_url}/{$customRepository}"; $base_command = "{$base_command} {$escapedRepoUrl}"; } else { - $github_access_token = generateGithubInstallationToken($this->source); + // Use provided token or generate a new one + $github_access_token = $github_access_token ?? generateGithubInstallationToken($this->source); if ($exec_in_docker) { $repoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git"; @@ -1250,7 +1251,7 @@ class Application extends BaseModel } } - public function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null, ?string $commit = null) + public function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null, ?string $commit = null, ?string $github_access_token = null) { $branch = $this->git_branch; ['repository' => $customRepository, 'port' => $customPort] = $this->customRepository(); @@ -1301,7 +1302,8 @@ class Application extends BaseModel $commands->push($git_clone_command); } } else { - $github_access_token = generateGithubInstallationToken($this->source); + // Use provided token or generate a new one + $github_access_token = $github_access_token ?? generateGithubInstallationToken($this->source); if ($exec_in_docker) { $repoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git"; $escapedRepoUrl = escapeshellarg($repoUrl); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 717daf2a2..40ed73829 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -66,16 +66,18 @@ class AppServiceProvider extends ServiceProvider private function configureGitHubHttp(): void { Http::macro('GitHub', function (string $api_url, ?string $github_access_token = null) { + $timeout = config('constants.github.api_timeout', 30); + if ($github_access_token) { return Http::withHeaders([ 'X-GitHub-Api-Version' => '2022-11-28', 'Accept' => 'application/vnd.github.v3+json', 'Authorization' => "Bearer $github_access_token", - ])->baseUrl($api_url); + ])->baseUrl($api_url)->timeout($timeout); } else { return Http::withHeaders([ 'Accept' => 'application/vnd.github.v3+json', - ])->baseUrl($api_url); + ])->baseUrl($api_url)->timeout($timeout); } }); } diff --git a/bootstrap/helpers/github.php b/bootstrap/helpers/github.php index 4a61960fb..b8ea9a585 100644 --- a/bootstrap/helpers/github.php +++ b/bootstrap/helpers/github.php @@ -14,7 +14,19 @@ use Lcobucci\JWT\Token\Builder; function generateGithubToken(GithubApp $source, string $type) { - $response = Http::get("{$source->api_url}/zen"); + $timeout = config('constants.github.api_timeout', 30); + + $response = Http::timeout($timeout) + ->retry(3, 200, throw: false) + ->get("{$source->api_url}/zen"); + + if (! $response->successful()) { + throw new \Exception( + 'Failed to connect to GitHub API to sync time. '. + 'Please check your network connection and try again.' + ); + } + $serverTime = CarbonImmutable::now()->setTimezone('UTC'); $githubTime = Carbon::parse($response->header('date')); $timeDiff = abs($serverTime->diffInSeconds($githubTime)); @@ -44,11 +56,13 @@ function generateGithubToken(GithubApp $source, string $type) return match ($type) { 'jwt' => $jwt, - 'installation' => (function () use ($source, $jwt) { + 'installation' => (function () use ($source, $jwt, $timeout) { $response = Http::withHeaders([ 'Authorization' => "Bearer $jwt", 'Accept' => 'application/vnd.github.machine-man-preview+json', - ])->post("{$source->api_url}/app/installations/{$source->installation_id}/access_tokens"); + ])->timeout($timeout) + ->retry(3, 200, throw: false) + ->post("{$source->api_url}/app/installations/{$source->installation_id}/access_tokens"); if (! $response->successful()) { $error = data_get($response->json(), 'message', 'no error message found'); @@ -74,7 +88,7 @@ function generateGithubJwt(GithubApp $source) return generateGithubToken($source, 'jwt'); } -function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $method = 'get', ?array $data = null, bool $throwError = true) +function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $method = 'get', ?array $data = null, bool $throwError = true, ?string $token = null) { if (is_null($source)) { throw new \Exception('Source is required for API calls'); @@ -87,7 +101,8 @@ function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $m if ($source->is_public) { $response = Http::GitHub($source->api_url)->$method($endpoint); } else { - $token = generateGithubInstallationToken($source); + // Use provided token or generate a new one + $token = $token ?? generateGithubInstallationToken($source); if ($data && in_array(strtolower($method), ['post', 'patch', 'put'])) { $response = Http::GitHub($source->api_url, $token)->$method($endpoint, $data); } else { diff --git a/config/constants.php b/config/constants.php index 499076e85..67c967de2 100644 --- a/config/constants.php +++ b/config/constants.php @@ -59,6 +59,10 @@ return [ 'minimum_required_version' => '24.0', ], + 'github' => [ + 'api_timeout' => env('GITHUB_API_TIMEOUT', 30), + ], + 'ssh' => [ 'mux_enabled' => env('MUX_ENABLED', env('SSH_MUX_ENABLED', true)), 'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', 3600),