diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 43f114bcf..9919a8054 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -650,7 +650,7 @@ public function create_private_deploy_key_application(Request $request) 'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'], 'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'], 'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'], - 'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'railpack', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'], + 'build_pack' => ['type' => 'string', 'enum' => ['dockerfile'], 'description' => 'The build pack type.'], 'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'], 'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'], 'name' => ['type' => 'string', 'description' => 'The application name.'], diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 663499cee..591159c5f 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -181,6 +181,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue private bool $dockerBuildkitSupported = false; + private bool $dockerBuildxAvailable = false; + private bool $dockerSecretsSupported = false; private bool $skip_build = false; @@ -421,6 +423,7 @@ private function detectBuildKitCapabilities(): void if ($majorVersion < 18 || ($majorVersion == 18 && $minorVersion < 9)) { $this->dockerBuildkitSupported = false; + $this->dockerBuildxAvailable = false; $this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not support BuildKit (requires 18.09+)."); return; @@ -434,8 +437,11 @@ private function detectBuildKitCapabilities(): void if (trim($buildxAvailable) === 'available') { $this->dockerBuildkitSupported = true; + $this->dockerBuildxAvailable = true; $this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit and Buildx detected on {$serverName}."); } else { + $this->dockerBuildxAvailable = false; + // Fallback: test DOCKER_BUILDKIT=1 support via --progress flag $buildkitTest = instant_remote_process( ["DOCKER_BUILDKIT=1 docker build --help 2>&1 | grep -q '\\-\\-progress' && echo 'supported' || echo 'not-supported'"], @@ -468,6 +474,7 @@ private function detectBuildKitCapabilities(): void } } catch (Exception $e) { $this->dockerBuildkitSupported = false; + $this->dockerBuildxAvailable = false; $this->dockerSecretsSupported = false; $this->application_deployment_queue->addLogEntry("Could not detect BuildKit capabilities on {$serverName}: {$e->getMessage()}"); } @@ -2760,8 +2767,19 @@ private function railpack_prepare_command(?string $configFilePath = null): strin return $prepare_command; } + private function ensure_docker_buildx_available_for_railpack(): void + { + if ($this->dockerBuildxAvailable) { + return; + } + + throw new DeploymentException('Railpack deployments require the Docker buildx CLI plugin on the build server. Install or enable docker buildx and retry the deployment.'); + } + private function build_railpack_image(): void { + $this->ensure_docker_buildx_available_for_railpack(); + $railpackVariables = $this->generate_railpack_env_variables(); $railpackConfigPath = $this->generate_railpack_config_file(); diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index dcbdad253..bfb02a470 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -371,7 +371,9 @@ private function set_environment_variables(?string $environment_variable = null) protected function key(): Attribute { return Attribute::make( - set: fn (string $value) => ValidationPatterns::validatedEnvironmentVariableKey($value), + set: fn (string $value) => ValidationPatterns::validatedEnvironmentVariableKey( + ValidationPatterns::normalizeEnvironmentVariableKey($value) + ), ); } diff --git a/openapi.json b/openapi.json index 1e9fc4170..25aada1e1 100644 --- a/openapi.json +++ b/openapi.json @@ -1451,11 +1451,7 @@ "build_pack": { "type": "string", "enum": [ - "nixpacks", - "railpack", - "static", - "dockerfile", - "dockercompose" + "dockerfile" ], "description": "The build pack type." }, diff --git a/openapi.yaml b/openapi.yaml index 3e652fa7b..4597b06f7 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -935,7 +935,7 @@ paths: description: 'The Dockerfile content.' build_pack: type: string - enum: [nixpacks, railpack, static, dockerfile, dockercompose] + enum: [dockerfile] description: 'The build pack type.' ports_exposes: type: string diff --git a/tests/Unit/ApplicationDeploymentRailpackConfigTest.php b/tests/Unit/ApplicationDeploymentRailpackConfigTest.php index 15bee488c..e1449a59e 100644 --- a/tests/Unit/ApplicationDeploymentRailpackConfigTest.php +++ b/tests/Unit/ApplicationDeploymentRailpackConfigTest.php @@ -205,6 +205,17 @@ function invokeRailpackMethod(object $job, ReflectionClass $reflection, string $ expect($command)->not->toContain('RAILPACK_START_CMD='); }); +it('fails fast when docker buildx is unavailable for railpack builds', function () { + [$job, $reflection] = makeRailpackDeploymentJob(); + + $dockerBuildxAvailableProperty = $reflection->getProperty('dockerBuildxAvailable'); + $dockerBuildxAvailableProperty->setAccessible(true); + $dockerBuildxAvailableProperty->setValue($job, false); + + expect(fn () => invokeRailpackMethod($job, $reflection, 'ensure_docker_buildx_available_for_railpack')) + ->toThrow(DeploymentException::class, 'Railpack deployments require the Docker buildx CLI plugin'); +}); + it('builds railpack docker command with matching env and secret flags for all railpack variables', function () { [$job, $reflection] = makeRailpackDeploymentJob([ 'uuid' => 'application-uuid', diff --git a/tests/Unit/ValidationPatternsTest.php b/tests/Unit/ValidationPatternsTest.php index 092c37b25..a959b18d5 100644 --- a/tests/Unit/ValidationPatternsTest.php +++ b/tests/Unit/ValidationPatternsTest.php @@ -1,5 +1,6 @@ toBe('node.name'); }); + +it('normalizes environment variable keys before model validation', function () { + $environmentVariable = new EnvironmentVariable; + $environmentVariable->key = ' APP_ENV '; + + expect($environmentVariable->key)->toBe('APP_ENV'); +});