Merge branch 'next' into next
This commit is contained in:
commit
80fdc87f36
3 changed files with 292 additions and 9 deletions
|
|
@ -2779,12 +2779,23 @@ private function generate_build_env_variables()
|
||||||
$secrets_hash = $this->generate_secrets_hash($variables);
|
$secrets_hash = $this->generate_secrets_hash($variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->build_args = $variables->map(function ($value, $key) {
|
$env_vars = $this->pull_request_id === 0
|
||||||
$value = escapeshellarg($value);
|
? $this->application->environment_variables()->where('is_buildtime', true)->get()
|
||||||
|
: $this->application->environment_variables_preview()->where('is_buildtime', true)->get();
|
||||||
|
|
||||||
return "--build-arg {$key}={$value}";
|
// Map variables to include is_multiline flag
|
||||||
|
$vars_with_metadata = $variables->map(function ($value, $key) use ($env_vars) {
|
||||||
|
$env = $env_vars->firstWhere('key', $key);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'key' => $key,
|
||||||
|
'value' => $value,
|
||||||
|
'is_multiline' => $env ? $env->is_multiline : false,
|
||||||
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$this->build_args = generateDockerBuildArgs($vars_with_metadata);
|
||||||
|
|
||||||
if ($secrets_hash) {
|
if ($secrets_hash) {
|
||||||
$this->build_args->push("--build-arg COOLIFY_BUILD_SECRETS_HASH={$secrets_hash}");
|
$this->build_args->push("--build-arg COOLIFY_BUILD_SECRETS_HASH={$secrets_hash}");
|
||||||
}
|
}
|
||||||
|
|
@ -2807,14 +2818,17 @@ private function generate_docker_env_flags_for_secrets()
|
||||||
}
|
}
|
||||||
|
|
||||||
$secrets_hash = $this->generate_secrets_hash($variables);
|
$secrets_hash = $this->generate_secrets_hash($variables);
|
||||||
$env_flags = $variables
|
|
||||||
->map(function ($env) {
|
|
||||||
$escaped_value = escapeshellarg($env->real_value);
|
|
||||||
|
|
||||||
return "-e {$env->key}={$escaped_value}";
|
// Map to simple array format for the helper function
|
||||||
})
|
$vars_array = $variables->map(function ($env) {
|
||||||
->implode(' ');
|
return [
|
||||||
|
'key' => $env->key,
|
||||||
|
'value' => $env->real_value,
|
||||||
|
'is_multiline' => $env->is_multiline,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
$env_flags = generateDockerEnvFlags($vars_array);
|
||||||
$env_flags .= " -e COOLIFY_BUILD_SECRETS_HASH={$secrets_hash}";
|
$env_flags .= " -e COOLIFY_BUILD_SECRETS_HASH={$secrets_hash}";
|
||||||
|
|
||||||
return $env_flags;
|
return $env_flags;
|
||||||
|
|
|
||||||
|
|
@ -1119,3 +1119,64 @@ function escapeDollarSign($value)
|
||||||
|
|
||||||
return str_replace($search, $replace, $value);
|
return str_replace($search, $replace, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate Docker build arguments from environment variables collection
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Support\Collection|array $variables Collection of variables with 'key', 'value', and optionally 'is_multiline'
|
||||||
|
* @return \Illuminate\Support\Collection Collection of formatted --build-arg strings
|
||||||
|
*/
|
||||||
|
function generateDockerBuildArgs($variables): \Illuminate\Support\Collection
|
||||||
|
{
|
||||||
|
$variables = collect($variables);
|
||||||
|
|
||||||
|
return $variables->map(function ($var) {
|
||||||
|
$key = is_array($var) ? data_get($var, 'key') : $var->key;
|
||||||
|
$value = is_array($var) ? data_get($var, 'value') : $var->value;
|
||||||
|
$isMultiline = is_array($var) ? data_get($var, 'is_multiline', false) : ($var->is_multiline ?? false);
|
||||||
|
|
||||||
|
if ($isMultiline) {
|
||||||
|
// For multiline variables, strip surrounding quotes and escape for bash
|
||||||
|
$raw_value = trim($value, "'");
|
||||||
|
$escaped_value = str_replace(['\\', '"', '$', '`'], ['\\\\', '\\"', '\\$', '\\`'], $raw_value);
|
||||||
|
|
||||||
|
return "--build-arg {$key}=\"{$escaped_value}\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
// For regular variables, use escapeshellarg for security
|
||||||
|
$value = escapeshellarg($value);
|
||||||
|
|
||||||
|
return "--build-arg {$key}={$value}";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate Docker environment flags from environment variables collection
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Support\Collection|array $variables Collection of variables with 'key', 'value', and optionally 'is_multiline'
|
||||||
|
* @return string Space-separated environment flags
|
||||||
|
*/
|
||||||
|
function generateDockerEnvFlags($variables): string
|
||||||
|
{
|
||||||
|
$variables = collect($variables);
|
||||||
|
|
||||||
|
return $variables
|
||||||
|
->map(function ($var) {
|
||||||
|
$key = is_array($var) ? data_get($var, 'key') : $var->key;
|
||||||
|
$value = is_array($var) ? data_get($var, 'value') : $var->value;
|
||||||
|
$isMultiline = is_array($var) ? data_get($var, 'is_multiline', false) : ($var->is_multiline ?? false);
|
||||||
|
|
||||||
|
if ($isMultiline) {
|
||||||
|
// For multiline variables, strip surrounding quotes and escape for bash
|
||||||
|
$raw_value = trim($value, "'");
|
||||||
|
$escaped_value = str_replace(['\\', '"', '$', '`'], ['\\\\', '\\"', '\\$', '\\`'], $raw_value);
|
||||||
|
|
||||||
|
return "-e {$key}=\"{$escaped_value}\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
$escaped_value = escapeshellarg($value);
|
||||||
|
|
||||||
|
return "-e {$key}={$escaped_value}";
|
||||||
|
})
|
||||||
|
->implode(' ');
|
||||||
|
}
|
||||||
|
|
|
||||||
208
tests/Feature/MultilineEnvironmentVariableTest.php
Normal file
208
tests/Feature/MultilineEnvironmentVariableTest.php
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
test('multiline environment variables are properly escaped for docker build args', function () {
|
||||||
|
$sshKey = '-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||||
|
QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk
|
||||||
|
hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA
|
||||||
|
AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV
|
||||||
|
uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||||
|
-----END OPENSSH PRIVATE KEY-----';
|
||||||
|
|
||||||
|
$variables = [
|
||||||
|
['key' => 'SSH_PRIVATE_KEY', 'value' => "'{$sshKey}'", 'is_multiline' => true],
|
||||||
|
['key' => 'REGULAR_VAR', 'value' => 'simple value', 'is_multiline' => false],
|
||||||
|
];
|
||||||
|
|
||||||
|
$buildArgs = generateDockerBuildArgs($variables);
|
||||||
|
|
||||||
|
// SSH key should use double quotes and have proper escaping
|
||||||
|
$sshArg = $buildArgs->first();
|
||||||
|
expect($sshArg)->toStartWith('--build-arg SSH_PRIVATE_KEY="');
|
||||||
|
expect($sshArg)->toEndWith('"');
|
||||||
|
expect($sshArg)->toContain('BEGIN OPENSSH PRIVATE KEY');
|
||||||
|
expect($sshArg)->not->toContain("'BEGIN"); // Should not have the wrapper single quotes
|
||||||
|
|
||||||
|
// Regular var should use escapeshellarg (single quotes)
|
||||||
|
$regularArg = $buildArgs->last();
|
||||||
|
expect($regularArg)->toBe("--build-arg REGULAR_VAR='simple value'");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('multiline variables with special bash characters are escaped correctly', function () {
|
||||||
|
$valueWithSpecialChars = "line1\nline2 with \"quotes\"\nline3 with \$variables\nline4 with `backticks`";
|
||||||
|
|
||||||
|
$variables = [
|
||||||
|
['key' => 'SPECIAL_VALUE', 'value' => "'{$valueWithSpecialChars}'", 'is_multiline' => true],
|
||||||
|
];
|
||||||
|
|
||||||
|
$buildArgs = generateDockerBuildArgs($variables);
|
||||||
|
$arg = $buildArgs->first();
|
||||||
|
|
||||||
|
// Verify double quotes are escaped
|
||||||
|
expect($arg)->toContain('\\"quotes\\"');
|
||||||
|
// Verify dollar signs are escaped
|
||||||
|
expect($arg)->toContain('\\$variables');
|
||||||
|
// Verify backticks are escaped
|
||||||
|
expect($arg)->toContain('\\`backticks\\`');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('single-line environment variables use escapeshellarg', function () {
|
||||||
|
$variables = [
|
||||||
|
['key' => 'SIMPLE_VAR', 'value' => 'simple value with spaces', 'is_multiline' => false],
|
||||||
|
];
|
||||||
|
|
||||||
|
$buildArgs = generateDockerBuildArgs($variables);
|
||||||
|
$arg = $buildArgs->first();
|
||||||
|
|
||||||
|
// Should use single quotes from escapeshellarg
|
||||||
|
expect($arg)->toBe("--build-arg SIMPLE_VAR='simple value with spaces'");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('multiline certificate with newlines is preserved', function () {
|
||||||
|
$certificate = '-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDXTCCAkWgAwIBAgIJAKL0UG+mRkSvMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||||
|
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||||
|
aWRnaXRzIFB0eSBMdGQwHhcNMTkwOTE3MDUzMzI5WhcNMjkwOTE0MDUzMzI5WjBF
|
||||||
|
-----END CERTIFICATE-----';
|
||||||
|
|
||||||
|
$variables = [
|
||||||
|
['key' => 'TLS_CERT', 'value' => "'{$certificate}'", 'is_multiline' => true],
|
||||||
|
];
|
||||||
|
|
||||||
|
$buildArgs = generateDockerBuildArgs($variables);
|
||||||
|
$arg = $buildArgs->first();
|
||||||
|
|
||||||
|
// Newlines should be preserved in the output
|
||||||
|
expect($arg)->toContain("\n");
|
||||||
|
expect($arg)->toContain('BEGIN CERTIFICATE');
|
||||||
|
expect($arg)->toContain('END CERTIFICATE');
|
||||||
|
expect(substr_count($arg, "\n"))->toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('multiline JSON configuration is properly escaped', function () {
|
||||||
|
$jsonConfig = '{
|
||||||
|
"key": "value",
|
||||||
|
"nested": {
|
||||||
|
"array": [1, 2, 3]
|
||||||
|
}
|
||||||
|
}';
|
||||||
|
|
||||||
|
$variables = [
|
||||||
|
['key' => 'JSON_CONFIG', 'value' => "'{$jsonConfig}'", 'is_multiline' => true],
|
||||||
|
];
|
||||||
|
|
||||||
|
$buildArgs = generateDockerBuildArgs($variables);
|
||||||
|
$arg = $buildArgs->first();
|
||||||
|
|
||||||
|
// All double quotes in JSON should be escaped
|
||||||
|
expect($arg)->toContain('\\"key\\"');
|
||||||
|
expect($arg)->toContain('\\"value\\"');
|
||||||
|
expect($arg)->toContain('\\"nested\\"');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('empty multiline variable is handled correctly', function () {
|
||||||
|
$variables = [
|
||||||
|
['key' => 'EMPTY_VAR', 'value' => "''", 'is_multiline' => true],
|
||||||
|
];
|
||||||
|
|
||||||
|
$buildArgs = generateDockerBuildArgs($variables);
|
||||||
|
$arg = $buildArgs->first();
|
||||||
|
|
||||||
|
expect($arg)->toBe('--build-arg EMPTY_VAR=""');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('multiline variable with only newlines', function () {
|
||||||
|
$onlyNewlines = "\n\n\n";
|
||||||
|
|
||||||
|
$variables = [
|
||||||
|
['key' => 'NEWLINES_ONLY', 'value' => "'{$onlyNewlines}'", 'is_multiline' => true],
|
||||||
|
];
|
||||||
|
|
||||||
|
$buildArgs = generateDockerBuildArgs($variables);
|
||||||
|
$arg = $buildArgs->first();
|
||||||
|
|
||||||
|
expect($arg)->toContain("\n");
|
||||||
|
// Should have 3 newlines preserved
|
||||||
|
expect(substr_count($arg, "\n"))->toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('multiline variable with backslashes is escaped correctly', function () {
|
||||||
|
$valueWithBackslashes = "path\\to\\file\nC:\\Windows\\System32";
|
||||||
|
|
||||||
|
$variables = [
|
||||||
|
['key' => 'PATH_VAR', 'value' => "'{$valueWithBackslashes}'", 'is_multiline' => true],
|
||||||
|
];
|
||||||
|
|
||||||
|
$buildArgs = generateDockerBuildArgs($variables);
|
||||||
|
$arg = $buildArgs->first();
|
||||||
|
|
||||||
|
// Backslashes should be doubled
|
||||||
|
expect($arg)->toContain('path\\\\to\\\\file');
|
||||||
|
expect($arg)->toContain('C:\\\\Windows\\\\System32');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('generateDockerEnvFlags produces correct format', function () {
|
||||||
|
$variables = [
|
||||||
|
['key' => 'NORMAL_VAR', 'value' => 'value', 'is_multiline' => false],
|
||||||
|
['key' => 'MULTILINE_VAR', 'value' => "'line1\nline2'", 'is_multiline' => true],
|
||||||
|
];
|
||||||
|
|
||||||
|
$envFlags = generateDockerEnvFlags($variables);
|
||||||
|
|
||||||
|
expect($envFlags)->toContain('-e NORMAL_VAR=');
|
||||||
|
expect($envFlags)->toContain('-e MULTILINE_VAR="');
|
||||||
|
expect($envFlags)->toContain('line1');
|
||||||
|
expect($envFlags)->toContain('line2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('helper functions work with collection input', function () {
|
||||||
|
$variables = collect([
|
||||||
|
(object) ['key' => 'VAR1', 'value' => 'value1', 'is_multiline' => false],
|
||||||
|
(object) ['key' => 'VAR2', 'value' => "'multiline\nvalue'", 'is_multiline' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$buildArgs = generateDockerBuildArgs($variables);
|
||||||
|
expect($buildArgs)->toHaveCount(2);
|
||||||
|
|
||||||
|
$envFlags = generateDockerEnvFlags($variables);
|
||||||
|
expect($envFlags)->toBeString();
|
||||||
|
expect($envFlags)->toContain('-e VAR1=');
|
||||||
|
expect($envFlags)->toContain('-e VAR2="');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('variables without is_multiline default to false', function () {
|
||||||
|
$variables = [
|
||||||
|
['key' => 'NO_FLAG_VAR', 'value' => 'some value'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$buildArgs = generateDockerBuildArgs($variables);
|
||||||
|
$arg = $buildArgs->first();
|
||||||
|
|
||||||
|
// Should use escapeshellarg (single quotes) since is_multiline defaults to false
|
||||||
|
expect($arg)->toBe("--build-arg NO_FLAG_VAR='some value'");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('real world SSH key example', function () {
|
||||||
|
// Simulate what real_value returns (wrapped in single quotes)
|
||||||
|
$sshKey = "'-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||||
|
QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk
|
||||||
|
hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA
|
||||||
|
AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV
|
||||||
|
uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||||
|
-----END OPENSSH PRIVATE KEY-----'";
|
||||||
|
|
||||||
|
$variables = [
|
||||||
|
['key' => 'KEY', 'value' => $sshKey, 'is_multiline' => true],
|
||||||
|
];
|
||||||
|
|
||||||
|
$buildArgs = generateDockerBuildArgs($variables);
|
||||||
|
$arg = $buildArgs->first();
|
||||||
|
|
||||||
|
// Should produce clean output without wrapper quotes
|
||||||
|
expect($arg)->toStartWith('--build-arg KEY="-----BEGIN OPENSSH PRIVATE KEY-----');
|
||||||
|
expect($arg)->toEndWith('-----END OPENSSH PRIVATE KEY-----"');
|
||||||
|
// Should NOT have the escaped quote sequence that was in the bug
|
||||||
|
expect($arg)->not->toContain("''");
|
||||||
|
expect($arg)->not->toContain("'\\''");
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue