diff --git a/Sources/ContainerCommands/Builder/BuilderStart.swift b/Sources/ContainerCommands/Builder/BuilderStart.swift index f8bf20972..70b3654c6 100644 --- a/Sources/ContainerCommands/Builder/BuilderStart.swift +++ b/Sources/ContainerCommands/Builder/BuilderStart.swift @@ -126,7 +126,7 @@ extension Application { let defaultBuildCPUs: Int = containerSystemConfig.build.cpus let defaultBuildMemory = containerSystemConfig.build.memory - let resources = try Parser.resources( + var resources = try Parser.resources( cpus: cpus, memory: memory, defaultCPUs: defaultBuildCPUs, @@ -138,6 +138,12 @@ extension Application { if let existingContainer { let existingImage = existingContainer.configuration.image.reference let existingResources = existingContainer.configuration.resources + resources = BuilderStart.resourcesForStart( + requested: resources, + existing: existingResources, + cpus: cpus, + memory: memory + ) let existingEnv = existingContainer.configuration.initProcess.environment let existingDNS = existingContainer.configuration.dns @@ -323,6 +329,23 @@ extension Application { try await startBuildKit(client: client, id: Builder.builderContainerId, progressUpdate, taskManager) log.debug("starting BuildKit and BuildKit-shim") } + + // A nil flag means the caller expressed no preference, so preserve the existing value. + static func resourcesForStart( + requested: ContainerConfiguration.Resources, + existing: ContainerConfiguration.Resources, + cpus: Int64?, + memory: String? + ) -> ContainerConfiguration.Resources { + var resources = requested + if cpus == nil { + resources.cpus = existing.cpus + } + if memory == nil { + resources.memoryInBytes = existing.memoryInBytes + } + return resources + } } } diff --git a/Tests/ContainerCommandsTests/BuilderStartTests.swift b/Tests/ContainerCommandsTests/BuilderStartTests.swift new file mode 100644 index 000000000..fdc71042e --- /dev/null +++ b/Tests/ContainerCommandsTests/BuilderStartTests.swift @@ -0,0 +1,78 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 Apple Inc. and the container project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import ContainerResource +import Foundation +import Testing + +@testable import ContainerCommands + +struct BuilderStartTests { + private func resources(cpus: Int, mebibytes: UInt64) -> ContainerConfiguration.Resources { + var resources = ContainerConfiguration.Resources() + resources.cpus = cpus + resources.memoryInBytes = mebibytes * 1024 * 1024 + return resources + } + + @Test + func startingWithoutResourceFlagsPreservesExistingResources() { + let requested = resources(cpus: 2, mebibytes: 2048) + let existing = resources(cpus: 6, mebibytes: 12288) + + let result = Application.BuilderStart.resourcesForStart( + requested: requested, + existing: existing, + cpus: nil, + memory: nil + ) + + #expect(result.cpus == 6) + #expect(result.memoryInBytes == 12288 * 1024 * 1024) + } + + @Test + func startingWithCpuFlagPreservesExistingMemory() { + let requested = resources(cpus: 2, mebibytes: 2048) + let existing = resources(cpus: 6, mebibytes: 12288) + + let result = Application.BuilderStart.resourcesForStart( + requested: requested, + existing: existing, + cpus: 2, + memory: nil + ) + + #expect(result.cpus == 2) + #expect(result.memoryInBytes == 12288 * 1024 * 1024) + } + + @Test + func startingWithMemoryFlagPreservesExistingCpus() { + let requested = resources(cpus: 2, mebibytes: 4096) + let existing = resources(cpus: 6, mebibytes: 12288) + + let result = Application.BuilderStart.resourcesForStart( + requested: requested, + existing: existing, + cpus: nil, + memory: "4G" + ) + + #expect(result.cpus == 6) + #expect(result.memoryInBytes == 4096 * 1024 * 1024) + } +}