Skip to content
Open
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
28 changes: 24 additions & 4 deletions Sources/ContainerCommands/BuildCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,32 @@ extension Application {
}
progress.start()

progress.set(description: "Dialing builder")

let dnsNameservers = self.dns.nameservers
let builder: Builder? = try await withThrowingTaskGroup(of: Builder.self) { [vsockPort, cpus, memory, dnsNameservers] group in
let dnsDomain = self.dns.domain
let dnsSearchDomains = self.dns.searchDomains
let dnsOptions = self.dns.options
progress.set(tasks: 0)
progress.set(totalTasks: 3)
try await BuilderStart.start(
cpus: self.cpus,
memory: self.memory,
log: log,
dnsNameservers: dnsNameservers,
dnsDomain: dnsDomain,
dnsSearchDomains: dnsSearchDomains,
dnsOptions: dnsOptions,
progressUpdate: progress.handler,
containerSystemConfig: containerSystemConfig,
)

progress.set(description: "Dialing builder")
let builder: Builder? = try await withThrowingTaskGroup(of: Builder.self) {
[vsockPort, cpus, memory, dnsNameservers, dnsDomain, dnsSearchDomains, dnsOptions] group in
defer {
group.cancelAll()
}

group.addTask { [vsockPort, cpus, memory, log, dnsNameservers] in
group.addTask { [vsockPort, cpus, memory, log, dnsNameservers, dnsDomain, dnsSearchDomains, dnsOptions] in
let client = ContainerClient()
while true {
do {
Expand All @@ -193,6 +210,9 @@ extension Application {
memory: memory,
log: log,
dnsNameservers: dnsNameservers,
dnsDomain: dnsDomain,
dnsSearchDomains: dnsSearchDomains,
dnsOptions: dnsOptions,
progressUpdate: progress.handler,
containerSystemConfig: containerSystemConfig,
)
Expand Down
41 changes: 24 additions & 17 deletions Sources/ContainerCommands/Builder/BuilderStart.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ extension Application {
progressUpdate: @escaping ProgressUpdateHandler,
containerSystemConfig: ContainerSystemConfig,
) async throws {
let dns = Utility.dnsConfiguration(
from: .init(
domain: dnsDomain,
nameservers: dnsNameservers,
options: dnsOptions,
searchDomains: dnsSearchDomains
),
defaults: containerSystemConfig.dns
)

await progressUpdate([
.setDescription("Fetching BuildKit image"),
.setItemsName("blobs"),
Expand Down Expand Up @@ -150,20 +160,17 @@ extension Application {
let imageChanged = existingImage != builderImage
let cpuChanged = existingResources.cpus != resources.cpus
let memChanged = existingResources.memoryInBytes != resources.memoryInBytes
let hasDNSConfig =
!dns.nameservers.isEmpty
|| dns.domain != nil
|| !dns.searchDomains.isEmpty
|| !dns.options.isEmpty
let dnsChanged = {
if !dnsNameservers.isEmpty {
return existingDNS?.nameservers != dnsNameservers
}
if dnsDomain != nil {
return existingDNS?.domain != dnsDomain
}
if !dnsSearchDomains.isEmpty {
return existingDNS?.searchDomains != dnsSearchDomains
}
if !dnsOptions.isEmpty {
return existingDNS?.options != dnsOptions
}
return false
guard hasDNSConfig else { return false }
return existingDNS?.nameservers != dns.nameservers
|| existingDNS?.domain != dns.domain
|| existingDNS?.searchDomains != dns.searchDomains
|| existingDNS?.options != dns.options
}()

switch existingContainer.status {
Expand Down Expand Up @@ -273,10 +280,10 @@ extension Application {
AttachmentConfiguration(network: defaultNetwork.id, options: AttachmentOptions(hostname: Builder.builderContainerId))
]
config.dns = ContainerConfiguration.DNSConfiguration(
nameservers: dnsNameservers,
domain: dnsDomain,
searchDomains: dnsSearchDomains,
options: dnsOptions
nameservers: dns.nameservers,
domain: dns.domain,
searchDomains: dns.searchDomains,
options: dns.options
)

let kernel = try await {
Expand Down
20 changes: 19 additions & 1 deletion Sources/ContainerPersistence/ContainerSystemConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,33 @@ final public class ContainerConfig: Codable, Sendable {
}

final public class DNSConfig: Codable, Sendable {
public static let defaultNameservers: [String] = []
public static let defaultSearchDomains: [String] = []
public static let defaultOptions: [String] = []

public let domain: String?
public let nameservers: [String]
public let searchDomains: [String]
public let options: [String]

public init(domain: String? = nil) {
public init(
domain: String? = nil,
nameservers: [String] = defaultNameservers,
searchDomains: [String] = defaultSearchDomains,
options: [String] = defaultOptions
) {
self.domain = domain
self.nameservers = nameservers
self.searchDomains = searchDomains
self.options = options
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.domain = try container.decodeIfPresent(String.self, forKey: .domain)
self.nameservers = try container.decodeIfPresent([String].self, forKey: .nameservers) ?? Self.defaultNameservers
self.searchDomains = try container.decodeIfPresent([String].self, forKey: .searchDomains) ?? Self.defaultSearchDomains
self.options = try container.decodeIfPresent([String].self, forKey: .options) ?? Self.defaultOptions
}
}

Expand Down
33 changes: 26 additions & 7 deletions Sources/Services/ContainerAPIService/Client/Utility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,7 @@ public struct Utility {
if management.dnsDisabled {
config.dns = nil
} else {
let domain = management.dns.domain ?? containerSystemConfig.dns.domain
config.dns = .init(
nameservers: management.dns.nameservers,
domain: domain,
searchDomains: management.dns.searchDomains,
options: management.dns.options
)
config.dns = dnsConfiguration(from: management.dns, defaults: containerSystemConfig.dns)
}

config.rosetta = management.rosetta || (Platform.current.architecture == "arm64" && requestedPlatform.architecture == "amd64")
Expand Down Expand Up @@ -333,6 +327,31 @@ public struct Utility {
return [AttachmentConfiguration(network: builtinNetworkId, options: AttachmentOptions(hostname: fqdn ?? containerId, macAddress: nil, mtu: 1280))]
}

public static func dnsConfiguration(
from flags: Flags.DNS,
defaults: DNSConfig
) -> ContainerConfiguration.DNSConfiguration {
let nameservers =
flags.nameservers.isEmpty
? defaults.nameservers
: flags.nameservers
let domain = flags.domain ?? defaults.domain
let searchDomains =
flags.searchDomains.isEmpty
? defaults.searchDomains
: flags.searchDomains
let options =
flags.options.isEmpty
? defaults.options
: flags.options
return .init(
nameservers: nameservers,
domain: domain,
searchDomains: searchDomains,
options: options
)
}

private static func getKernel(management: Flags.Management) async throws -> Kernel {
// For the image itself we'll take the user input and try with it as we can do userspace
// emulation for x86, but for the kernel we need it to match the hosts architecture.
Expand Down
71 changes: 71 additions & 0 deletions Tests/ContainerAPIClientTests/UtilityTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// limitations under the License.
//===----------------------------------------------------------------------===//

import ContainerPersistence
import ContainerResource
import ContainerizationError
import Foundation
Expand Down Expand Up @@ -90,6 +91,76 @@ struct UtilityTests {
}
}

@Test
func testDNSConfigurationUsesDefaultsWhenFlagsAbsent() {
let defaults = DNSConfig(
domain: "corp.local",
nameservers: ["1.1.1.1"],
searchDomains: ["corp.local"],
options: ["ndots:2"]
)

let flags = Flags.DNS(
domain: nil,
nameservers: [],
options: [],
searchDomains: []
)

let result = Utility.dnsConfiguration(from: flags, defaults: defaults)

#expect(result.domain == "corp.local")
#expect(result.nameservers == ["1.1.1.1"])
#expect(result.searchDomains == ["corp.local"])
#expect(result.options == ["ndots:2"])
}

@Test
func testDNSConfigurationFlagsOverrideDefaults() {
let defaults = DNSConfig(
domain: "corp.local",
nameservers: ["1.1.1.1"],
searchDomains: ["corp.local"],
options: ["ndots:2"]
)
let flags = Flags.DNS(
domain: "cli.local",
nameservers: ["8.8.8.8"],
options: ["debug"],
searchDomains: ["cli.local"]
)

let result = Utility.dnsConfiguration(from: flags, defaults: defaults)

#expect(result.domain == "cli.local")
#expect(result.nameservers == ["8.8.8.8"])
#expect(result.searchDomains == ["cli.local"])
#expect(result.options == ["debug"])
}

@Test
func testDNSConfigurationPartialFlagsFallbackToDefaults() {
let defaults = DNSConfig(
domain: "corp.local",
nameservers: ["1.1.1.1"],
searchDomains: ["corp.local"],
options: ["ndots:2"]
)
let flags = Flags.DNS(
domain: nil,
nameservers: ["8.8.8.8"],
options: [],
searchDomains: []
)

let result = Utility.dnsConfiguration(from: flags, defaults: defaults)

#expect(result.domain == "corp.local")
#expect(result.nameservers == ["8.8.8.8"])
#expect(result.searchDomains == ["corp.local"])
#expect(result.options == ["ndots:2"])
}

@Test
func testPublishPortParser() throws {
let ports = try Parser.publishPorts([
Expand Down
26 changes: 26 additions & 0 deletions Tests/ContainerPersistenceTests/ConfigurationLoaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ struct ConfigurationLoaderTests {
#expect(config.container.cpus == 4)
#expect(config.container.memory == ContainerConfig.defaultMemory)
#expect(config.dns.domain == nil)
#expect(config.dns.nameservers == [])
#expect(config.dns.searchDomains == [])
#expect(config.dns.options == [])
#expect(!config.build.image.isEmpty)
#expect(!config.vminit.image.isEmpty)
#expect(!config.kernel.binaryPath.isEmpty)
Expand All @@ -116,6 +119,9 @@ struct ConfigurationLoaderTests {

[dns]
domain = "custom"
nameservers = ["1.1.1.1", "8.8.8.8"]
searchDomains = ["corp.local", "lab.corp.local"]
options = ["ndots:2", "timeout:1"]

[kernel]
binaryPath = "custom/path"
Expand Down Expand Up @@ -143,6 +149,9 @@ struct ConfigurationLoaderTests {
let expectedContainerMemory = try MemorySize("8g")
#expect(config.container.memory == expectedContainerMemory)
#expect(config.dns.domain == "custom")
#expect(config.dns.nameservers == ["1.1.1.1", "8.8.8.8"])
#expect(config.dns.searchDomains == ["corp.local", "lab.corp.local"])
#expect(config.dns.options == ["ndots:2", "timeout:1"])
#expect(config.build.image == "custom-builder:latest")
#expect(config.vminit.image == "custom-init:latest")
#expect(config.kernel.binaryPath == "custom/path")
Expand Down Expand Up @@ -173,6 +182,23 @@ struct ConfigurationLoaderTests {
}
}

@Test func dnsArraysLoadFromTomlIndependently() async throws {
try await TemporaryStorage.withTempDir { tempDir in
let toml = """
[dns]
nameservers = ["1.1.1.1", "8.8.8.8"]
"""
let tmpFile = tempDir.appending("dns.toml")
try Self.writeToml(toml, to: tmpFile)

let config: ContainerSystemConfig = try await ConfigurationLoader.load(configurationFiles: [tmpFile])
#expect(config.dns.nameservers == ["1.1.1.1", "8.8.8.8"])
#expect(config.dns.domain == nil)
#expect(config.dns.searchDomains == [])
#expect(config.dns.options == [])
}
}

@Test func unknownKeysIgnored() async throws {
try await TemporaryStorage.withTempDir { tempDir in
let toml = """
Expand Down
14 changes: 14 additions & 0 deletions docs/how-to.md
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,20 @@ rosetta = false

This is useful when you want to ensure builds only produce native arm64 images and avoid any x86_64 emulation.

### Example: Set default DNS settings

To avoid passing `--dns`, `--dns-search`, and `--dns-option` on every `container run`, `container build`, or `container builder start` invocation, set defaults in `~/.config/container/config.toml`:

```toml
[dns]
domain = "corp.local"
nameservers = ["1.1.1.1", "8.8.8.8"]
searchDomains = ["corp.local", "lab.corp.local"]
options = ["ndots:2", "timeout:1"]
```

CLI flags override these defaults when provided. Use `container run --no-dns` to skip DNS configuration entirely. Restart the daemon (`container system stop && container system start`) for changes to take effect.

## View system logs

The `container system logs` command allows you to look at the log messages that `container` writes:
Expand Down