This commit is contained in:
Andras Bacsai 2026-03-11 03:20:51 +08:00 committed by GitHub
commit 23dcf344e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 59 additions and 13 deletions

View file

@ -177,6 +177,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
@ -214,6 +216,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if ($source) {
$this->source = $source->getMorphClass()::where('id', $this->application->source->id)->first();
}
$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();
@ -279,6 +282,18 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
return;
}
// Generate GitHub installation token once per deployment execution (not in constructor,
// since queued jobs may sit in the queue long enough for tokens to expire)
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 via null-coalesce fallback
$this->github_access_token = null;
}
}
try {
// Make sure the private key is stored in the filesystem
$this->server->privateKey->storeInFileSystem();
@ -2091,7 +2106,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)) {
@ -2216,7 +2231,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;

View file

@ -1142,7 +1142,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();
@ -1162,7 +1162,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";
@ -1249,7 +1250,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();
@ -1300,7 +1301,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);

View file

@ -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);
}
});
}

View file

@ -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,20 @@ 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, function (\Exception $exception) {
// Don't retry auth errors — the same JWT won't become valid on retry
if ($exception instanceof \Illuminate\Http\Client\RequestException) {
return ! in_array($exception->response->status(), [401, 403]);
}
return true;
}, 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 +95,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 +108,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 {

View file

@ -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),