Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Sources/ContainerCommands/BuildCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ extension Application {
}()

@Option(name: .shortAndLong, help: "Number of CPUs to allocate to the builder container")
var cpus: Int64 = 2
var cpus: Int64?

@Option(name: .shortAndLong, help: ArgumentHelp("Path to Dockerfile", valueName: "path"))
var file: String?
Expand All @@ -79,7 +79,7 @@ extension Application {
name: .shortAndLong,
help: "Amount of builder container memory (1MiByte granularity), with optional K, M, G, T, or P suffix"
)
var memory: String = "2048MB"
var memory: String?

@Flag(name: .long, help: "Do not use cache")
var noCache: Bool = false
Expand Down
40 changes: 20 additions & 20 deletions Sources/ContainerCommands/Builder/BuilderStart.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ import TerminalProgress

extension Application {
public struct BuilderStart: AsyncLoggableCommand {
static let defaultCPUs = 2
static let defaultMemoryInBytes: UInt64 = 2048.mib()

public static var configuration: CommandConfiguration {
var config = CommandConfiguration()
config.commandName = "start"
Expand All @@ -37,13 +40,13 @@ extension Application {
}

@Option(name: .shortAndLong, help: "Number of CPUs to allocate to the builder container")
var cpus: Int64 = 2
var cpus: Int64?

@Option(
name: .shortAndLong,
help: "Amount of builder container memory (1MiByte granularity), with optional K, M, G, T, or P suffix"
)
var memory: String = "2048MB"
var memory: String?

@OptionGroup
public var dns: Flags.DNS
Expand Down Expand Up @@ -135,23 +138,16 @@ extension Application {

// Check if we need to recreate the builder due to different image
let imageChanged = existingImage != builderImage
let cpuChanged = {
if let cpus {
if existingResources.cpus != cpus {
return true
}
}
return false
}()
let memChanged = try {
if let memory {
let memoryInBytes = try Parser.resources(cpus: nil, memory: memory).memoryInBytes
if existingResources.memoryInBytes != memoryInBytes {
return true
}
}
return false
}()
let resolvedResources = try Parser.resources(
cpus: cpus,
memory: memory,
cpuPropertyKey: .defaultBuildCPUs,
memoryPropertyKey: .defaultBuildMemory,
defaultCPUs: Self.defaultCPUs,
defaultMemoryInBytes: Self.defaultMemoryInBytes
)
let cpuChanged = existingResources.cpus != resolvedResources.cpus
let memChanged = existingResources.memoryInBytes != resolvedResources.memoryInBytes
let dnsChanged = {
if !dnsNameservers.isEmpty {
return existingDNS?.nameservers != dnsNameservers
Expand Down Expand Up @@ -241,7 +237,11 @@ extension Application {

let resources = try Parser.resources(
cpus: cpus,
memory: memory
memory: memory,
cpuPropertyKey: .defaultBuildCPUs,
memoryPropertyKey: .defaultBuildMemory,
defaultCPUs: Self.defaultCPUs,
defaultMemoryInBytes: Self.defaultMemoryInBytes
)

var config = ContainerConfiguration(id: Builder.builderContainerId, image: imageDesc, process: processConfig)
Expand Down
20 changes: 10 additions & 10 deletions Sources/ContainerCommands/System/Property/PropertySet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ extension Application {
throw ContainerizationError(.invalidArgument, message: "invalid boolean value: \(value)")
}
DefaultsStore.setBool(value: boolValue, key: key)
case .defaultBuildCPUs, .defaultContainerCPUs:
guard let cpuCount = Int(value), cpuCount > 0 else {
throw ContainerizationError(.invalidArgument, message: "invalid CPU count: \(value)")
}
DefaultsStore.set(value: value, key: key)
case .defaultBuildMemory, .defaultContainerMemory:
guard let memoryMiB = try? Parser.memoryStringAsMiB(value), memoryMiB > 0 else {
throw ContainerizationError(.invalidArgument, message: "invalid memory value: \(value)")
}
DefaultsStore.set(value: value, key: key)
case .defaultDNSDomain, .defaultRegistryDomain:
guard Parser.isValidDomainName(value) else {
throw ContainerizationError(.invalidArgument, message: "invalid domain name: \(value)")
Expand All @@ -69,16 +79,6 @@ extension Application {
}
DefaultsStore.set(value: value, key: key)
return
case .defaultContainerCPUs:
guard let cpuCount = Int(value), cpuCount > 0 else {
throw ContainerizationError(.invalidArgument, message: "invalid CPU count: \(value)")
}
DefaultsStore.set(value: value, key: key)
case .defaultContainerMemory:
guard let memoryMiB = try? Parser.memoryStringAsMiB(value), memoryMiB > 0 else {
throw ContainerizationError(.invalidArgument, message: "invalid memory value: \(value)")
}
DefaultsStore.set(value: value, key: key)
case .defaultSubnet:
guard (try? CIDRv4(value)) != nil else {
throw ContainerizationError(.invalidArgument, message: "invalid CIDRv4 address: \(value)")
Expand Down
18 changes: 18 additions & 0 deletions Sources/ContainerPersistence/DefaultsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public enum DefaultsStore {

public enum Keys: String {
case buildRosetta = "build.rosetta"
case defaultBuildCPUs = "build.cpus"
case defaultBuildMemory = "build.memory"
case defaultContainerCPUs = "container.cpus"
case defaultContainerMemory = "container.memory"
case defaultDNSDomain = "dns.domain"
Expand Down Expand Up @@ -71,6 +73,8 @@ public enum DefaultsStore {
public static func allValues() -> [DefaultsStoreValue] {
let allKeys: [(Self.Keys, (Self.Keys) -> Any?)] = [
(.buildRosetta, { Self.getBool(key: $0) }),
(.defaultBuildCPUs, { Self.getOptional(key: $0) }),
(.defaultBuildMemory, { Self.getOptional(key: $0) }),
(.defaultContainerCPUs, { Self.getOptional(key: $0) }),
(.defaultContainerMemory, { Self.getOptional(key: $0) }),
(.defaultBuilderImage, { Self.get(key: $0) }),
Expand Down Expand Up @@ -126,6 +130,10 @@ extension DefaultsStore.Keys {
switch self {
case .buildRosetta:
return "Build amd64 images on arm64 using Rosetta, instead of QEMU."
case .defaultBuildCPUs:
return "If defined, the default number of CPUs to allocate to the builder container."
case .defaultBuildMemory:
return "If defined, the default amount of memory to allocate to the builder container."
case .defaultContainerCPUs:
return "If defined, the default number of CPUs to allocate to a container."
case .defaultContainerMemory:
Expand Down Expand Up @@ -153,6 +161,10 @@ extension DefaultsStore.Keys {
switch self {
case .buildRosetta:
return Bool.self
case .defaultBuildCPUs:
return String.self
case .defaultBuildMemory:
return String.self
case .defaultContainerCPUs:
return String.self
case .defaultContainerMemory:
Expand Down Expand Up @@ -181,6 +193,12 @@ extension DefaultsStore.Keys {
case .buildRosetta:
// This is a boolean key, not used with the string get() method
return "true"
case .defaultBuildCPUs:
// This key is read with getOptional(), not get(); this value is never used
return "2"
case .defaultBuildMemory:
// This key is read with getOptional(), not get(); this value is never used
return "2048MB"
case .defaultContainerCPUs:
// This key is read with getOptional(), not get(); this value is never used
return "4"
Expand Down
16 changes: 13 additions & 3 deletions Sources/Services/ContainerAPIService/Client/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,28 @@ public struct Parser {
try .init(from: platform)
}

public static func resources(cpus: Int64?, memory: String?) throws -> ContainerConfiguration.Resources {
public static func resources(
cpus: Int64?,
memory: String?,
cpuPropertyKey: DefaultsStore.Keys = .defaultContainerCPUs,
memoryPropertyKey: DefaultsStore.Keys = .defaultContainerMemory,
defaultCPUs: Int = 4,
defaultMemoryInBytes: UInt64 = 1024.mib()
) throws -> ContainerConfiguration.Resources {
var resource = ContainerConfiguration.Resources()
resource.cpus = defaultCPUs
resource.memoryInBytes = defaultMemoryInBytes

if let cpus {
resource.cpus = Int(cpus)
} else if let cpuStr = DefaultsStore.getOptional(key: .defaultContainerCPUs),
} else if let cpuStr = DefaultsStore.getOptional(key: cpuPropertyKey),
let cpuVal = Int(cpuStr), cpuVal > 0
{
resource.cpus = cpuVal
}
if let memory {
resource.memoryInBytes = try Parser.memoryStringAsMiB(memory).mib()
} else if let memStr = DefaultsStore.getOptional(key: .defaultContainerMemory) {
} else if let memStr = DefaultsStore.getOptional(key: memoryPropertyKey) {
resource.memoryInBytes = try Parser.memoryStringAsMiB(memStr).mib()
}
return resource
Expand Down
45 changes: 45 additions & 0 deletions Tests/ContainerAPIClientTests/ParserTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,34 @@ struct ParserTest {

// MARK: - Parser.resources

@Test
func testResourcesCustomDefaults() throws {
let result = try Parser.resources(
cpus: nil, memory: nil,
cpuPropertyKey: .defaultBuildCPUs, memoryPropertyKey: .defaultBuildMemory,
defaultCPUs: 2, defaultMemoryInBytes: 2048.mib()
)
#expect(result.cpus == 2)
#expect(result.memoryInBytes == 2048.mib())
}

@Test
func testResourcesBuildPropertyLookup() throws {
DefaultsStore.set(value: "8", key: .defaultBuildCPUs)
DefaultsStore.set(value: "4g", key: .defaultBuildMemory)
defer {
DefaultsStore.unset(key: .defaultBuildCPUs)
DefaultsStore.unset(key: .defaultBuildMemory)
}
let result = try Parser.resources(
cpus: nil, memory: nil,
cpuPropertyKey: .defaultBuildCPUs, memoryPropertyKey: .defaultBuildMemory,
defaultCPUs: 2, defaultMemoryInBytes: 2048.mib()
)
#expect(result.cpus == 8)
#expect(result.memoryInBytes == 4096.mib())
}

@Test
func testResourcesCPUsFromProperty() throws {
DefaultsStore.set(value: "8", key: .defaultContainerCPUs)
Expand Down Expand Up @@ -1075,4 +1103,21 @@ struct ParserTest {
#expect(result.cpus == 1)
#expect(result.memoryInBytes == 256.mib())
}

@Test
func testResourcesPropertyKeysAreIsolated() throws {
DefaultsStore.set(value: "16", key: .defaultContainerCPUs)
DefaultsStore.set(value: "8g", key: .defaultContainerMemory)
defer {
DefaultsStore.unset(key: .defaultContainerCPUs)
DefaultsStore.unset(key: .defaultContainerMemory)
}
let result = try Parser.resources(
cpus: nil, memory: nil,
cpuPropertyKey: .defaultBuildCPUs, memoryPropertyKey: .defaultBuildMemory,
defaultCPUs: 2, defaultMemoryInBytes: 2048.mib()
)
#expect(result.cpus == 2)
#expect(result.memoryInBytes == 2048.mib())
}
}
Loading