-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from appswithlove/feature/gitlab
GitLab Runner Support
- Loading branch information
Showing
8 changed files
with
308 additions
and
1 deletion.
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
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,12 @@ | ||
import Foundation | ||
|
||
struct GitlabProvisionerConfig: Decodable { | ||
/// The name by which the runner can be identified | ||
let name: String | ||
/// The url to register the runner at. In a self-hosted environment, this is probably your main GitLab URL, e.g. https://gitlab.yourdomain.net/ | ||
let url: URL | ||
/// The runner registration token, can be obtained in the GitLab runner UI | ||
let registrationToken: String | ||
/// A list of tags to apply to the runner, comma-separated | ||
let tagList: String | ||
} |
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
79 changes: 79 additions & 0 deletions
79
Cilicon/Provisioner/Gitlab Runner/GitlabRunnerProvisioner.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,79 @@ | ||
import Foundation | ||
|
||
class GitlabRunnerProvisioner: Provisioner { | ||
let config: Config | ||
let runnerConfig: GitlabProvisionerConfig | ||
let service: GitlabService | ||
let fileManager: FileManager | ||
|
||
private var runnerToken: String? | ||
|
||
init(config: Config, gitlabConfig: GitlabProvisionerConfig, fileManager: FileManager = .default) { | ||
self.config = config | ||
self.runnerConfig = gitlabConfig | ||
self.service = GitlabService(config: gitlabConfig) | ||
self.fileManager = fileManager | ||
} | ||
|
||
func provision(bundle: VMBundle) async throws { | ||
let registration = try await service.registerRunner() | ||
try setRunnerEndpointURL(bundle: bundle, url: runnerConfig.url) | ||
try setRunnerToken(bundle: bundle, token: registration.token) | ||
self.runnerToken = registration.token | ||
} | ||
|
||
func deprovision(bundle: VMBundle) async throws { | ||
if let runnerToken { | ||
try await service.deregisterRunner(runnerToken: runnerToken) | ||
} else { | ||
print("Nothing to deregister, skipping...") | ||
} | ||
return | ||
} | ||
|
||
private func setRunnerEndpointURL(bundle: VMBundle, url: URL) throws { | ||
let tokenPath = bundle.runnerEndpointURL.relativePath | ||
guard fileManager.createFile(atPath: tokenPath, contents: url.absoluteString.data(using: .utf8)) else { | ||
throw GitlabRunnerProvisioner.Error.couldNotCreateRunnerTokenFile(path: tokenPath) | ||
} | ||
} | ||
|
||
private func setRunnerToken(bundle: VMBundle, token: String) throws { | ||
let tokenPath = bundle.runnerTokenURL.relativePath | ||
guard fileManager.createFile(atPath: tokenPath, contents: token.data(using: .utf8)) else { | ||
throw GitlabRunnerProvisioner.Error.couldNotCreateRunnerTokenFile(path: tokenPath) | ||
} | ||
} | ||
} | ||
|
||
extension GitlabRunnerProvisioner { | ||
enum Error: Swift.Error { | ||
case couldNotCreateRunnerTokenFile(path: String) | ||
case couldNotCreateRunnerEndpointFile(path: String) | ||
case invalidConfiguration(reason: String) | ||
} | ||
} | ||
|
||
extension GitlabRunnerProvisioner.Error: LocalizedError { | ||
var errorDescription: String? { | ||
switch self { | ||
case let .couldNotCreateRunnerTokenFile(path): | ||
return "Could not create Runner Token File at \(path)" | ||
case let .couldNotCreateRunnerEndpointFile(path): | ||
return "Could not create Runner Endpoint File at \(path)" | ||
case let .invalidConfiguration(reason): | ||
return "Configuration invalid: \(reason)" | ||
} | ||
} | ||
} | ||
|
||
|
||
fileprivate extension VMBundle { | ||
var runnerTokenURL: URL { | ||
resourcesURL.appending(component: "RUNNER_TOKEN") | ||
} | ||
|
||
var runnerEndpointURL: URL { | ||
resourcesURL.appending(component: "RUNNER_ENDPOINT_URL") | ||
} | ||
} |
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,147 @@ | ||
import Foundation | ||
|
||
class GitlabService { | ||
private let urlSession: URLSession | ||
let config: GitlabProvisionerConfig | ||
let baseURL: URL | ||
|
||
init(config: GitlabProvisionerConfig) { | ||
self.config = config | ||
self.baseURL = config.url | ||
let config = URLSessionConfiguration.default | ||
config.waitsForConnectivity = true | ||
self.urlSession = URLSession(configuration: config) | ||
} | ||
|
||
private func apiURL() -> URL { | ||
baseURL | ||
.appendingPathComponent("api") | ||
.appendingPathComponent("v4") | ||
} | ||
|
||
private func runnersURL() -> URL { | ||
apiURL() | ||
.appendingPathComponent("runners") | ||
} | ||
} | ||
|
||
// MARK: Methods | ||
|
||
extension GitlabService { | ||
func registerRunner() async throws -> RunnerRegistrationResponse { | ||
let registration = RunnerRegistration(registrationToken: config.registrationToken, | ||
description: config.name, | ||
tags: config.tagList.components(separatedBy: ",")) | ||
let jsonData = try encode(registration) | ||
let (data, response) = try await postRequest(to: runnersURL(), jsonData: jsonData) | ||
|
||
guard let httpResponse = response as? HTTPURLResponse else { | ||
throw Error.couldNotRegisterRunner(reason: "Expected a HTTP Response, got \(response)") | ||
} | ||
|
||
guard httpResponse.statusCode == 201 else { | ||
throw Error.couldNotRegisterRunner(reason: "Got response code \(httpResponse.statusCode), expected to receive 201 instead.") | ||
} | ||
|
||
guard let registrationResponse: RunnerRegistrationResponse = try? decode(data) else { | ||
throw Error.couldNotRegisterRunner(reason: "Could not decode the response") | ||
} | ||
|
||
return registrationResponse | ||
} | ||
|
||
func deregisterRunner(runnerToken token: String) async throws { | ||
let deletion = RunnerDeletion(token: token) | ||
let jsonData = try encode(deletion) | ||
|
||
let (_, response) = try await deleteRequest(to: runnersURL(), jsonData: jsonData) | ||
|
||
guard let httpResponse = response as? HTTPURLResponse else { | ||
throw Error.couldNotDeleteRunner(reason: "Expected a HTTP Response, got \(response)") | ||
} | ||
|
||
guard httpResponse.statusCode == 204 else { | ||
throw Error.couldNotDeleteRunner(reason: "Got response code \(httpResponse.statusCode), expected to receive 204 instead.") | ||
} | ||
} | ||
} | ||
|
||
// MARK: Requests | ||
|
||
private extension GitlabService { | ||
private func postRequest(to url: URL, jsonData: Data) async throws -> (Data, URLResponse) { | ||
return try await makeRequest(to: url, method: "POST", jsonData: jsonData) | ||
} | ||
|
||
private func deleteRequest(to url: URL, jsonData: Data) async throws -> (Data, URLResponse) { | ||
return try await makeRequest(to: url, method: "DELETE", jsonData: jsonData) | ||
} | ||
|
||
private func makeRequest(to url: URL, method: String, jsonData: Data) async throws -> (Data, URLResponse) { | ||
var request = URLRequest(url: url) | ||
request.httpMethod = method | ||
request.setValue("application/json", forHTTPHeaderField: "Content-Type") | ||
request.httpBody = jsonData | ||
|
||
return try await urlSession.data(for: request) | ||
} | ||
} | ||
|
||
// MARK: Codable | ||
|
||
private extension GitlabService { | ||
private func encode(_ encodable: Encodable) throws -> Data { | ||
let encoder = JSONEncoder() | ||
return try encoder.encode(encodable) | ||
} | ||
|
||
private func decode<T: Decodable>(_ decodable: Data) throws -> T { | ||
let decoder = JSONDecoder() | ||
decoder.dateDecodingStrategy = .iso8601 | ||
return try decoder.decode(T.self, from: decodable) | ||
} | ||
} | ||
|
||
// MARK: Models | ||
|
||
extension GitlabService { | ||
private struct RunnerRegistration: Codable { | ||
let registrationToken: String | ||
let description: String | ||
let tags: [String] | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case registrationToken = "token" | ||
case description | ||
case tags = "tag_list" | ||
} | ||
} | ||
|
||
public struct RunnerRegistrationResponse: Decodable { | ||
let id: Int | ||
let token: String | ||
} | ||
|
||
private struct RunnerDeletion: Codable { | ||
let token: String | ||
} | ||
} | ||
|
||
extension GitlabService { | ||
enum Error: Swift.Error { | ||
case couldNotRegisterRunner(reason: String) | ||
case couldNotDeleteRunner(reason: String) | ||
} | ||
} | ||
|
||
extension GitlabService.Error: LocalizedError { | ||
var errorDescription: String? { | ||
switch self { | ||
case .couldNotRegisterRunner(let reason): | ||
return "Could not register runner: \(reason)" | ||
case .couldNotDeleteRunner(let reason): | ||
return "Could not delete runner: \(reason)" | ||
} | ||
} | ||
} | ||
|
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
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
Oops, something went wrong.