diff --git a/Sources/ContainerPersistence/ContainerSystemConfig.swift b/Sources/ContainerPersistence/ContainerSystemConfig.swift index c3b04e2ce..16b5a82cc 100644 --- a/Sources/ContainerPersistence/ContainerSystemConfig.swift +++ b/Sources/ContainerPersistence/ContainerSystemConfig.swift @@ -15,6 +15,7 @@ //===----------------------------------------------------------------------===// import CVersion +import ContainerResource import ContainerVersion import ContainerizationExtras import Foundation @@ -132,15 +133,31 @@ final public class ContainerConfig: Codable, Sendable { } final public class DNSConfig: Codable, Sendable { + public let nameservers: [String] public let domain: String? + public let searchDomains: [String] + public let options: [String] - public init(domain: String? = nil) { + public init( + nameservers: [String] = ContainerConfiguration.DNSConfiguration.defaultNameservers, + domain: String? = nil, + searchDomains: [String] = [], + options: [String] = [] + ) { + self.nameservers = nameservers self.domain = domain + self.searchDomains = searchDomains + self.options = options } public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) + self.nameservers = + try container.decodeIfPresent([String].self, forKey: .nameservers) + ?? ContainerConfiguration.DNSConfiguration.defaultNameservers self.domain = try container.decodeIfPresent(String.self, forKey: .domain) + self.searchDomains = try container.decodeIfPresent([String].self, forKey: .searchDomains) ?? [] + self.options = try container.decodeIfPresent([String].self, forKey: .options) ?? [] } } diff --git a/Sources/Services/ContainerAPIService/Client/Utility.swift b/Sources/Services/ContainerAPIService/Client/Utility.swift index ce937ebdf..a38726a8f 100644 --- a/Sources/Services/ContainerAPIService/Client/Utility.swift +++ b/Sources/Services/ContainerAPIService/Client/Utility.swift @@ -224,17 +224,11 @@ 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( + dns: management.dns, + dnsDisabled: management.dnsDisabled, + defaults: containerSystemConfig.dns + ) config.rosetta = management.rosetta || (Platform.current.architecture == "arm64" && requestedPlatform.architecture == "amd64") @@ -272,6 +266,19 @@ public struct Utility { return (config, kernel, management.initImage) } + static func dnsConfiguration(dns: Flags.DNS, dnsDisabled: Bool, defaults: DNSConfig) -> ContainerConfiguration.DNSConfiguration? { + guard !dnsDisabled else { + return nil + } + + return .init( + nameservers: dns.nameservers.isEmpty ? defaults.nameservers : dns.nameservers, + domain: dns.domain ?? defaults.domain, + searchDomains: dns.searchDomains.isEmpty ? defaults.searchDomains : dns.searchDomains, + options: dns.options.isEmpty ? defaults.options : dns.options + ) + } + static func getAttachmentConfigurations( containerId: String, builtinNetworkId: String?, diff --git a/Tests/ContainerAPIClientTests/UtilityTests.swift b/Tests/ContainerAPIClientTests/UtilityTests.swift index 3a9b3a495..1740dd3d0 100644 --- a/Tests/ContainerAPIClientTests/UtilityTests.swift +++ b/Tests/ContainerAPIClientTests/UtilityTests.swift @@ -14,6 +14,7 @@ // limitations under the License. //===----------------------------------------------------------------------===// +import ContainerPersistence import ContainerResource import ContainerizationError import Foundation @@ -31,6 +32,53 @@ struct UtilityTests { #expect(result["key2"] == "value2") } + @Test("DNS config uses system defaults when CLI flags are absent") + func testDNSConfigurationUsesSystemDefaults() throws { + let defaults = DNSConfig( + nameservers: ["9.9.9.9", "149.112.112.112"], + domain: "containers.example", + searchDomains: ["svc.example"], + options: ["ndots:1"] + ) + + let dns = try #require(Utility.dnsConfiguration(dns: try Flags.DNS.parse([]), dnsDisabled: false, defaults: defaults)) + + #expect(dns.nameservers == ["9.9.9.9", "149.112.112.112"]) + #expect(dns.domain == "containers.example") + #expect(dns.searchDomains == ["svc.example"]) + #expect(dns.options == ["ndots:1"]) + } + + @Test("DNS CLI flags override system defaults") + func testDNSConfigurationCLIOverridesSystemDefaults() throws { + let defaults = DNSConfig( + nameservers: ["9.9.9.9"], + domain: "containers.example", + searchDomains: ["svc.example"], + options: ["ndots:1"] + ) + let flags = Flags.DNS( + domain: "override.example", + nameservers: ["8.8.8.8"], + options: ["debug"], + searchDomains: ["override.search"] + ) + + let dns = try #require(Utility.dnsConfiguration(dns: flags, dnsDisabled: false, defaults: defaults)) + + #expect(dns.nameservers == ["8.8.8.8"]) + #expect(dns.domain == "override.example") + #expect(dns.searchDomains == ["override.search"]) + #expect(dns.options == ["debug"]) + } + + @Test("DNS config is disabled by no-dns") + func testDNSConfigurationDisabled() throws { + let dns = Utility.dnsConfiguration(dns: try Flags.DNS.parse([]), dnsDisabled: true, defaults: .init(nameservers: ["9.9.9.9"])) + + #expect(dns == nil) + } + @Test("Parse standalone keys") func testStandaloneKeys() { let result = Utility.parseKeyValuePairs(["standalone"]) diff --git a/Tests/ContainerPersistenceTests/ConfigurationLoaderTests.swift b/Tests/ContainerPersistenceTests/ConfigurationLoaderTests.swift index 5f27694dc..ca725ddda 100644 --- a/Tests/ContainerPersistenceTests/ConfigurationLoaderTests.swift +++ b/Tests/ContainerPersistenceTests/ConfigurationLoaderTests.swift @@ -15,6 +15,7 @@ //===----------------------------------------------------------------------===// import ContainerTestSupport +import ContainerResource import ContainerizationExtras import Foundation import SystemPackage @@ -90,7 +91,10 @@ struct ConfigurationLoaderTests { #expect(config.build.memory == BuildConfig.defaultMemory) #expect(config.container.cpus == 4) #expect(config.container.memory == ContainerConfig.defaultMemory) + #expect(config.dns.nameservers == ContainerConfiguration.DNSConfiguration.defaultNameservers) #expect(config.dns.domain == nil) + #expect(config.dns.searchDomains == []) + #expect(config.dns.options == []) #expect(!config.build.image.isEmpty) #expect(!config.vminit.image.isEmpty) #expect(!config.kernel.binaryPath.isEmpty) @@ -115,7 +119,10 @@ struct ConfigurationLoaderTests { memory = "8g" [dns] + nameservers = ["8.8.8.8", "8.8.4.4"] domain = "custom" + searchDomains = ["svc.local", "corp.local"] + options = ["ndots:1"] [kernel] binaryPath = "custom/path" @@ -142,7 +149,10 @@ struct ConfigurationLoaderTests { #expect(config.container.cpus == 16) let expectedContainerMemory = try MemorySize("8g") #expect(config.container.memory == expectedContainerMemory) + #expect(config.dns.nameservers == ["8.8.8.8", "8.8.4.4"]) #expect(config.dns.domain == "custom") + #expect(config.dns.searchDomains == ["svc.local", "corp.local"]) + #expect(config.dns.options == ["ndots:1"]) #expect(config.build.image == "custom-builder:latest") #expect(config.vminit.image == "custom-init:latest") #expect(config.kernel.binaryPath == "custom/path") diff --git a/docs/container-system-config.md b/docs/container-system-config.md index 547ae5ff7..7f3ea8112 100644 --- a/docs/container-system-config.md +++ b/docs/container-system-config.md @@ -14,7 +14,7 @@ Source of truth: [`Sources/ContainerPersistence/ContainerSystemConfig.swift`](.. ```toml [build] # builder VM resources and image [container] # default per-container resources -[dns] # default DNS domain for DNS resolution on host +[dns] # default DNS settings for containers and host resolution [kernel] # guest kernel binary path and download URL [network] # default subnets for new networks [registry] # default registry domain @@ -46,9 +46,12 @@ Defaults applied when `container run` / `container create` is invoked without `- ## `[dns]` -| Key | Type | Default | Description | -|----------|-----------|---------|----------------------------------------------------------------------------| -| `domain` | `String?` | unset | Local DNS domain appended to container hostnames (e.g. `"test"` makes `my-web-server` resolvable as `my-web-server.test`). When unset, no domain is appended. | +| Key | Type | Default | Description | +|-----------------|------------|---------------|----------------------------------------------------------------------------| +| `nameservers` | `[String]` | `["1.1.1.1"]` | DNS nameserver IP addresses configured in containers when `--dns` is not supplied. | +| `domain` | `String?` | unset | Local DNS domain appended to container hostnames (e.g. `"test"` makes `my-web-server` resolvable as `my-web-server.test`). When unset, no domain is appended. | +| `searchDomains` | `[String]` | `[]` | DNS search domains configured in containers when `--dns-search` is not supplied. | +| `options` | `[String]` | `[]` | DNS resolver options configured in containers when `--dns-option` is not supplied. | ## `[kernel]` diff --git a/docs/how-to.md b/docs/how-to.md index 07697ecd2..066297528 100644 --- a/docs/how-to.md +++ b/docs/how-to.md @@ -656,7 +656,10 @@ cpus = 4 memory = "1gb" [dns] +nameservers = ["1.1.1.1"] domain = "test" +searchDomains = [] +options = [] [kernel] binaryPath = "opt/kata/share/kata-containers/vmlinux-6.18.5-177" diff --git a/docs/tutorials/container-system-config-tutorial.md b/docs/tutorials/container-system-config-tutorial.md index 05a2bfa6e..c3f38d7e6 100644 --- a/docs/tutorials/container-system-config-tutorial.md +++ b/docs/tutorials/container-system-config-tutorial.md @@ -41,7 +41,10 @@ cpus = 8 memory = "4g" [dns] +nameservers = ["9.9.9.9", "149.112.112.112"] domain = "test" +searchDomains = ["corp.example"] +options = ["ndots:1"] ``` Each top-level table maps directly to a section of [ContainerSystemConfig](../container-system-config.md).