fix(deploy): split BuildKit and secrets detection (#8565)
This commit is contained in:
commit
021605dbf0
2 changed files with 114 additions and 240 deletions
|
|
@ -171,6 +171,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
|
||||||
private bool $dockerBuildkitSupported = false;
|
private bool $dockerBuildkitSupported = false;
|
||||||
|
|
||||||
|
private bool $dockerSecretsSupported = false;
|
||||||
|
|
||||||
private bool $skip_build = false;
|
private bool $skip_build = false;
|
||||||
|
|
||||||
private Collection|string $build_secrets;
|
private Collection|string $build_secrets;
|
||||||
|
|
@ -381,13 +383,6 @@ public function handle(): void
|
||||||
|
|
||||||
private function detectBuildKitCapabilities(): void
|
private function detectBuildKitCapabilities(): void
|
||||||
{
|
{
|
||||||
// If build secrets are not enabled, skip detection and use traditional args
|
|
||||||
if (! $this->application->settings->use_build_secrets) {
|
|
||||||
$this->dockerBuildkitSupported = false;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$serverToCheck = $this->use_build_server ? $this->build_server : $this->server;
|
$serverToCheck = $this->use_build_server ? $this->build_server : $this->server;
|
||||||
$serverName = $this->use_build_server ? "build server ({$serverToCheck->name})" : "deployment server ({$serverToCheck->name})";
|
$serverName = $this->use_build_server ? "build server ({$serverToCheck->name})" : "deployment server ({$serverToCheck->name})";
|
||||||
|
|
||||||
|
|
@ -403,53 +398,55 @@ private function detectBuildKitCapabilities(): void
|
||||||
|
|
||||||
if ($majorVersion < 18 || ($majorVersion == 18 && $minorVersion < 9)) {
|
if ($majorVersion < 18 || ($majorVersion == 18 && $minorVersion < 9)) {
|
||||||
$this->dockerBuildkitSupported = false;
|
$this->dockerBuildkitSupported = false;
|
||||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not support BuildKit (requires 18.09+). Build secrets feature disabled.");
|
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not support BuildKit (requires 18.09+).");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$buildkitEnabled = instant_remote_process(
|
// Check buildx availability (always installed by Coolify on Docker 24.0+)
|
||||||
|
$buildxAvailable = instant_remote_process(
|
||||||
["docker buildx version >/dev/null 2>&1 && echo 'available' || echo 'not-available'"],
|
["docker buildx version >/dev/null 2>&1 && echo 'available' || echo 'not-available'"],
|
||||||
$serverToCheck
|
$serverToCheck
|
||||||
);
|
);
|
||||||
|
|
||||||
if (trim($buildkitEnabled) !== 'available') {
|
if (trim($buildxAvailable) === 'available') {
|
||||||
|
$this->dockerBuildkitSupported = true;
|
||||||
|
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit and Buildx detected on {$serverName}.");
|
||||||
|
} else {
|
||||||
|
// Fallback: test DOCKER_BUILDKIT=1 support via --progress flag
|
||||||
$buildkitTest = instant_remote_process(
|
$buildkitTest = instant_remote_process(
|
||||||
["DOCKER_BUILDKIT=1 docker build --help 2>&1 | grep -q 'secret' && echo 'supported' || echo 'not-supported'"],
|
["DOCKER_BUILDKIT=1 docker build --help 2>&1 | grep -q '\\-\\-progress' && echo 'supported' || echo 'not-supported'"],
|
||||||
$serverToCheck
|
$serverToCheck
|
||||||
);
|
);
|
||||||
|
|
||||||
if (trim($buildkitTest) === 'supported') {
|
if (trim($buildkitTest) === 'supported') {
|
||||||
$this->dockerBuildkitSupported = true;
|
$this->dockerBuildkitSupported = true;
|
||||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit secrets support detected on {$serverName}.");
|
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit support detected on {$serverName}.");
|
||||||
$this->application_deployment_queue->addLogEntry('Build secrets are enabled and will be used for enhanced security.');
|
|
||||||
} else {
|
} else {
|
||||||
$this->dockerBuildkitSupported = false;
|
$this->dockerBuildkitSupported = false;
|
||||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not have BuildKit secrets support.");
|
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not support BuildKit. Build output progress will be limited.");
|
||||||
$this->application_deployment_queue->addLogEntry('Build secrets feature is enabled but not supported. Using traditional build arguments.');
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// Buildx is available, which means BuildKit is available
|
|
||||||
// Now specifically test for secrets support
|
// If build secrets are enabled and BuildKit is available, verify --secret flag support
|
||||||
|
if ($this->application->settings->use_build_secrets && $this->dockerBuildkitSupported) {
|
||||||
$secretsTest = instant_remote_process(
|
$secretsTest = instant_remote_process(
|
||||||
["docker build --help 2>&1 | grep -q 'secret' && echo 'supported' || echo 'not-supported'"],
|
["docker build --help 2>&1 | grep -q 'secret' && echo 'supported' || echo 'not-supported'"],
|
||||||
$serverToCheck
|
$serverToCheck
|
||||||
);
|
);
|
||||||
|
|
||||||
if (trim($secretsTest) === 'supported') {
|
if (trim($secretsTest) === 'supported') {
|
||||||
$this->dockerBuildkitSupported = true;
|
$this->dockerSecretsSupported = true;
|
||||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit and Buildx detected on {$serverName}.");
|
|
||||||
$this->application_deployment_queue->addLogEntry('Build secrets are enabled and will be used for enhanced security.');
|
$this->application_deployment_queue->addLogEntry('Build secrets are enabled and will be used for enhanced security.');
|
||||||
} else {
|
} else {
|
||||||
$this->dockerBuildkitSupported = false;
|
$this->dockerSecretsSupported = false;
|
||||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with Buildx on {$serverName}, but secrets not supported.");
|
$this->application_deployment_queue->addLogEntry("Docker on {$serverName} does not support build secrets. Using traditional build arguments.");
|
||||||
$this->application_deployment_queue->addLogEntry('Build secrets feature is enabled but not supported. Using traditional build arguments.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->dockerBuildkitSupported = false;
|
$this->dockerBuildkitSupported = false;
|
||||||
|
$this->dockerSecretsSupported = false;
|
||||||
$this->application_deployment_queue->addLogEntry("Could not detect BuildKit capabilities on {$serverName}: {$e->getMessage()}");
|
$this->application_deployment_queue->addLogEntry("Could not detect BuildKit capabilities on {$serverName}: {$e->getMessage()}");
|
||||||
$this->application_deployment_queue->addLogEntry('Build secrets feature is enabled but detection failed. Using traditional build arguments.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -632,7 +629,7 @@ private function deploy_docker_compose_buildpack()
|
||||||
|
|
||||||
// For raw compose, we cannot automatically add secrets configuration
|
// For raw compose, we cannot automatically add secrets configuration
|
||||||
// User must define it manually in their docker-compose file
|
// User must define it manually in their docker-compose file
|
||||||
if ($this->application->settings->use_build_secrets && $this->dockerBuildkitSupported && ! empty($this->build_secrets)) {
|
if ($this->dockerSecretsSupported && ! empty($this->build_secrets)) {
|
||||||
$this->application_deployment_queue->addLogEntry('Build secrets are configured. Ensure your docker-compose file includes build.secrets configuration for services that need them.');
|
$this->application_deployment_queue->addLogEntry('Build secrets are configured. Ensure your docker-compose file includes build.secrets configuration for services that need them.');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -653,7 +650,7 @@ private function deploy_docker_compose_buildpack()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add build secrets to compose file if enabled and BuildKit is supported
|
// Add build secrets to compose file if enabled and BuildKit is supported
|
||||||
if ($this->application->settings->use_build_secrets && $this->dockerBuildkitSupported && ! empty($this->build_secrets)) {
|
if ($this->dockerSecretsSupported && ! empty($this->build_secrets)) {
|
||||||
$composeFile = $this->add_build_secrets_to_compose($composeFile);
|
$composeFile = $this->add_build_secrets_to_compose($composeFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2817,7 +2814,11 @@ private function build_static_image()
|
||||||
$nginx_config = base64_encode(defaultNginxConfiguration());
|
$nginx_config = base64_encode(defaultNginxConfiguration());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile --progress plain -t {$this->production_image_name} {$this->workdir}";
|
if ($this->dockerBuildkitSupported) {
|
||||||
|
$build_command = "DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile --progress plain -t {$this->production_image_name} {$this->workdir}";
|
||||||
|
} else {
|
||||||
|
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile -t {$this->production_image_name} {$this->workdir}";
|
||||||
|
}
|
||||||
$base64_build_command = base64_encode($build_command);
|
$base64_build_command = base64_encode($build_command);
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
|
|
@ -2857,21 +2858,19 @@ private function wrap_build_command_with_env_export(string $build_command): stri
|
||||||
private function build_image()
|
private function build_image()
|
||||||
{
|
{
|
||||||
// Add Coolify related variables to the build args/secrets
|
// Add Coolify related variables to the build args/secrets
|
||||||
if ($this->dockerBuildkitSupported) {
|
if (! $this->dockerBuildkitSupported) {
|
||||||
// Coolify variables are already included in the secrets from generate_build_env_variables
|
|
||||||
// build_secrets is already a string at this point
|
|
||||||
} else {
|
|
||||||
// Traditional build args approach - generate COOLIFY_ variables locally
|
// Traditional build args approach - generate COOLIFY_ variables locally
|
||||||
// Generate COOLIFY_ variables locally for build args
|
|
||||||
$coolify_envs = $this->generate_coolify_env_variables(forBuildTime: true);
|
$coolify_envs = $this->generate_coolify_env_variables(forBuildTime: true);
|
||||||
$coolify_envs->each(function ($value, $key) {
|
$coolify_envs->each(function ($value, $key) {
|
||||||
$this->build_args->push("--build-arg '{$key}'");
|
$this->build_args->push("--build-arg '{$key}'");
|
||||||
});
|
});
|
||||||
$this->build_args = $this->build_args instanceof \Illuminate\Support\Collection
|
|
||||||
? $this->build_args->implode(' ')
|
|
||||||
: (string) $this->build_args;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always convert build_args Collection to string for command interpolation
|
||||||
|
$this->build_args = $this->build_args instanceof \Illuminate\Support\Collection
|
||||||
|
? $this->build_args->implode(' ')
|
||||||
|
: (string) $this->build_args;
|
||||||
|
|
||||||
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
||||||
if ($this->disableBuildCache) {
|
if ($this->disableBuildCache) {
|
||||||
$this->application_deployment_queue->addLogEntry('Docker build cache is disabled. It will not be used during the build process.');
|
$this->application_deployment_queue->addLogEntry('Docker build cache is disabled. It will not be used during the build process.');
|
||||||
|
|
@ -2899,7 +2898,7 @@ private function build_image()
|
||||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
|
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
]);
|
]);
|
||||||
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
if ($this->dockerSecretsSupported) {
|
||||||
// Modify the nixpacks Dockerfile to use build secrets
|
// Modify the nixpacks Dockerfile to use build secrets
|
||||||
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
||||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||||
|
|
@ -2907,9 +2906,8 @@ private function build_image()
|
||||||
} elseif ($this->dockerBuildkitSupported) {
|
} elseif ($this->dockerBuildkitSupported) {
|
||||||
// BuildKit without secrets
|
// BuildKit without secrets
|
||||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
|
||||||
ray($build_command);
|
|
||||||
} else {
|
} else {
|
||||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
|
|
@ -2919,18 +2917,16 @@ private function build_image()
|
||||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
|
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
]);
|
]);
|
||||||
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
if ($this->dockerSecretsSupported) {
|
||||||
// Modify the nixpacks Dockerfile to use build secrets
|
// Modify the nixpacks Dockerfile to use build secrets
|
||||||
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
||||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->build_image_name} {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->build_image_name} {$this->workdir}");
|
||||||
} elseif ($this->dockerBuildkitSupported) {
|
} elseif ($this->dockerBuildkitSupported) {
|
||||||
// BuildKit without secrets
|
// BuildKit without secrets
|
||||||
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
|
||||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
|
||||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
|
|
||||||
} else {
|
} else {
|
||||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2952,7 +2948,7 @@ private function build_image()
|
||||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm '.self::NIXPACKS_PLAN_PATH), 'hidden' => true]);
|
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm '.self::NIXPACKS_PLAN_PATH), 'hidden' => true]);
|
||||||
} else {
|
} else {
|
||||||
// Dockerfile buildpack
|
// Dockerfile buildpack
|
||||||
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
if ($this->dockerSecretsSupported) {
|
||||||
// Modify the Dockerfile to use build secrets
|
// Modify the Dockerfile to use build secrets
|
||||||
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
|
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
|
||||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||||
|
|
@ -2963,19 +2959,17 @@ private function build_image()
|
||||||
}
|
}
|
||||||
} elseif ($this->dockerBuildkitSupported) {
|
} elseif ($this->dockerBuildkitSupported) {
|
||||||
// BuildKit without secrets
|
// BuildKit without secrets
|
||||||
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
|
|
||||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
|
||||||
if ($this->force_rebuild) {
|
if ($this->force_rebuild) {
|
||||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}");
|
||||||
} else {
|
} else {
|
||||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Traditional build with args
|
// Traditional build with args
|
||||||
if ($this->force_rebuild) {
|
if ($this->force_rebuild) {
|
||||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t $this->build_image_name {$this->workdir}");
|
||||||
} else {
|
} else {
|
||||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t $this->build_image_name {$this->workdir}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$base64_build_command = base64_encode($build_command);
|
$base64_build_command = base64_encode($build_command);
|
||||||
|
|
@ -3010,7 +3004,11 @@ private function build_image()
|
||||||
$nginx_config = base64_encode(defaultNginxConfiguration());
|
$nginx_config = base64_encode(defaultNginxConfiguration());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
if ($this->dockerBuildkitSupported) {
|
||||||
|
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
||||||
|
} else {
|
||||||
|
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} -t {$this->production_image_name} {$this->workdir}");
|
||||||
|
}
|
||||||
$base64_build_command = base64_encode($build_command);
|
$base64_build_command = base64_encode($build_command);
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
|
|
@ -3035,7 +3033,7 @@ private function build_image()
|
||||||
} else {
|
} else {
|
||||||
// Pure Dockerfile based deployment
|
// Pure Dockerfile based deployment
|
||||||
if ($this->application->dockerfile) {
|
if ($this->application->dockerfile) {
|
||||||
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
if ($this->dockerSecretsSupported) {
|
||||||
// Modify the Dockerfile to use build secrets
|
// Modify the Dockerfile to use build secrets
|
||||||
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
|
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
|
||||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||||
|
|
@ -3044,12 +3042,19 @@ private function build_image()
|
||||||
} else {
|
} else {
|
||||||
$build_command = "DOCKER_BUILDKIT=1 docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}";
|
$build_command = "DOCKER_BUILDKIT=1 docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}";
|
||||||
}
|
}
|
||||||
} else {
|
} elseif ($this->dockerBuildkitSupported) {
|
||||||
// Traditional build with args
|
// BuildKit without secrets
|
||||||
if ($this->force_rebuild) {
|
if ($this->force_rebuild) {
|
||||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||||
} else {
|
} else {
|
||||||
$build_command = $this->wrap_build_command_with_env_export("docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Traditional build with args (no --progress for legacy builder compatibility)
|
||||||
|
if ($this->force_rebuild) {
|
||||||
|
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t {$this->production_image_name} {$this->workdir}");
|
||||||
|
} else {
|
||||||
|
$build_command = $this->wrap_build_command_with_env_export("docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t {$this->production_image_name} {$this->workdir}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$base64_build_command = base64_encode($build_command);
|
$base64_build_command = base64_encode($build_command);
|
||||||
|
|
@ -3079,18 +3084,16 @@ private function build_image()
|
||||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
|
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
]);
|
]);
|
||||||
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
if ($this->dockerSecretsSupported) {
|
||||||
// Modify the nixpacks Dockerfile to use build secrets
|
// Modify the nixpacks Dockerfile to use build secrets
|
||||||
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
||||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
||||||
} elseif ($this->dockerBuildkitSupported) {
|
} elseif ($this->dockerBuildkitSupported) {
|
||||||
// BuildKit without secrets
|
// BuildKit without secrets
|
||||||
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
|
||||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
|
||||||
} else {
|
} else {
|
||||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
|
|
@ -3100,18 +3103,16 @@ private function build_image()
|
||||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
|
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
]);
|
]);
|
||||||
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
if ($this->dockerSecretsSupported) {
|
||||||
// Modify the nixpacks Dockerfile to use build secrets
|
// Modify the nixpacks Dockerfile to use build secrets
|
||||||
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
||||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
||||||
} elseif ($this->dockerBuildkitSupported) {
|
} elseif ($this->dockerBuildkitSupported) {
|
||||||
// BuildKit without secrets
|
// BuildKit without secrets
|
||||||
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
|
||||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
|
||||||
} else {
|
} else {
|
||||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$base64_build_command = base64_encode($build_command);
|
$base64_build_command = base64_encode($build_command);
|
||||||
|
|
@ -3132,7 +3133,7 @@ private function build_image()
|
||||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm '.self::NIXPACKS_PLAN_PATH), 'hidden' => true]);
|
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm '.self::NIXPACKS_PLAN_PATH), 'hidden' => true]);
|
||||||
} else {
|
} else {
|
||||||
// Dockerfile buildpack
|
// Dockerfile buildpack
|
||||||
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
if ($this->dockerSecretsSupported) {
|
||||||
// Modify the Dockerfile to use build secrets
|
// Modify the Dockerfile to use build secrets
|
||||||
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
|
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
|
||||||
// Use BuildKit with secrets
|
// Use BuildKit with secrets
|
||||||
|
|
@ -3144,19 +3145,17 @@ private function build_image()
|
||||||
}
|
}
|
||||||
} elseif ($this->dockerBuildkitSupported) {
|
} elseif ($this->dockerBuildkitSupported) {
|
||||||
// BuildKit without secrets
|
// BuildKit without secrets
|
||||||
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
|
|
||||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
|
||||||
if ($this->force_rebuild) {
|
if ($this->force_rebuild) {
|
||||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||||
} else {
|
} else {
|
||||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Traditional build with args
|
// Traditional build with args
|
||||||
if ($this->force_rebuild) {
|
if ($this->force_rebuild) {
|
||||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t {$this->production_image_name} {$this->workdir}");
|
||||||
} else {
|
} else {
|
||||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t {$this->production_image_name} {$this->workdir}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$base64_build_command = base64_encode($build_command);
|
$base64_build_command = base64_encode($build_command);
|
||||||
|
|
@ -3332,7 +3331,7 @@ private function generate_build_env_variables()
|
||||||
$this->analyzeBuildTimeVariables($variables);
|
$this->analyzeBuildTimeVariables($variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
if ($this->dockerSecretsSupported) {
|
||||||
$this->generate_build_secrets($variables);
|
$this->generate_build_secrets($variables);
|
||||||
$this->build_args = '';
|
$this->build_args = '';
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -3819,7 +3818,7 @@ private function modify_dockerfiles_for_compose($composeFile)
|
||||||
$this->application_deployment_queue->addLogEntry("Service {$serviceName}: All required ARG declarations already exist.");
|
$this->application_deployment_queue->addLogEntry("Service {$serviceName}: All required ARG declarations already exist.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->application->settings->use_build_secrets && $this->dockerBuildkitSupported && ! empty($this->build_secrets)) {
|
if ($this->dockerSecretsSupported && ! empty($this->build_secrets)) {
|
||||||
$fullDockerfilePath = "{$this->workdir}/{$dockerfilePath}";
|
$fullDockerfilePath = "{$this->workdir}/{$dockerfilePath}";
|
||||||
$this->modify_dockerfile_for_secrets($fullDockerfilePath);
|
$this->modify_dockerfile_for_secrets($fullDockerfilePath);
|
||||||
$this->application_deployment_queue->addLogEntry("Modified Dockerfile for service {$serviceName} to use build secrets.");
|
$this->application_deployment_queue->addLogEntry("Modified Dockerfile for service {$serviceName} to use build secrets.");
|
||||||
|
|
|
||||||
|
|
@ -1,144 +1,59 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
test('multiline environment variables are properly escaped for docker build args', function () {
|
test('generateDockerBuildArgs returns only keys without values', function () {
|
||||||
$sshKey = '-----BEGIN OPENSSH PRIVATE KEY-----
|
|
||||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
|
||||||
QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk
|
|
||||||
hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA
|
|
||||||
AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV
|
|
||||||
uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|
||||||
-----END OPENSSH PRIVATE KEY-----';
|
|
||||||
|
|
||||||
$variables = [
|
$variables = [
|
||||||
['key' => 'SSH_PRIVATE_KEY', 'value' => "'{$sshKey}'", 'is_multiline' => true],
|
['key' => 'SSH_PRIVATE_KEY', 'value' => "'some-ssh-key'", 'is_multiline' => true],
|
||||||
['key' => 'REGULAR_VAR', 'value' => 'simple value', 'is_multiline' => false],
|
['key' => 'REGULAR_VAR', 'value' => 'simple value', 'is_multiline' => false],
|
||||||
];
|
];
|
||||||
|
|
||||||
$buildArgs = generateDockerBuildArgs($variables);
|
$buildArgs = generateDockerBuildArgs($variables);
|
||||||
|
|
||||||
// SSH key should use double quotes and have proper escaping
|
// Docker gets values from the environment, so only keys should be in build args
|
||||||
$sshArg = $buildArgs->first();
|
expect($buildArgs->first())->toBe('--build-arg SSH_PRIVATE_KEY');
|
||||||
expect($sshArg)->toStartWith('--build-arg SSH_PRIVATE_KEY="');
|
expect($buildArgs->last())->toBe('--build-arg REGULAR_VAR');
|
||||||
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 () {
|
test('generateDockerBuildArgs works with collection of objects', function () {
|
||||||
$valueWithSpecialChars = "line1\nline2 with \"quotes\"\nline3 with \$variables\nline4 with `backticks`";
|
$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);
|
||||||
|
expect($buildArgs->values()->toArray())->toBe([
|
||||||
|
'--build-arg VAR1',
|
||||||
|
'--build-arg VAR2',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('generateDockerBuildArgs collection can be imploded into valid command string', function () {
|
||||||
$variables = [
|
$variables = [
|
||||||
['key' => 'SPECIAL_VALUE', 'value' => "'{$valueWithSpecialChars}'", 'is_multiline' => true],
|
['key' => 'COOLIFY_URL', 'value' => 'http://example.com', 'is_multiline' => false],
|
||||||
|
['key' => 'COOLIFY_BRANCH', 'value' => 'main', 'is_multiline' => false],
|
||||||
|
];
|
||||||
|
|
||||||
|
$buildArgs = generateDockerBuildArgs($variables);
|
||||||
|
|
||||||
|
// The collection must be imploded to a string for command interpolation
|
||||||
|
// This was the bug: Collection was interpolated as JSON instead of a space-separated string
|
||||||
|
$argsString = $buildArgs->implode(' ');
|
||||||
|
expect($argsString)->toBe('--build-arg COOLIFY_URL --build-arg COOLIFY_BRANCH');
|
||||||
|
|
||||||
|
// Verify it does NOT produce JSON when cast to string
|
||||||
|
expect($argsString)->not->toContain('{');
|
||||||
|
expect($argsString)->not->toContain('}');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('generateDockerBuildArgs handles variables without is_multiline', function () {
|
||||||
|
$variables = [
|
||||||
|
['key' => 'NO_FLAG_VAR', 'value' => 'some value'],
|
||||||
];
|
];
|
||||||
|
|
||||||
$buildArgs = generateDockerBuildArgs($variables);
|
$buildArgs = generateDockerBuildArgs($variables);
|
||||||
$arg = $buildArgs->first();
|
$arg = $buildArgs->first();
|
||||||
|
|
||||||
// Verify double quotes are escaped
|
expect($arg)->toBe('--build-arg NO_FLAG_VAR');
|
||||||
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 () {
|
test('generateDockerEnvFlags produces correct format', function () {
|
||||||
|
|
@ -155,54 +70,14 @@
|
||||||
expect($envFlags)->toContain('line2');
|
expect($envFlags)->toContain('line2');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('helper functions work with collection input', function () {
|
test('generateDockerEnvFlags works with collection input', function () {
|
||||||
$variables = collect([
|
$variables = collect([
|
||||||
(object) ['key' => 'VAR1', 'value' => 'value1', 'is_multiline' => false],
|
(object) ['key' => 'VAR1', 'value' => 'value1', 'is_multiline' => false],
|
||||||
(object) ['key' => 'VAR2', 'value' => "'multiline\nvalue'", 'is_multiline' => true],
|
(object) ['key' => 'VAR2', 'value' => "'multiline\nvalue'", 'is_multiline' => true],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$buildArgs = generateDockerBuildArgs($variables);
|
|
||||||
expect($buildArgs)->toHaveCount(2);
|
|
||||||
|
|
||||||
$envFlags = generateDockerEnvFlags($variables);
|
$envFlags = generateDockerEnvFlags($variables);
|
||||||
expect($envFlags)->toBeString();
|
expect($envFlags)->toBeString();
|
||||||
expect($envFlags)->toContain('-e VAR1=');
|
expect($envFlags)->toContain('-e VAR1=');
|
||||||
expect($envFlags)->toContain('-e VAR2="');
|
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