fix(deployment): enhance Dockerfile modification for build-time variables and secrets during deployment in case of docker compose buildpack
This commit is contained in:
@@ -606,6 +606,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"),
|
||||
'hidden' => true,
|
||||
]);
|
||||
|
||||
// Modify Dockerfiles for ARGs and build secrets
|
||||
$this->modify_dockerfiles_for_compose($composeFile);
|
||||
// Build new container to limit downtime.
|
||||
$this->application_deployment_queue->addLogEntry('Pulling & building required images.');
|
||||
|
||||
@@ -632,6 +635,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
} else {
|
||||
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build --pull";
|
||||
}
|
||||
|
||||
if (! $this->application->settings->use_build_secrets && $this->build_args instanceof \Illuminate\Support\Collection && $this->build_args->isNotEmpty()) {
|
||||
$build_args_string = $this->build_args->implode(' ');
|
||||
$command .= " {$build_args_string}";
|
||||
$this->application_deployment_queue->addLogEntry('Adding build arguments to Docker Compose build command.');
|
||||
}
|
||||
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
||||
);
|
||||
@@ -2830,8 +2840,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
|
||||
// Get environment variables for secrets
|
||||
$variables = $this->pull_request_id === 0
|
||||
? $this->application->environment_variables()->where('key', 'not like', 'NIXPACKS_%')->get()
|
||||
: $this->application->environment_variables_preview()->where('key', 'not like', 'NIXPACKS_%')->get();
|
||||
? $this->application->environment_variables()->where('key', 'not like', 'NIXPACKS_%')->where('is_buildtime', true)->get()
|
||||
: $this->application->environment_variables_preview()->where('key', 'not like', 'NIXPACKS_%')->where('is_buildtime', true)->get();
|
||||
|
||||
if ($variables->isEmpty()) {
|
||||
return;
|
||||
@@ -2868,6 +2878,164 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
}
|
||||
}
|
||||
|
||||
private function modify_dockerfiles_for_compose($composeFile)
|
||||
{
|
||||
if ($this->application->build_pack !== 'dockercompose') {
|
||||
return;
|
||||
}
|
||||
|
||||
$variables = $this->pull_request_id === 0
|
||||
? $this->application->environment_variables()
|
||||
->where('key', 'not like', 'NIXPACKS_%')
|
||||
->where('is_buildtime', true)
|
||||
->get()
|
||||
: $this->application->environment_variables_preview()
|
||||
->where('key', 'not like', 'NIXPACKS_%')
|
||||
->where('is_buildtime', true)
|
||||
->get();
|
||||
|
||||
if ($variables->isEmpty()) {
|
||||
$this->application_deployment_queue->addLogEntry('No build-time variables to add to Dockerfiles.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$services = data_get($composeFile, 'services', []);
|
||||
|
||||
foreach ($services as $serviceName => $service) {
|
||||
if (! isset($service['build'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$context = '.';
|
||||
$dockerfile = 'Dockerfile';
|
||||
|
||||
if (is_string($service['build'])) {
|
||||
$context = $service['build'];
|
||||
} elseif (is_array($service['build'])) {
|
||||
$context = data_get($service['build'], 'context', '.');
|
||||
$dockerfile = data_get($service['build'], 'dockerfile', 'Dockerfile');
|
||||
}
|
||||
|
||||
$dockerfilePath = rtrim($context, '/').'/'.ltrim($dockerfile, '/');
|
||||
if (str_starts_with($dockerfilePath, './')) {
|
||||
$dockerfilePath = substr($dockerfilePath, 2);
|
||||
}
|
||||
if (str_starts_with($dockerfilePath, '/')) {
|
||||
$dockerfilePath = substr($dockerfilePath, 1);
|
||||
}
|
||||
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "test -f {$this->workdir}/{$dockerfilePath} && echo 'exists' || echo 'not found'"),
|
||||
'hidden' => true,
|
||||
'save' => 'dockerfile_check_'.$serviceName,
|
||||
]);
|
||||
|
||||
if (str($this->saved_outputs->get('dockerfile_check_'.$serviceName))->trim()->toString() !== 'exists') {
|
||||
$this->application_deployment_queue->addLogEntry("Dockerfile not found for service {$serviceName} at {$dockerfilePath}, skipping ARG injection.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/{$dockerfilePath}"),
|
||||
'hidden' => true,
|
||||
'save' => 'dockerfile_content_'.$serviceName,
|
||||
]);
|
||||
|
||||
$dockerfileContent = $this->saved_outputs->get('dockerfile_content_'.$serviceName);
|
||||
if (! $dockerfileContent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dockerfile_lines = collect(str($dockerfileContent)->trim()->explode("\n"));
|
||||
|
||||
$fromIndices = [];
|
||||
$dockerfile_lines->each(function ($line, $index) use (&$fromIndices) {
|
||||
if (str($line)->trim()->startsWith('FROM')) {
|
||||
$fromIndices[] = $index;
|
||||
}
|
||||
});
|
||||
|
||||
if (empty($fromIndices)) {
|
||||
$this->application_deployment_queue->addLogEntry("No FROM instruction found in Dockerfile for service {$serviceName}, skipping.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$isMultiStage = count($fromIndices) > 1;
|
||||
|
||||
$argsToAdd = collect([]);
|
||||
foreach ($variables as $env) {
|
||||
$argsToAdd->push("ARG {$env->key}");
|
||||
}
|
||||
|
||||
ray($argsToAdd);
|
||||
if ($argsToAdd->isEmpty()) {
|
||||
$this->application_deployment_queue->addLogEntry("Service {$serviceName}: No build-time variables to add.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$totalAdded = 0;
|
||||
$offset = 0;
|
||||
|
||||
foreach ($fromIndices as $stageIndex => $fromIndex) {
|
||||
$adjustedIndex = $fromIndex + $offset;
|
||||
|
||||
$stageStart = $adjustedIndex + 1;
|
||||
$stageEnd = isset($fromIndices[$stageIndex + 1])
|
||||
? $fromIndices[$stageIndex + 1] + $offset
|
||||
: $dockerfile_lines->count();
|
||||
|
||||
$existingStageArgs = collect([]);
|
||||
for ($i = $stageStart; $i < $stageEnd; $i++) {
|
||||
$line = $dockerfile_lines->get($i);
|
||||
if (! $line || ! str($line)->trim()->startsWith('ARG')) {
|
||||
break;
|
||||
}
|
||||
$parts = explode(' ', trim($line), 2);
|
||||
if (count($parts) >= 2) {
|
||||
$argPart = $parts[1];
|
||||
$keyValue = explode('=', $argPart, 2);
|
||||
$existingStageArgs->push($keyValue[0]);
|
||||
}
|
||||
}
|
||||
|
||||
$stageArgsToAdd = $argsToAdd->filter(function ($arg) use ($existingStageArgs) {
|
||||
$key = str($arg)->after('ARG ')->trim()->toString();
|
||||
|
||||
return ! $existingStageArgs->contains($key);
|
||||
});
|
||||
|
||||
if ($stageArgsToAdd->isNotEmpty()) {
|
||||
$dockerfile_lines->splice($adjustedIndex + 1, 0, $stageArgsToAdd->toArray());
|
||||
$totalAdded += $stageArgsToAdd->count();
|
||||
$offset += $stageArgsToAdd->count();
|
||||
}
|
||||
}
|
||||
|
||||
if ($totalAdded > 0) {
|
||||
$dockerfile_base64 = base64_encode($dockerfile_lines->implode("\n"));
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d | tee {$this->workdir}/{$dockerfilePath} > /dev/null"),
|
||||
'hidden' => true,
|
||||
]);
|
||||
|
||||
$stageInfo = $isMultiStage ? ' (multi-stage build, added to '.count($fromIndices).' stages)' : '';
|
||||
$this->application_deployment_queue->addLogEntry("Added {$totalAdded} ARG declarations to Dockerfile for service {$serviceName}{$stageInfo}.");
|
||||
} else {
|
||||
$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)) {
|
||||
$fullDockerfilePath = "{$this->workdir}/{$dockerfilePath}";
|
||||
$this->modify_dockerfile_for_secrets($fullDockerfilePath);
|
||||
$this->application_deployment_queue->addLogEntry("Modified Dockerfile for service {$serviceName} to use build secrets.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function add_build_secrets_to_compose($composeFile)
|
||||
{
|
||||
// Get environment variables for secrets
|
||||
|
Reference in New Issue
Block a user