1
1
import Foundation
2
+ import Network
2
3
import NIO
3
4
4
5
extension DockerClient {
@@ -36,10 +37,31 @@ extension DockerClient {
36
37
/// - Parameters:
37
38
/// - image: Instance of an `Image`.
38
39
/// - commands: Override the default commands from the image. Default `nil`.
40
+ /// - portBindings: Port bindings (forwardings). See ``PortBinding`` for details. Default `[]`.
39
41
/// - Throws: Errors that can occur when executing the request.
40
42
/// - Returns: Returns an `EventLoopFuture` of a `Container`.
41
- public func createContainer( image: Image , commands: [ String ] ? = nil ) throws -> EventLoopFuture < Container > {
42
- return try client. run ( CreateContainerEndpoint ( imageName: image. id. value, commands: commands) )
43
+ public func createContainer( image: Image , commands: [ String ] ? = nil , portBindings: [ PortBinding ] = [ ] ) throws -> EventLoopFuture < Container > {
44
+ let hostConfig : CreateContainerEndpoint . CreateContainerBody . HostConfig ?
45
+ let exposedPorts : [ String : CreateContainerEndpoint . CreateContainerBody . Empty ] ?
46
+ if portBindings. isEmpty {
47
+ exposedPorts = nil
48
+ hostConfig = nil
49
+ } else {
50
+ var exposedPortsBuilder : [ String : CreateContainerEndpoint . CreateContainerBody . Empty ] = [ : ]
51
+ var portBindingsByContainerPort : [ String : [ CreateContainerEndpoint . CreateContainerBody . HostConfig . PortBinding ] ] = [ : ]
52
+ for portBinding in portBindings {
53
+ let containerPort : String = " \( portBinding. containerPort) / \( portBinding. networkProtocol) "
54
+
55
+ exposedPortsBuilder [ containerPort] = CreateContainerEndpoint . CreateContainerBody. Empty ( )
56
+ var hostAddresses = portBindingsByContainerPort [ containerPort, default: [ ] ]
57
+ hostAddresses. append (
58
+ CreateContainerEndpoint . CreateContainerBody. HostConfig. PortBinding ( HostIp: " \( portBinding. hostIP) " , HostPort: " \( portBinding. hostPort) " ) )
59
+ portBindingsByContainerPort [ containerPort] = hostAddresses
60
+ }
61
+ exposedPorts = exposedPortsBuilder
62
+ hostConfig = CreateContainerEndpoint . CreateContainerBody. HostConfig ( PortBindings: portBindingsByContainerPort)
63
+ }
64
+ return try client. run ( CreateContainerEndpoint ( imageName: image. id. value, commands: commands, exposedPorts: exposedPorts, hostConfig: hostConfig) )
43
65
. flatMap ( { response in
44
66
try self . get ( containerByNameOrId: response. Id)
45
67
} )
@@ -48,10 +70,36 @@ extension DockerClient {
48
70
/// Starts a container. Before starting it needs to be created.
49
71
/// - Parameter container: Instance of a created `Container`.
50
72
/// - Throws: Errors that can occur when executing the request.
51
- /// - Returns: Returns an `EventLoopFuture` when the container is started.
52
- public func start( container: Container ) throws -> EventLoopFuture < Void > {
73
+ /// - Returns: Returns an `EventLoopFuture` of active actual `PortBinding`s when the container is started.
74
+ public func start( container: Container ) throws -> EventLoopFuture < [ PortBinding ] > {
53
75
return try client. run ( StartContainerEndpoint ( containerId: container. id. value) )
54
- . map ( { _ in Void ( ) } )
76
+ . flatMap { _ in
77
+ try client. run ( InspectContainerEndpoint ( nameOrId: container. id. value) )
78
+ . flatMapThrowing { response in
79
+ try response. NetworkSettings. Ports. flatMap { ( containerPortSpec, bindings) in
80
+ let containerPortParts = containerPortSpec. split ( separator: " / " , maxSplits: 2 )
81
+ guard
82
+ let containerPort: UInt16 = UInt16 ( containerPortParts [ 0 ] ) ,
83
+ let networkProtocol: NetworkProtocol = NetworkProtocol ( rawValue: String ( containerPortParts [ 1 ] ) )
84
+ else { throw DockerError . message ( #"unable to parse port/protocol from NetworkSettings.Ports key - " \#( containerPortSpec) ""# ) }
85
+
86
+ return try ( bindings ?? [ ] ) . compactMap { binding in
87
+ guard
88
+ let hostIP: IPAddress = IPv4Address ( binding. HostIp) ?? IPv6Address ( binding. HostIp)
89
+ else {
90
+ throw DockerError . message ( #"unable to parse IPAddress from NetworkSettings.Ports[].HostIp - " \#( binding. HostIp) ""# )
91
+ }
92
+ guard
93
+ let hostPort = UInt16 ( binding. HostPort)
94
+ else {
95
+ throw DockerError . message ( #"unable to parse port number from NetworkSettings.Ports[].HostPort - " \#( binding. HostPort) ""# )
96
+ }
97
+
98
+ return PortBinding ( hostIP: hostIP, hostPort: hostPort, containerPort: containerPort, networkProtocol: networkProtocol)
99
+ }
100
+ }
101
+ }
102
+ }
55
103
}
56
104
57
105
/// Stops a container. Before stopping it needs to be created and started..
@@ -105,7 +153,7 @@ extension DockerClient {
105
153
repositoryTag = repoTag
106
154
}
107
155
let image = Image ( id: . init( response. Image) , digest: digest, repositoryTags: repositoryTag. map ( { [ $0] } ) , createdAt: nil )
108
- return Container ( id: . init( response. Id) , image: image, createdAt: Date . parseDockerDate ( response. Created) !, names: [ response. Name] , state: response. State. Status, command: response. Config. Cmd. joined ( separator: " " ) )
156
+ return Container ( id: . init( response. Id) , image: image, createdAt: Date . parseDockerDate ( response. Created) !, names: [ response. Name] , state: response. State. Status, command: ( response. Config. Cmd ?? [ ] ) . joined ( separator: " " ) )
109
157
}
110
158
}
111
159
@@ -134,7 +182,7 @@ extension Container {
134
182
/// - Parameter client: A `DockerClient` instance that is used to perform the request.
135
183
/// - Throws: Errors that can occur when executing the request.
136
184
/// - Returns: Returns an `EventLoopFuture` when the container is started.
137
- public func start( on client: DockerClient ) throws -> EventLoopFuture < Void > {
185
+ public func start( on client: DockerClient ) throws -> EventLoopFuture < [ PortBinding ] > {
138
186
try client. containers. start ( container: self )
139
187
}
140
188
0 commit comments