mirror of
https://github.com/apple/container.git
synced 2026-09-10 17:55:38 +00:00
LinuxContainer: Add /etc/hosts writing functionality (#207)
Closes #206 Much like we have support for supplying DNS configurations and writing out /etc/resolv.conf, this adds a way to write out /etc/hosts for a given container.
This commit is contained in:
@@ -309,6 +309,11 @@ extension Vminitd {
|
||||
})
|
||||
}
|
||||
|
||||
/// Configure /etc/hosts within the sandbox's environment.
|
||||
public func configureHosts(config: Hosts, location: String) async throws {
|
||||
_ = try await client.configureHosts(config.toAgentHostsRequest(location: location))
|
||||
}
|
||||
|
||||
/// Perform a sync call.
|
||||
public func sync() async throws {
|
||||
_ = try await client.sync(.init())
|
||||
@@ -337,6 +342,27 @@ extension Vminitd {
|
||||
}
|
||||
}
|
||||
|
||||
extension Hosts {
|
||||
func toAgentHostsRequest(location: String) -> Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest {
|
||||
Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest.with {
|
||||
$0.location = location
|
||||
if let comment {
|
||||
$0.comment = comment
|
||||
}
|
||||
$0.entries = entries.map {
|
||||
let entry = $0
|
||||
return Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest.HostsEntry.with {
|
||||
if let comment = entry.comment {
|
||||
$0.comment = comment
|
||||
}
|
||||
$0.ipAddress = entry.ipAddress
|
||||
$0.hostnames = entry.hostnames
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Vminitd.Client {
|
||||
public init(socket: String, group: MultiThreadedEventLoopGroup) {
|
||||
var config = ClientConnection.Configuration.default(
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/// Static table lookups for a container. The values will be used to
|
||||
/// construct /etc/hosts for a given container.
|
||||
public struct Hosts: Sendable {
|
||||
/// Represents one entry in an /etc/hosts file.
|
||||
public struct Entry: Sendable {
|
||||
/// The IPV4 or IPV6 address in String form.
|
||||
public var ipAddress: String
|
||||
/// The hostname(s) for the entry.
|
||||
public var hostnames: [String]
|
||||
/// An optional comment to be placed to the right side of the entry.
|
||||
public var comment: String?
|
||||
|
||||
public init(ipAddress: String, hostnames: [String], comment: String? = nil) {
|
||||
self.comment = comment
|
||||
self.hostnames = hostnames
|
||||
self.ipAddress = ipAddress
|
||||
}
|
||||
|
||||
/// The information in the structure rendered to a String representation
|
||||
/// that matches the format /etc/hosts expects.
|
||||
public var rendered: String {
|
||||
var line = ipAddress
|
||||
if !hostnames.isEmpty {
|
||||
line += " " + hostnames.joined(separator: " ")
|
||||
}
|
||||
if let comment {
|
||||
line += " # \(comment) "
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
||||
public static func localHostIPV4(comment: String? = nil) -> Self {
|
||||
Self(
|
||||
ipAddress: "127.0.0.1",
|
||||
hostnames: ["localhost"],
|
||||
comment: comment
|
||||
)
|
||||
}
|
||||
|
||||
public static func localHostIPV6(comment: String? = nil) -> Self {
|
||||
Self(
|
||||
ipAddress: "::1",
|
||||
hostnames: ["localhost", "ip6-localhost", "ip6-loopback"],
|
||||
comment: comment
|
||||
)
|
||||
}
|
||||
|
||||
public static func ipv6LocalNet(comment: String? = nil) -> Self {
|
||||
Self(
|
||||
ipAddress: "fe00::",
|
||||
hostnames: ["ip6-localnet"],
|
||||
comment: comment
|
||||
)
|
||||
}
|
||||
|
||||
public static func ipv6MulticastPrefix(comment: String? = nil) -> Self {
|
||||
Self(
|
||||
ipAddress: "ff00::",
|
||||
hostnames: ["ip6-mcastprefix"],
|
||||
comment: comment
|
||||
)
|
||||
}
|
||||
|
||||
public static func ipv6AllNodes(comment: String? = nil) -> Self {
|
||||
Self(
|
||||
ipAddress: "ff02::1",
|
||||
hostnames: ["ip6-allnodes"],
|
||||
comment: comment
|
||||
)
|
||||
}
|
||||
|
||||
public static func ipv6AllRouters(comment: String? = nil) -> Self {
|
||||
Self(
|
||||
ipAddress: "ff02::2",
|
||||
hostnames: ["ip6-allrouters"],
|
||||
comment: comment
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// The entries to be written to /etc/hosts.
|
||||
public var entries: [Entry]
|
||||
|
||||
/// A comment to render at the top of the file.
|
||||
public var comment: String?
|
||||
|
||||
public init(
|
||||
entries: [Entry],
|
||||
comment: String? = nil
|
||||
) {
|
||||
self.entries = entries
|
||||
self.comment = comment
|
||||
}
|
||||
}
|
||||
|
||||
extension Hosts {
|
||||
/// A default entry that can be used for convenience. It contains a IPV4
|
||||
/// and IPV6 localhost entry, as well as ipv6 localnet, ipv6 mcastprefix,
|
||||
/// ipv6 allnodes, and ipv6 allrouters.
|
||||
public static let `default` = Hosts(entries: [
|
||||
Entry.localHostIPV4(),
|
||||
Entry.localHostIPV6(),
|
||||
Entry.ipv6LocalNet(),
|
||||
Entry.ipv6MulticastPrefix(),
|
||||
Entry.ipv6AllNodes(),
|
||||
Entry.ipv6AllRouters(),
|
||||
])
|
||||
|
||||
/// Returns a string variant of the data that can be written to
|
||||
/// /etc/hosts directly.
|
||||
public var hostsFile: String {
|
||||
var lines: [String] = []
|
||||
|
||||
if let comment {
|
||||
lines.append("# \(comment)")
|
||||
}
|
||||
|
||||
for entry in entries {
|
||||
lines.append(entry.rendered)
|
||||
}
|
||||
|
||||
return lines.joined(separator: "\n") + "\n"
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ public final class LinuxContainer: Container, Sendable {
|
||||
var ioHandlers: LinuxProcess.IOHandler = .nullIO()
|
||||
var mounts: [Mount]
|
||||
var dns: DNS? = nil
|
||||
var hosts: Hosts? = nil
|
||||
}
|
||||
|
||||
@SendablePropertyUnchecked
|
||||
@@ -324,6 +325,12 @@ extension LinuxContainer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Hostname mapping configurations for the container.
|
||||
public var hosts: Hosts? {
|
||||
get { config.hosts }
|
||||
set { config.hosts = newValue }
|
||||
}
|
||||
|
||||
/// Unix sockets to share into or out of the container.
|
||||
///
|
||||
/// The VirtualMachineAgent used to launch the container
|
||||
@@ -545,9 +552,14 @@ extension LinuxContainer {
|
||||
try await agent.routeAddDefault(name: name, gateway: gateway)
|
||||
}
|
||||
}
|
||||
|
||||
// Setup /etc/resolv.conf and /etc/hosts if asked for.
|
||||
if let dns = self.dns {
|
||||
try await agent.configureDNS(config: dns, location: rootfs.destination)
|
||||
}
|
||||
if let hosts = self.hosts {
|
||||
try await agent.configureHosts(config: hosts, location: rootfs.destination)
|
||||
}
|
||||
|
||||
try state.setCreated(vm: vm, relayManager: relayManager)
|
||||
}
|
||||
|
||||
@@ -144,6 +144,11 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextClientProtoc
|
||||
callOptions: CallOptions?
|
||||
) -> UnaryCall<Com_Apple_Containerization_Sandbox_V3_ConfigureDnsRequest, Com_Apple_Containerization_Sandbox_V3_ConfigureDnsResponse>
|
||||
|
||||
func configureHosts(
|
||||
_ request: Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest,
|
||||
callOptions: CallOptions?
|
||||
) -> UnaryCall<Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest, Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse>
|
||||
|
||||
func sync(
|
||||
_ request: Com_Apple_Containerization_Sandbox_V3_SyncRequest,
|
||||
callOptions: CallOptions?
|
||||
@@ -557,6 +562,24 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextClientProtocol {
|
||||
)
|
||||
}
|
||||
|
||||
/// Configure /etc/hosts.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - request: Request to send to ConfigureHosts.
|
||||
/// - callOptions: Call options.
|
||||
/// - Returns: A `UnaryCall` with futures for the metadata, status and response.
|
||||
public func configureHosts(
|
||||
_ request: Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> UnaryCall<Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest, Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse> {
|
||||
return self.makeUnaryCall(
|
||||
path: Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.configureHosts.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeConfigureHostsInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
/// Perform the sync syscall.
|
||||
///
|
||||
/// - Parameters:
|
||||
@@ -767,6 +790,11 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncClientP
|
||||
callOptions: CallOptions?
|
||||
) -> GRPCAsyncUnaryCall<Com_Apple_Containerization_Sandbox_V3_ConfigureDnsRequest, Com_Apple_Containerization_Sandbox_V3_ConfigureDnsResponse>
|
||||
|
||||
func makeConfigureHostsCall(
|
||||
_ request: Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest,
|
||||
callOptions: CallOptions?
|
||||
) -> GRPCAsyncUnaryCall<Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest, Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse>
|
||||
|
||||
func makeSyncCall(
|
||||
_ request: Com_Apple_Containerization_Sandbox_V3_SyncRequest,
|
||||
callOptions: CallOptions?
|
||||
@@ -1052,6 +1080,18 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncClientProtoco
|
||||
)
|
||||
}
|
||||
|
||||
public func makeConfigureHostsCall(
|
||||
_ request: Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncUnaryCall<Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest, Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse> {
|
||||
return self.makeAsyncUnaryCall(
|
||||
path: Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.configureHosts.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeConfigureHostsInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
public func makeSyncCall(
|
||||
_ request: Com_Apple_Containerization_Sandbox_V3_SyncRequest,
|
||||
callOptions: CallOptions? = nil
|
||||
@@ -1343,6 +1383,18 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncClientProtoco
|
||||
)
|
||||
}
|
||||
|
||||
public func configureHosts(
|
||||
_ request: Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest,
|
||||
callOptions: CallOptions? = nil
|
||||
) async throws -> Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse {
|
||||
return try await self.performAsyncUnaryCall(
|
||||
path: Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.configureHosts.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeConfigureHostsInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
public func sync(
|
||||
_ request: Com_Apple_Containerization_Sandbox_V3_SyncRequest,
|
||||
callOptions: CallOptions? = nil
|
||||
@@ -1453,6 +1505,9 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextClientInterc
|
||||
/// - Returns: Interceptors to use when invoking 'configureDns'.
|
||||
func makeConfigureDnsInterceptors() -> [ClientInterceptor<Com_Apple_Containerization_Sandbox_V3_ConfigureDnsRequest, Com_Apple_Containerization_Sandbox_V3_ConfigureDnsResponse>]
|
||||
|
||||
/// - Returns: Interceptors to use when invoking 'configureHosts'.
|
||||
func makeConfigureHostsInterceptors() -> [ClientInterceptor<Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest, Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse>]
|
||||
|
||||
/// - Returns: Interceptors to use when invoking 'sync'.
|
||||
func makeSyncInterceptors() -> [ClientInterceptor<Com_Apple_Containerization_Sandbox_V3_SyncRequest, Com_Apple_Containerization_Sandbox_V3_SyncResponse>]
|
||||
|
||||
@@ -1487,6 +1542,7 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata {
|
||||
Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.ipRouteAddLink,
|
||||
Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.ipRouteAddDefault,
|
||||
Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.configureDns,
|
||||
Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.configureHosts,
|
||||
Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.sync,
|
||||
Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.kill,
|
||||
]
|
||||
@@ -1625,6 +1681,12 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata {
|
||||
type: GRPCCallType.unary
|
||||
)
|
||||
|
||||
public static let configureHosts = GRPCMethodDescriptor(
|
||||
name: "ConfigureHosts",
|
||||
path: "/com.apple.containerization.sandbox.v3.SandboxContext/ConfigureHosts",
|
||||
type: GRPCCallType.unary
|
||||
)
|
||||
|
||||
public static let sync = GRPCMethodDescriptor(
|
||||
name: "Sync",
|
||||
path: "/com.apple.containerization.sandbox.v3.SandboxContext/Sync",
|
||||
@@ -1712,6 +1774,9 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextProvider: Ca
|
||||
/// Configure DNS resolver.
|
||||
func configureDns(request: Com_Apple_Containerization_Sandbox_V3_ConfigureDnsRequest, context: StatusOnlyCallContext) -> EventLoopFuture<Com_Apple_Containerization_Sandbox_V3_ConfigureDnsResponse>
|
||||
|
||||
/// Configure /etc/hosts.
|
||||
func configureHosts(request: Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest, context: StatusOnlyCallContext) -> EventLoopFuture<Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse>
|
||||
|
||||
/// Perform the sync syscall.
|
||||
func sync(request: Com_Apple_Containerization_Sandbox_V3_SyncRequest, context: StatusOnlyCallContext) -> EventLoopFuture<Com_Apple_Containerization_Sandbox_V3_SyncResponse>
|
||||
|
||||
@@ -1929,6 +1994,15 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextProvider {
|
||||
userFunction: self.configureDns(request:context:)
|
||||
)
|
||||
|
||||
case "ConfigureHosts":
|
||||
return UnaryServerHandler(
|
||||
context: context,
|
||||
requestDeserializer: ProtobufDeserializer<Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest>(),
|
||||
responseSerializer: ProtobufSerializer<Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse>(),
|
||||
interceptors: self.interceptors?.makeConfigureHostsInterceptors() ?? [],
|
||||
userFunction: self.configureHosts(request:context:)
|
||||
)
|
||||
|
||||
case "Sync":
|
||||
return UnaryServerHandler(
|
||||
context: context,
|
||||
@@ -2094,6 +2168,12 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvide
|
||||
context: GRPCAsyncServerCallContext
|
||||
) async throws -> Com_Apple_Containerization_Sandbox_V3_ConfigureDnsResponse
|
||||
|
||||
/// Configure /etc/hosts.
|
||||
func configureHosts(
|
||||
request: Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest,
|
||||
context: GRPCAsyncServerCallContext
|
||||
) async throws -> Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse
|
||||
|
||||
/// Perform the sync syscall.
|
||||
func sync(
|
||||
request: Com_Apple_Containerization_Sandbox_V3_SyncRequest,
|
||||
@@ -2324,6 +2404,15 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvider {
|
||||
wrapping: { try await self.configureDns(request: $0, context: $1) }
|
||||
)
|
||||
|
||||
case "ConfigureHosts":
|
||||
return GRPCAsyncServerHandler(
|
||||
context: context,
|
||||
requestDeserializer: ProtobufDeserializer<Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest>(),
|
||||
responseSerializer: ProtobufSerializer<Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse>(),
|
||||
interceptors: self.interceptors?.makeConfigureHostsInterceptors() ?? [],
|
||||
wrapping: { try await self.configureHosts(request: $0, context: $1) }
|
||||
)
|
||||
|
||||
case "Sync":
|
||||
return GRPCAsyncServerHandler(
|
||||
context: context,
|
||||
@@ -2438,6 +2527,10 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextServerInterc
|
||||
/// Defaults to calling `self.makeInterceptors()`.
|
||||
func makeConfigureDnsInterceptors() -> [ServerInterceptor<Com_Apple_Containerization_Sandbox_V3_ConfigureDnsRequest, Com_Apple_Containerization_Sandbox_V3_ConfigureDnsResponse>]
|
||||
|
||||
/// - Returns: Interceptors to use when handling 'configureHosts'.
|
||||
/// Defaults to calling `self.makeInterceptors()`.
|
||||
func makeConfigureHostsInterceptors() -> [ServerInterceptor<Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest, Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse>]
|
||||
|
||||
/// - Returns: Interceptors to use when handling 'sync'.
|
||||
/// Defaults to calling `self.makeInterceptors()`.
|
||||
func makeSyncInterceptors() -> [ServerInterceptor<Com_Apple_Containerization_Sandbox_V3_SyncRequest, Com_Apple_Containerization_Sandbox_V3_SyncResponse>]
|
||||
@@ -2474,6 +2567,7 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata {
|
||||
Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.ipRouteAddLink,
|
||||
Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.ipRouteAddDefault,
|
||||
Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.configureDns,
|
||||
Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.configureHosts,
|
||||
Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.sync,
|
||||
Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.kill,
|
||||
]
|
||||
@@ -2612,6 +2706,12 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata {
|
||||
type: GRPCCallType.unary
|
||||
)
|
||||
|
||||
public static let configureHosts = GRPCMethodDescriptor(
|
||||
name: "ConfigureHosts",
|
||||
path: "/com.apple.containerization.sandbox.v3.SandboxContext/ConfigureHosts",
|
||||
type: GRPCCallType.unary
|
||||
)
|
||||
|
||||
public static let sync = GRPCMethodDescriptor(
|
||||
name: "Sync",
|
||||
path: "/com.apple.containerization.sandbox.v3.SandboxContext/Sync",
|
||||
|
||||
@@ -830,6 +830,66 @@ public struct Com_Apple_Containerization_Sandbox_V3_ConfigureDnsResponse: Sendab
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public struct Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
public var location: String = String()
|
||||
|
||||
public var entries: [Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest.HostsEntry] = []
|
||||
|
||||
public var comment: String {
|
||||
get {return _comment ?? String()}
|
||||
set {_comment = newValue}
|
||||
}
|
||||
/// Returns true if `comment` has been explicitly set.
|
||||
public var hasComment: Bool {return self._comment != nil}
|
||||
/// Clears the value of `comment`. Subsequent reads from it will return its default value.
|
||||
public mutating func clearComment() {self._comment = nil}
|
||||
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public struct HostsEntry: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
public var ipAddress: String = String()
|
||||
|
||||
public var hostnames: [String] = []
|
||||
|
||||
public var comment: String {
|
||||
get {return _comment ?? String()}
|
||||
set {_comment = newValue}
|
||||
}
|
||||
/// Returns true if `comment` has been explicitly set.
|
||||
public var hasComment: Bool {return self._comment != nil}
|
||||
/// Clears the value of `comment`. Subsequent reads from it will return its default value.
|
||||
public mutating func clearComment() {self._comment = nil}
|
||||
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public init() {}
|
||||
|
||||
fileprivate var _comment: String? = nil
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
||||
fileprivate var _comment: String? = nil
|
||||
}
|
||||
|
||||
public struct Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public struct Com_Apple_Containerization_Sandbox_V3_SyncRequest: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
@@ -2415,6 +2475,121 @@ extension Com_Apple_Containerization_Sandbox_V3_ConfigureDnsResponse: SwiftProto
|
||||
}
|
||||
}
|
||||
|
||||
extension Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = _protobuf_package + ".ConfigureHostsRequest"
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "location"),
|
||||
2: .same(proto: "entries"),
|
||||
3: .same(proto: "comment"),
|
||||
]
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularStringField(value: &self.location) }()
|
||||
case 2: try { try decoder.decodeRepeatedMessageField(value: &self.entries) }()
|
||||
case 3: try { try decoder.decodeSingularStringField(value: &self._comment) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every if/case branch local when no optimizations
|
||||
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||
// https://github.com/apple/swift-protobuf/issues/1182
|
||||
if !self.location.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.location, fieldNumber: 1)
|
||||
}
|
||||
if !self.entries.isEmpty {
|
||||
try visitor.visitRepeatedMessageField(value: self.entries, fieldNumber: 2)
|
||||
}
|
||||
try { if let v = self._comment {
|
||||
try visitor.visitSingularStringField(value: v, fieldNumber: 3)
|
||||
} }()
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest, rhs: Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest) -> Bool {
|
||||
if lhs.location != rhs.location {return false}
|
||||
if lhs.entries != rhs.entries {return false}
|
||||
if lhs._comment != rhs._comment {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest.HostsEntry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest.protoMessageName + ".HostsEntry"
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "ipAddress"),
|
||||
2: .same(proto: "hostnames"),
|
||||
3: .same(proto: "comment"),
|
||||
]
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularStringField(value: &self.ipAddress) }()
|
||||
case 2: try { try decoder.decodeRepeatedStringField(value: &self.hostnames) }()
|
||||
case 3: try { try decoder.decodeSingularStringField(value: &self._comment) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every if/case branch local when no optimizations
|
||||
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||
// https://github.com/apple/swift-protobuf/issues/1182
|
||||
if !self.ipAddress.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.ipAddress, fieldNumber: 1)
|
||||
}
|
||||
if !self.hostnames.isEmpty {
|
||||
try visitor.visitRepeatedStringField(value: self.hostnames, fieldNumber: 2)
|
||||
}
|
||||
try { if let v = self._comment {
|
||||
try visitor.visitSingularStringField(value: v, fieldNumber: 3)
|
||||
} }()
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest.HostsEntry, rhs: Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest.HostsEntry) -> Bool {
|
||||
if lhs.ipAddress != rhs.ipAddress {return false}
|
||||
if lhs.hostnames != rhs.hostnames {return false}
|
||||
if lhs._comment != rhs._comment {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = _protobuf_package + ".ConfigureHostsResponse"
|
||||
public static let _protobuf_nameMap = SwiftProtobuf._NameMap()
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
// Load everything into unknown fields
|
||||
while try decoder.nextFieldNumber() != nil {}
|
||||
}
|
||||
|
||||
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse, rhs: Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse) -> Bool {
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension Com_Apple_Containerization_Sandbox_V3_SyncRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = _protobuf_package + ".SyncRequest"
|
||||
public static let _protobuf_nameMap = SwiftProtobuf._NameMap()
|
||||
|
||||
@@ -52,6 +52,8 @@ service SandboxContext {
|
||||
rpc IpRouteAddDefault(IpRouteAddDefaultRequest) returns (IpRouteAddDefaultResponse);
|
||||
// Configure DNS resolver.
|
||||
rpc ConfigureDns(ConfigureDnsRequest) returns (ConfigureDnsResponse);
|
||||
// Configure /etc/hosts.
|
||||
rpc ConfigureHosts(ConfigureHostsRequest) returns (ConfigureHostsResponse);
|
||||
// Perform the sync syscall.
|
||||
rpc Sync(SyncRequest) returns (SyncResponse);
|
||||
// Send a signal to a process via the PID.
|
||||
@@ -239,6 +241,19 @@ message ConfigureDnsRequest {
|
||||
|
||||
message ConfigureDnsResponse {}
|
||||
|
||||
message ConfigureHostsRequest {
|
||||
message HostsEntry {
|
||||
string ipAddress = 1;
|
||||
repeated string hostnames = 2;
|
||||
optional string comment = 3;
|
||||
}
|
||||
string location = 1;
|
||||
repeated HostsEntry entries = 2;
|
||||
optional string comment = 3;
|
||||
}
|
||||
|
||||
message ConfigureHostsResponse {}
|
||||
|
||||
message SyncRequest {}
|
||||
message SyncResponse {}
|
||||
|
||||
|
||||
@@ -60,10 +60,15 @@ public protocol VirtualMachineAgent: Sendable {
|
||||
func addressAdd(name: String, address: String) async throws
|
||||
func routeAddDefault(name: String, gateway: String) async throws
|
||||
func configureDNS(config: DNS, location: String) async throws
|
||||
func configureHosts(config: Hosts, location: String) async throws
|
||||
}
|
||||
|
||||
extension VirtualMachineAgent {
|
||||
public func closeProcessStdin(id: String, containerID: String?) async throws {
|
||||
throw ContainerizationError(.unsupported, message: "closeProcessStdin")
|
||||
}
|
||||
|
||||
public func configureHosts(config: Hosts, location: String) async throws {
|
||||
throw ContainerizationError(.unsupported, message: "configureHosts")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,6 +418,39 @@ extension IntegrationSuite {
|
||||
}
|
||||
}
|
||||
|
||||
func testHostsFile() async throws {
|
||||
let id = "test-container-hosts-file"
|
||||
|
||||
let bs = try await bootstrap()
|
||||
let container = LinuxContainer(
|
||||
id,
|
||||
rootfs: bs.rootfs,
|
||||
vmm: bs.vmm
|
||||
)
|
||||
container.arguments = ["cat", "/etc/hosts"]
|
||||
let entry = Hosts.Entry.localHostIPV4(comment: "Testaroo")
|
||||
container.hosts = Hosts(entries: [entry])
|
||||
|
||||
let buffer = BufferWriter()
|
||||
container.stdout = buffer
|
||||
|
||||
try await container.create()
|
||||
try await container.start()
|
||||
|
||||
let status = try await container.wait()
|
||||
try await container.stop()
|
||||
|
||||
guard status == 0 else {
|
||||
throw IntegrationError.assert(msg: "process status \(status) != 0")
|
||||
}
|
||||
|
||||
let expected = entry.rendered
|
||||
guard String(data: buffer.data, encoding: .utf8) == "\(expected)\n" else {
|
||||
throw IntegrationError.assert(
|
||||
msg: "process should have returned on stdout '\(expected)' != '\(String(data: buffer.data, encoding: .utf8)!)'")
|
||||
}
|
||||
}
|
||||
|
||||
func testProcessStdin() async throws {
|
||||
let id = "test-container-stdin"
|
||||
|
||||
|
||||
@@ -206,6 +206,7 @@ struct IntegrationSuite: AsyncParsableCommand {
|
||||
"multiple concurrent processes": testMultipleConcurrentProcesses,
|
||||
"multiple concurrent processes with output stress": testMultipleConcurrentProcessesOutputStress,
|
||||
"container hostname": testHostname,
|
||||
"container hosts": testHostsFile,
|
||||
"container mount": testMounts,
|
||||
"nested virt": testNestedVirtualizationEnabled,
|
||||
]
|
||||
|
||||
@@ -119,6 +119,7 @@ extension Application {
|
||||
container.mounts.append(czMount)
|
||||
}
|
||||
|
||||
var hosts = Hosts.default
|
||||
if let ip {
|
||||
guard let gateway else {
|
||||
throw ContainerizationError(.invalidArgument, message: "gateway must be specified")
|
||||
@@ -128,7 +129,13 @@ extension Application {
|
||||
if nameservers.count > 0 {
|
||||
container.dns = .init(nameservers: nameservers)
|
||||
}
|
||||
hosts.entries.append(
|
||||
Hosts.Entry(
|
||||
ipAddress: ip,
|
||||
hostnames: [id]
|
||||
))
|
||||
}
|
||||
container.hosts = hosts
|
||||
|
||||
try await container.create()
|
||||
try await container.start()
|
||||
|
||||
@@ -794,6 +794,38 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid
|
||||
return .init()
|
||||
}
|
||||
|
||||
func configureHosts(
|
||||
request: Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest,
|
||||
context: GRPC.GRPCAsyncServerCallContext
|
||||
) async throws -> Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse {
|
||||
log.debug(
|
||||
"configureHosts",
|
||||
metadata: [
|
||||
"location": "\(request.location)"
|
||||
])
|
||||
|
||||
do {
|
||||
let etc = URL(fileURLWithPath: request.location).appendingPathComponent("etc")
|
||||
try FileManager.default.createDirectory(atPath: etc.path, withIntermediateDirectories: true)
|
||||
let hostsPath = etc.appendingPathComponent("hosts")
|
||||
|
||||
let config = request.toCZHosts()
|
||||
let text = config.hostsFile
|
||||
try text.write(toFile: hostsPath.path, atomically: true, encoding: .utf8)
|
||||
|
||||
log.debug("wrote /etc/hosts configuration", metadata: ["path": "\(hostsPath.path)"])
|
||||
} catch {
|
||||
log.error(
|
||||
"configureHosts",
|
||||
metadata: [
|
||||
"error": "\(error)"
|
||||
])
|
||||
throw GRPCStatus(code: .internalError, message: "configureHosts: \(error)")
|
||||
}
|
||||
|
||||
return .init()
|
||||
}
|
||||
|
||||
private func swiftErrno(_ msg: Logger.Message) -> POSIXError {
|
||||
let error = POSIXError(.init(rawValue: errno)!)
|
||||
log.error(
|
||||
@@ -832,6 +864,22 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid
|
||||
}
|
||||
}
|
||||
|
||||
extension Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest {
|
||||
func toCZHosts() -> Hosts {
|
||||
let entries = self.entries.map {
|
||||
Hosts.Entry(
|
||||
ipAddress: $0.ipAddress,
|
||||
hostnames: $0.hostnames,
|
||||
comment: $0.hasComment ? $0.comment : nil
|
||||
)
|
||||
}
|
||||
return Hosts(
|
||||
entries: entries,
|
||||
comment: self.hasComment ? self.comment : nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension Initd {
|
||||
func ociAlterations(ociSpec: inout ContainerizationOCI.Spec) throws {
|
||||
guard var process = ociSpec.process else {
|
||||
|
||||
Reference in New Issue
Block a user