fix(git): ensure ssh credentials are propagated to submodule operations (#8900)

This commit is contained in:
Andras Bacsai
2026-06-01 13:18:18 +02:00
committed by GitHub
6 changed files with 263 additions and 28 deletions
+40 -26
View File
@@ -1279,15 +1279,19 @@ class Application extends BaseModel
return application_configuration_dir()."/{$this->uuid}";
}
public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false, ?string $commit = null, ?string $git_ssh_command = null)
public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false, ?string $commit = null, ?string $gitSshCommand = null, ?string $git_ssh_command = null, ?string $gitConfigOptions = null)
{
$baseDir = $this->generateBaseDir($deployment_uuid);
$escapedBaseDir = escapeshellarg($baseDir);
$isShallowCloneEnabled = $this->settings?->is_git_shallow_clone_enabled ?? false;
$gitCommand = $gitConfigOptions ? "git {$gitConfigOptions}" : 'git';
// Use the full GIT_SSH_COMMAND (including -i for SSH key and port options) when provided,
// so that git fetch, submodule update, and lfs pull can authenticate the same way as git clone.
$sshCommand = $git_ssh_command ?? 'GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"';
$resolvedGitSshCommand = $git_ssh_command ?? $gitSshCommand;
$sshCommand = $resolvedGitSshCommand
? (str_starts_with($resolvedGitSshCommand, 'GIT_SSH_COMMAND=')
? $resolvedGitSshCommand
: 'GIT_SSH_COMMAND="'.$resolvedGitSshCommand.'"')
: 'GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"';
// Use the explicitly passed commit (e.g. from rollback), falling back to the application's git_commit_sha.
// Invalid refs will cause the git checkout/fetch command to fail on the remote server.
@@ -1298,9 +1302,9 @@ class Application extends BaseModel
// If shallow clone is enabled and we need a specific commit,
// we need to fetch that specific commit with depth=1
if ($isShallowCloneEnabled) {
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && {$sshCommand} git fetch --depth=1 origin {$escapedCommit} && git -c advice.detachedHead=false checkout {$escapedCommit} >/dev/null 2>&1";
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && {$sshCommand} {$gitCommand} fetch --depth=1 origin {$escapedCommit} && {$gitCommand} -c advice.detachedHead=false checkout {$escapedCommit} >/dev/null 2>&1";
} else {
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && {$sshCommand} git -c advice.detachedHead=false checkout {$escapedCommit} >/dev/null 2>&1";
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && {$sshCommand} {$gitCommand} -c advice.detachedHead=false checkout {$escapedCommit} >/dev/null 2>&1";
}
}
if ($this->settings->is_git_submodules_enabled) {
@@ -1311,10 +1315,10 @@ class Application extends BaseModel
}
// Add shallow submodules flag if shallow clone is enabled
$submoduleFlags = $isShallowCloneEnabled ? '--depth=1' : '';
$git_clone_command = "{$git_clone_command} git submodule sync && {$sshCommand} git submodule update --init --recursive {$submoduleFlags}; fi";
$git_clone_command = "{$git_clone_command} {$gitCommand} submodule sync && {$sshCommand} {$gitCommand} submodule update --init --recursive {$submoduleFlags}; fi";
}
if ($this->settings->is_git_lfs_enabled) {
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && {$sshCommand} git lfs pull";
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && {$sshCommand} {$gitCommand} lfs pull";
}
return $git_clone_command;
@@ -1555,6 +1559,11 @@ class Application extends BaseModel
} else {
$github_access_token = generateGithubInstallationToken($this->source);
$encodedToken = rawurlencode($github_access_token);
// Rewrite same-host HTTPS URLs only for these git commands so submodules can authenticate without persisting credentials.
$gitConfigOption = '-c '.escapeshellarg("url.{$source_html_url_scheme}://x-access-token:{$encodedToken}@{$source_html_url_host}/.insteadOf={$source_html_url_scheme}://{$source_html_url_host}/");
$git_clone_command = str_replace('git clone', "git {$gitConfigOption} clone", $git_clone_command);
if ($exec_in_docker) {
$repoUrl = "$source_html_url_scheme://x-access-token:$encodedToken@$source_html_url_host/{$customRepository}.git";
$escapedRepoUrl = escapeshellarg($repoUrl);
@@ -1567,7 +1576,7 @@ class Application extends BaseModel
$fullRepoUrl = $repoUrl;
}
if (! $only_checkout) {
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false, commit: $commit);
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false, commit: $commit, gitConfigOptions: $gitConfigOption);
}
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
@@ -1578,7 +1587,7 @@ class Application extends BaseModel
if ($pull_request_id !== 0) {
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
$git_checkout_command = $this->buildGitCheckoutCommand($pr_branch_name);
$git_checkout_command = $this->buildGitCheckoutCommand($pr_branch_name, gitConfigOptions: $gitConfigOption ?? null);
$escapedPrBranch = escapeshellarg($branch);
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, "cd {$escapedBaseDir} && git fetch origin {$escapedPrBranch} && $git_checkout_command"));
@@ -1603,12 +1612,13 @@ class Application extends BaseModel
$private_key = base64_encode($private_key);
$gitlabPort = $gitlabSource->custom_port ?? 22;
$escapedCustomRepository = escapeshellarg($customRepository);
$gitlabSshCommand = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$gitlabPort} -o Port={$gitlabPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\"";
$git_clone_command_base = "{$gitlabSshCommand} {$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}";
$gitlabSshCommand = "ssh -o ConnectTimeout=30 -p {$gitlabPort} -o Port={$gitlabPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa";
$gitlabGitSshCommand = "GIT_SSH_COMMAND=\"{$gitlabSshCommand}\"";
$git_clone_command_base = "{$gitlabGitSshCommand} {$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}";
if ($only_checkout) {
$git_clone_command = $git_clone_command_base;
} else {
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base, commit: $commit, git_ssh_command: $gitlabSshCommand);
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base, commit: $commit, gitSshCommand: $gitlabSshCommand);
}
if ($exec_in_docker) {
$commands = collect([
@@ -1631,7 +1641,7 @@ class Application extends BaseModel
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$gitlabPort} -o Port={$gitlabPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && {$gitlabGitSshCommand} git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name, $gitlabSshCommand);
}
if ($exec_in_docker) {
@@ -1674,12 +1684,13 @@ class Application extends BaseModel
}
$private_key = base64_encode($private_key);
$escapedCustomRepository = escapeshellarg($customRepository);
$deployKeySshCommand = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\"";
$git_clone_command_base = "{$deployKeySshCommand} {$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}";
$deployKeySshCommand = "ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa";
$deployKeyGitSshCommand = "GIT_SSH_COMMAND=\"{$deployKeySshCommand}\"";
$git_clone_command_base = "{$deployKeyGitSshCommand} {$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}";
if ($only_checkout) {
$git_clone_command = $git_clone_command_base;
} else {
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base, commit: $commit, git_ssh_command: $deployKeySshCommand);
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base, commit: $commit, gitSshCommand: $deployKeySshCommand);
}
if ($exec_in_docker) {
$commands = collect([
@@ -1702,7 +1713,7 @@ class Application extends BaseModel
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$deployKeySshCommand}\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name, $deployKeySshCommand);
} elseif ($git_type === 'github' || $git_type === 'gitea') {
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
if ($exec_in_docker) {
@@ -1710,14 +1721,14 @@ class Application extends BaseModel
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$deployKeySshCommand}\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name, $deployKeySshCommand);
} elseif ($git_type === 'bitbucket') {
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit);
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$deployKeySshCommand}\" ".$this->buildGitCheckoutCommand($commit, $deployKeySshCommand);
}
}
@@ -1738,6 +1749,7 @@ class Application extends BaseModel
$escapedCustomRepository = escapeshellarg($customRepository);
$git_clone_command = "{$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}";
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true, commit: $commit);
$otherSshCommand = "ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa";
if ($pull_request_id !== 0) {
if ($git_type === 'gitlab') {
@@ -1747,7 +1759,7 @@ class Application extends BaseModel
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$otherSshCommand}\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name, $otherSshCommand);
} elseif ($git_type === 'github' || $git_type === 'gitea') {
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
if ($exec_in_docker) {
@@ -1755,14 +1767,14 @@ class Application extends BaseModel
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$otherSshCommand}\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name, $otherSshCommand);
} elseif ($git_type === 'bitbucket') {
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit);
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$otherSshCommand}\" ".$this->buildGitCheckoutCommand($commit, $otherSshCommand);
}
}
@@ -2011,13 +2023,15 @@ class Application extends BaseModel
);
}
protected function buildGitCheckoutCommand($target): string
protected function buildGitCheckoutCommand($target, ?string $gitSshCommand = null, ?string $gitConfigOptions = null): string
{
$escapedTarget = escapeshellarg($target);
$command = "git checkout {$escapedTarget}";
$gitCommand = $gitConfigOptions ? "git {$gitConfigOptions}" : 'git';
$command = "{$gitCommand} checkout {$escapedTarget}";
if ($this->settings->is_git_submodules_enabled) {
$command .= ' && git submodule update --init --recursive';
$sshCommand = $gitSshCommand ?? 'ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null';
$command .= " && GIT_SSH_COMMAND=\"{$sshCommand}\" {$gitCommand} submodule update --init --recursive";
}
return $command;
+1 -1
View File
@@ -144,7 +144,7 @@ function sharedDataApplications()
'docker_compose_custom_start_command' => \App\Support\ValidationPatterns::shellSafeCommandRules(),
'docker_compose_custom_build_command' => \App\Support\ValidationPatterns::shellSafeCommandRules(),
'is_container_label_escape_enabled' => 'boolean',
'is_preserve_repository_enabled' => 'boolean'
'is_preserve_repository_enabled' => 'boolean',
];
}
+4
View File
@@ -110,6 +110,7 @@ function connectProxyToNetworks(Server $server)
if ($server->isSwarm()) {
$commands = $networks->map(function ($network) {
$safe = escapeshellarg($network);
return [
"docker network ls --format '{{.Name}}' | grep '^{$network}$' >/dev/null || docker network create --driver overlay --attachable {$safe} >/dev/null",
"docker network connect {$safe} coolify-proxy >/dev/null 2>&1 || true",
@@ -119,6 +120,7 @@ function connectProxyToNetworks(Server $server)
} else {
$commands = $networks->map(function ($network) {
$safe = escapeshellarg($network);
return [
"docker network ls --format '{{.Name}}' | grep '^{$network}$' >/dev/null || docker network create --attachable {$safe} >/dev/null",
"docker network connect {$safe} coolify-proxy >/dev/null 2>&1 || true",
@@ -144,6 +146,7 @@ function ensureProxyNetworksExist(Server $server)
if ($server->isSwarm()) {
$commands = $networks->map(function ($network) {
$safe = escapeshellarg($network);
return [
"echo 'Ensuring network {$safe} exists...'",
"docker network ls --format '{{.Name}}' | grep -q '^{$network}$' || docker network create --driver overlay --attachable {$safe}",
@@ -152,6 +155,7 @@ function ensureProxyNetworksExist(Server $server)
} else {
$commands = $networks->map(function ($network) {
$safe = escapeshellarg($network);
return [
"echo 'Ensuring network {$safe} exists...'",
"docker network ls --format '{{.Name}}' | grep -q '^{$network}$' || docker network create --attachable {$safe}",
@@ -0,0 +1,49 @@
<?php
namespace App\Models {
function generateGithubInstallationToken(GithubApp $source): string
{
return 'review token/with+symbols';
}
}
namespace {
use App\Models\Application;
use App\Models\ApplicationSetting;
use App\Models\GithubApp;
test('private github app submodule credentials use per command git config', function () {
$application = new Application;
$application->forceFill([
'uuid' => 'test-app-uuid',
'git_repository' => 'coollabsio/private-app',
'git_branch' => 'main',
'git_commit_sha' => 'HEAD',
]);
$settings = new ApplicationSetting;
$settings->is_git_shallow_clone_enabled = false;
$settings->is_git_submodules_enabled = true;
$settings->is_git_lfs_enabled = false;
$application->setRelation('settings', $settings);
$source = new GithubApp;
$source->forceFill([
'html_url' => 'https://github.com',
'api_url' => 'https://api.github.com',
'is_public' => false,
]);
$application->setRelation('source', $source);
$result = $application->generateGitImportCommands(
deployment_uuid: 'test-deployment',
exec_in_docker: false,
);
expect($result['commands'])
->not->toContain('git config --global')
->toContain("git -c 'url.https://x-access-token:review%20token%2Fwith%2Bsymbols@github.com/.insteadOf=https://github.com/' clone --recurse-submodules -b 'main'")
->toContain("git -c 'url.https://x-access-token:review%20token%2Fwith%2Bsymbols@github.com/.insteadOf=https://github.com/' submodule sync")
->toContain("git -c 'url.https://x-access-token:review%20token%2Fwith%2Bsymbols@github.com/.insteadOf=https://github.com/' submodule update --init --recursive");
});
}
+168
View File
@@ -0,0 +1,168 @@
<?php
use App\Models\Application;
use App\Models\ApplicationSetting;
use App\Models\GitlabApp;
use App\Models\PrivateKey;
describe('Git submodule credential propagation', function () {
beforeEach(function () {
$this->application = new Application;
$this->application->forceFill([
'uuid' => 'test-app-uuid',
'git_commit_sha' => 'HEAD',
]);
$settings = new ApplicationSetting;
$settings->is_git_shallow_clone_enabled = false;
$settings->is_git_submodules_enabled = true;
$settings->is_git_lfs_enabled = false;
$this->application->setRelation('settings', $settings);
});
test('setGitImportSettings uses provided gitSshCommand for submodule update', function () {
$sshCommand = 'ssh -o ConnectTimeout=30 -p 22 -o Port=22 -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa';
$result = $this->application->setGitImportSettings(
deployment_uuid: 'test-uuid',
git_clone_command: 'git clone',
public: false,
gitSshCommand: $sshCommand
);
expect($result)
->toContain('GIT_SSH_COMMAND="'.$sshCommand.'" git submodule update --init --recursive')
->toContain('git submodule sync');
});
test('setGitImportSettings uses default ssh command when no gitSshCommand provided', function () {
$result = $this->application->setGitImportSettings(
deployment_uuid: 'test-uuid',
git_clone_command: 'git clone',
public: false,
);
expect($result)
->toContain('GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" git submodule update --init --recursive');
});
test('setGitImportSettings uses provided gitSshCommand for fetch and checkout', function () {
$this->application->git_commit_sha = 'abc123def456';
$sshCommand = 'ssh -o ConnectTimeout=30 -p 22 -o Port=22 -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa';
$result = $this->application->setGitImportSettings(
deployment_uuid: 'test-uuid',
git_clone_command: 'git clone',
public: false,
gitSshCommand: $sshCommand
);
expect($result)
->toContain('GIT_SSH_COMMAND="'.$sshCommand.'" git -c advice.detachedHead=false checkout');
});
test('setGitImportSettings uses provided gitSshCommand for shallow fetch', function () {
$this->application->git_commit_sha = 'abc123def456';
$this->application->settings->is_git_shallow_clone_enabled = true;
$sshCommand = 'ssh -o ConnectTimeout=30 -p 22 -o Port=22 -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa';
$result = $this->application->setGitImportSettings(
deployment_uuid: 'test-uuid',
git_clone_command: 'git clone',
public: false,
gitSshCommand: $sshCommand
);
expect($result)
->toContain('GIT_SSH_COMMAND="'.$sshCommand.'" git fetch --depth=1 origin');
});
test('setGitImportSettings uses provided gitSshCommand for lfs pull', function () {
$this->application->settings->is_git_lfs_enabled = true;
$sshCommand = 'ssh -o ConnectTimeout=30 -p 22 -i /root/.ssh/id_rsa';
$result = $this->application->setGitImportSettings(
deployment_uuid: 'test-uuid',
git_clone_command: 'git clone',
public: false,
gitSshCommand: $sshCommand
);
expect($result)
->toContain('GIT_SSH_COMMAND="'.$sshCommand.'" git lfs pull');
});
test('buildGitCheckoutCommand includes GIT_SSH_COMMAND for submodule update when provided', function () {
$sshCommand = 'ssh -o ConnectTimeout=30 -p 22 -i /root/.ssh/id_rsa';
$method = new ReflectionMethod($this->application, 'buildGitCheckoutCommand');
$result = $method->invoke($this->application, 'main', $sshCommand);
expect($result)
->toContain("git checkout 'main'")
->toContain('GIT_SSH_COMMAND="'.$sshCommand.'" git submodule update --init --recursive');
});
test('buildGitCheckoutCommand uses default ssh command for submodule update when none provided', function () {
$method = new ReflectionMethod($this->application, 'buildGitCheckoutCommand');
$result = $method->invoke($this->application, 'main');
expect($result)
->toContain('GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" git submodule update --init --recursive');
});
test('buildGitCheckoutCommand omits submodule update when submodules disabled', function () {
$this->application->settings->is_git_submodules_enabled = false;
$method = new ReflectionMethod($this->application, 'buildGitCheckoutCommand');
$result = $method->invoke($this->application, 'main');
expect($result)
->toContain("git checkout 'main'")
->not->toContain('submodule');
});
test('generateGitImportCommands uses GitLab private key for PR submodule checkout', function () {
$settings = new ApplicationSetting;
$settings->is_git_shallow_clone_enabled = false;
$settings->is_git_submodules_enabled = true;
$settings->is_git_lfs_enabled = false;
$privateKey = Mockery::mock(PrivateKey::class)->makePartial();
$privateKey->shouldReceive('getAttribute')->with('private_key')->andReturn('fake-private-key');
$gitlabSource = Mockery::mock(GitlabApp::class)->makePartial();
$gitlabSource->shouldReceive('getMorphClass')->andReturn(GitlabApp::class);
$gitlabSource->shouldReceive('getAttribute')->with('privateKey')->andReturn($privateKey);
$gitlabSource->shouldReceive('getAttribute')->with('custom_port')->andReturn(22);
$gitlabSource->shouldReceive('getAttribute')->with('html_url')->andReturn('https://gitlab.com');
$application = Mockery::mock(Application::class)->makePartial();
$application->git_branch = 'main';
$application->git_commit_sha = 'HEAD';
$application->setRelation('settings', $settings);
$application->source = $gitlabSource;
$application->shouldReceive('deploymentType')->andReturn('source');
$application->shouldReceive('customRepository')->andReturn([
'repository' => 'git@gitlab.com:user/repo.git',
'port' => 22,
]);
$application->shouldReceive('getAttribute')->with('source')->andReturn($gitlabSource);
$result = $application->generateGitImportCommands(
deployment_uuid: 'test-uuid',
pull_request_id: 123,
git_type: 'gitlab',
exec_in_docker: false,
);
$sshCommand = 'ssh -o ConnectTimeout=30 -p 22 -o Port=22 -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa';
expect($result['commands'])
->toContain('GIT_SSH_COMMAND="'.$sshCommand.'" git fetch origin merge-requests/123/head:pr-123-coolify')
->toContain("git checkout 'pr-123-coolify'")
->toContain('GIT_SSH_COMMAND="'.$sshCommand.'" git submodule update --init --recursive')
->not->toContain('GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" git submodule update --init --recursive');
});
});
+1 -1
View File
@@ -163,7 +163,7 @@ it('accepts stored Caddy config when proxy type is CADDY', function () {
});
it('accepts stored config when YAML parsing fails', function () {
$invalidYaml = "this: is: not: [valid yaml: {{{}}}";
$invalidYaml = 'this: is: not: [valid yaml: {{{}}}';
$server = mockServerWithDbConfig($invalidYaml, 'TRAEFIK');
// Invalid YAML should not block — configMatchesProxyType returns true on parse failure