forked from alexsteinerde/docker-client-swift
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 8283d48
Showing
29 changed files
with
1,307 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
.DS_Store | ||
/.build | ||
/Packages | ||
/*.xcodeproj | ||
xcuserdata/ | ||
Package.resolved |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2021 Alexander Steiner | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// swift-tools-version:5.3 | ||
// The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "docker-client-swift", | ||
platforms: [.macOS(.v10_15)], | ||
products: [ | ||
.library(name: "DockerClient", targets: ["DockerClient"]), | ||
], | ||
dependencies: [ | ||
.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"), | ||
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.0.0"), | ||
], | ||
targets: [ | ||
.target( | ||
name: "DockerClient", | ||
dependencies: [ | ||
.product(name: "NIO", package: "swift-nio"), | ||
.product(name: "AsyncHTTPClient", package: "async-http-client"), | ||
]), | ||
.testTarget( | ||
name: "DockerClientTests", | ||
dependencies: ["DockerClient"] | ||
), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# Docker Client | ||
[![Language](https://img.shields.io/badge/Swift-5.4-brightgreen.svg)](http://swift.org) | ||
[![Docker Engine API](https://img.shields.io/badge/Docker%20Engine%20API-%20%201.4.1-blue)](https://docs.docker.com/engine/api/v1.41/) | ||
|
||
This is a Docker Client written in Swift. It's using the NIO Framework to communicate with the Docker Engine via sockets. | ||
|
||
## Current Use Cases | ||
- [x] List of all images | ||
- [x] List of all containers | ||
- [x] Pull an image | ||
- [x] Create a new container from an image | ||
- [x] Start a container | ||
- [x] Get the stdOut and stdErr output of a container | ||
- [x] Get the docker version information | ||
|
||
|
||
## Installation | ||
```Swift | ||
import PackageDescription | ||
|
||
let package = Package( | ||
dependencies: [ | ||
.package(url: "https://github.com/alexsteinerde/docker-client-swift.git", from: "0.1.0"), | ||
], | ||
targets: [ | ||
.target(name: "App", dependencies: ["DockerClient"]), | ||
... | ||
] | ||
) | ||
``` | ||
|
||
## Usage Example | ||
```swift | ||
let client = DockerClient() | ||
let image = try client.images.pullImage(imageName: "hello-world:latest").wait() | ||
let container = try! client.containers.createContainer(image: image).wait() | ||
try container.start(on: client).wait() | ||
let output = try container.logs(on: client).wait() | ||
print(output) | ||
``` | ||
|
||
For further usage examples, please consider looking at the provided test cases. | ||
|
||
## Security Advice | ||
When using this in production, make sure you secure your appclication so no others can execute code. Otherwise the attacker could access your Docker environment and so all of the containers running in it. | ||
|
||
## License | ||
This project is released under the MIT license. See [LICENSE](LICENSE) for details. | ||
|
||
## Contribution | ||
You can contribute to this project by submitting a detailed issue or by forking this project and sending a pull request. Contributions of any kind are very welcome :) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import Foundation | ||
import NIO | ||
|
||
extension DockerClient { | ||
|
||
/// APIs related to containers. | ||
public var containers: ContainersAPI { | ||
ContainersAPI(client: self) | ||
} | ||
|
||
public struct ContainersAPI { | ||
fileprivate var client: DockerClient | ||
|
||
public func list(all: Bool=false) throws -> EventLoopFuture<[Container]> { | ||
try client.run(ListContainersEndpoint(all: all)) | ||
.map({ containers in | ||
containers.map { container in | ||
Container(id: .init(container.Id), image: Image(id: .init(container.Image), digest: nil), createdAt: Date(timeIntervalSince1970: TimeInterval(container.Created))) | ||
} | ||
}) | ||
} | ||
|
||
public func createContainer(image: Image, commands: [String]?=nil) throws -> EventLoopFuture<Container> { | ||
var id = image.id.value | ||
if let tag = image.repositoryTags.first?.tag { | ||
id += ":\(tag)" | ||
} | ||
if let digest = image.digest { | ||
id += "@\(digest.rawValue)" | ||
} | ||
return try client.run(CreateContainerEndpoint(imageName: id, commands: commands)) | ||
.map({ response in | ||
Container(id: .init(response.Id), image: image, createdAt: .init()) | ||
}) | ||
} | ||
|
||
public func start(container: Container) throws -> EventLoopFuture<Void> { | ||
return try client.run(StartContainerEndpoint(containerId: container.id.value)) | ||
.map({ _ in Void() }) | ||
} | ||
|
||
public func logs(container: Container) throws -> EventLoopFuture<String> { | ||
try client.run(GetContainerLogsEndpoint(containerId: container.id.value)) | ||
.map({ response in | ||
// Removing the first character of each line because random characters went there | ||
response.split(separator: "\n") | ||
.map({ originalLine in | ||
var line = originalLine | ||
line.removeFirst(8) | ||
return String(line) | ||
}) | ||
.joined(separator: "\n") | ||
}) | ||
} | ||
} | ||
} | ||
|
||
extension Container { | ||
public func start(on client: DockerClient) throws -> EventLoopFuture<Void> { | ||
try client.containers.start(container: self) | ||
} | ||
|
||
public func logs(on client: DockerClient) throws -> EventLoopFuture<String> { | ||
try client.containers.logs(container: self) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import Foundation | ||
import NIO | ||
|
||
extension DockerClient { | ||
|
||
/// APIs related to images. | ||
public var images: ImagesAPI { | ||
ImagesAPI(client: self) | ||
} | ||
|
||
public struct ImagesAPI { | ||
fileprivate var client: DockerClient | ||
|
||
public func pullImage(byName name: Identifier<Image>, tag: String?=nil, digest: Digest?=nil) throws -> EventLoopFuture<Image> { | ||
var identifier = name.value | ||
if let tag = tag { | ||
identifier += ":\(tag)" | ||
} | ||
if let digest = digest { | ||
identifier += "@\(digest.rawValue)" | ||
} | ||
return try client.run(PullImageEndpoint(imageName: identifier)) | ||
.map({ response in | ||
Image(id: name, digest: .init(response.digest), repoTags: tag.map({ ["\(name.value):\($0)"] })) | ||
}) | ||
} | ||
|
||
public func list(all: Bool=false) throws -> EventLoopFuture<[Image]> { | ||
try client.run(ListImagesEndpoint(all: all)) | ||
.map({ images in | ||
images.map { image in | ||
Image(id: .init(image.Id), digest: image.RepoDigests?.first.map({ Digest.init($0) }), repoTags: image.RepoTags, createdAt: Date(timeIntervalSince1970: TimeInterval(image.Created))) | ||
} | ||
}) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import NIO | ||
|
||
extension DockerClient { | ||
public func version() throws -> EventLoopFuture<DockerVersion> { | ||
try run(VersionEndpoint()) | ||
.map({ response in | ||
DockerVersion(version: response.version, architecture: response.arch, kernelVersion: response.kernelVersion, minAPIVersion: response.minAPIVersion, os: response.os) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import Foundation | ||
import NIO | ||
import NIOHTTP1 | ||
import AsyncHTTPClient | ||
import Logging | ||
|
||
/// The entry point for docker client commands. | ||
public class DockerClient { | ||
private let daemonSocket: String | ||
private let client: HTTPClient | ||
private let logger: Logger | ||
|
||
public init(daemonSocket: String = "/var/run/docker.sock", client: HTTPClient = .init(eventLoopGroupProvider: .createNew), logger: Logger = .init(label: "docker-client")) { | ||
self.daemonSocket = daemonSocket | ||
self.client = client | ||
self.logger = logger | ||
} | ||
|
||
deinit { | ||
try? client.syncShutdown() | ||
} | ||
|
||
func run<T: Endpoint>(_ endpoint: T) throws -> EventLoopFuture<T.Response> { | ||
logger.info("Execute: \(endpoint.path)") | ||
return client.execute(endpoint.method, socketPath: daemonSocket, urlPath: "/v1.40/\(endpoint.path)", body: endpoint.body.map {HTTPClient.Body.data( try! $0.encode())}, logger: logger, headers: HTTPHeaders([("Content-Type", "application/json")])) | ||
.logResponseBody(logger) | ||
.decode(as: T.Response.self) | ||
} | ||
|
||
func run<T: PipelineEndpoint>(_ endpoint: T) throws -> EventLoopFuture<T.Response> { | ||
client.execute(endpoint.method, socketPath: daemonSocket, urlPath: "/v1.40/\(endpoint.path)", body: try endpoint.body.map({ HTTPClient.Body.data( try $0.encode()) })) | ||
.logResponseBody(logger) | ||
.mapString(map: endpoint.map(data: )) | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
Sources/DockerClient/Endpoints/CreateContainerEndpoint.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import NIOHTTP1 | ||
|
||
struct CreateContainerEndpoint: Endpoint { | ||
var body: CreateContainerBody? | ||
|
||
typealias Response = CreateContainerResponse | ||
typealias Body = CreateContainerBody | ||
var method: HTTPMethod = .POST | ||
|
||
private let imageName: String | ||
private let commands: [String]? | ||
|
||
init(imageName: String, commands: [String]?=nil) { | ||
self.imageName = imageName | ||
self.commands = commands | ||
self.body = .init(Image: imageName, Cmd: commands) | ||
} | ||
|
||
var path: String { | ||
"containers/create" | ||
} | ||
|
||
struct CreateContainerBody: Codable { | ||
let Image: String | ||
let Cmd: [String]? | ||
} | ||
|
||
struct CreateContainerResponse: Codable { | ||
let Id: String | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
Sources/DockerClient/Endpoints/GetContainerLogsEndpoint.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import NIOHTTP1 | ||
|
||
struct GetContainerLogsEndpoint: Endpoint { | ||
typealias Body = NoBody | ||
|
||
typealias Response = String | ||
var method: HTTPMethod = .GET | ||
|
||
private let containerId: String | ||
|
||
init(containerId: String) { | ||
self.containerId = containerId | ||
} | ||
|
||
var path: String { | ||
"containers/\(containerId)/logs?stdout=true&stderr=true" | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
Sources/DockerClient/Endpoints/ListContainersEndpoint.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import NIOHTTP1 | ||
|
||
struct ListContainersEndpoint: Endpoint { | ||
typealias Body = NoBody | ||
typealias Response = [ContainerResponse] | ||
var method: HTTPMethod = .GET | ||
|
||
private var all: Bool | ||
|
||
init(all: Bool) { | ||
self.all = all | ||
} | ||
|
||
var path: String { | ||
"containers/json?all=\(all)" | ||
} | ||
|
||
struct ContainerResponse: Codable { | ||
let Id: String | ||
let Names: [String] | ||
let Image: String | ||
let ImageID: String | ||
let Command: String | ||
let Created: Int | ||
let State: String | ||
let Status: String | ||
// TODO: Add additional fields | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import NIOHTTP1 | ||
|
||
struct ListImagesEndpoint: Endpoint { | ||
typealias Body = NoBody | ||
typealias Response = [ImageResponse] | ||
var method: HTTPMethod = .GET | ||
|
||
private var all: Bool | ||
|
||
init(all: Bool) { | ||
self.all = all | ||
} | ||
|
||
var path: String { | ||
"images/json?all=\(all)" | ||
} | ||
|
||
struct ImageResponse: Codable { | ||
let Id: String | ||
let ParentId: String | ||
let RepoTags: [String]? | ||
let RepoDigests: [String]? | ||
let Created: Int | ||
let Size: Int | ||
let VirtualSize: Int | ||
let SharedSize: Int | ||
let Containers: Int | ||
// TODO: Add additional fields | ||
} | ||
} |
Oops, something went wrong.