Basic pagination support
This commit is contained in:
parent
fa81cfdf2c
commit
1b2c7472fa
|
@ -34,20 +34,6 @@
|
||||||
ReferencedContainer = "container:">
|
ReferencedContainer = "container:">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildActionEntry>
|
</BuildActionEntry>
|
||||||
<BuildActionEntry
|
|
||||||
buildForTesting = "YES"
|
|
||||||
buildForRunning = "NO"
|
|
||||||
buildForProfiling = "NO"
|
|
||||||
buildForArchiving = "NO"
|
|
||||||
buildForAnalyzing = "NO">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "BuildkiteIntegrationTests"
|
|
||||||
BuildableName = "BuildkiteIntegrationTests"
|
|
||||||
BlueprintName = "BuildkiteIntegrationTests"
|
|
||||||
ReferencedContainer = "container:">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildActionEntry>
|
|
||||||
</BuildActionEntries>
|
</BuildActionEntries>
|
||||||
</BuildAction>
|
</BuildAction>
|
||||||
<TestAction
|
<TestAction
|
||||||
|
@ -67,16 +53,6 @@
|
||||||
ReferencedContainer = "container:">
|
ReferencedContainer = "container:">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</TestableReference>
|
</TestableReference>
|
||||||
<TestableReference
|
|
||||||
skipped = "NO">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "BuildkiteIntegrationTests"
|
|
||||||
BuildableName = "BuildkiteIntegrationTests"
|
|
||||||
BlueprintName = "BuildkiteIntegrationTests"
|
|
||||||
ReferencedContainer = "container:">
|
|
||||||
</BuildableReference>
|
|
||||||
</TestableReference>
|
|
||||||
</Testables>
|
</Testables>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
|
|
|
@ -12,7 +12,7 @@ import Buildkite
|
||||||
let client = Buildkite()
|
let client = Buildkite()
|
||||||
client.token = "..."
|
client.token = "..."
|
||||||
var cancellables: Set<AnyCancellable> = []
|
var cancellables: Set<AnyCancellable> = []
|
||||||
client.sendPublisher(Build.Resources.ListAll())
|
client.sendPublisher(Build.Resources.Get(organization: "wayfair", pipeline: "merge-train-ci", build: 162))
|
||||||
.map(\.content)
|
.map(\.content)
|
||||||
.sink(receiveCompletion: { result in
|
.sink(receiveCompletion: { result in
|
||||||
if case let .failure(error) = result {
|
if case let .failure(error) = result {
|
||||||
|
|
|
@ -32,7 +32,7 @@ public struct Artifact: Codable, Equatable {
|
||||||
public var fileSize: Int
|
public var fileSize: Int
|
||||||
public var sha1sum: String
|
public var sha1sum: String
|
||||||
|
|
||||||
public struct URLs: Codable {
|
public struct URLs: Codable, Equatable {
|
||||||
public var url: URL
|
public var url: URL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,29 +12,72 @@ import Foundation
|
||||||
import FoundationNetworking
|
import FoundationNetworking
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public struct Job: Codable, Equatable {
|
public enum Job: Codable, Equatable {
|
||||||
public struct Agent: Codable, Equatable {
|
case script(Command)
|
||||||
public var id: UUID
|
case waiter(Wait)
|
||||||
public var url: URL
|
case manual(Block)
|
||||||
public var name: String
|
case trigger(Trigger)
|
||||||
|
|
||||||
|
private enum Unassociated: String, Codable {
|
||||||
|
case script
|
||||||
|
case waiter
|
||||||
|
case manual
|
||||||
|
case trigger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
let type = try container.decode(Unassociated.self, forKey: .type)
|
||||||
|
switch type {
|
||||||
|
case .script:
|
||||||
|
self = .script(try Command(from: decoder))
|
||||||
|
case .waiter:
|
||||||
|
self = .waiter(try Wait(from: decoder))
|
||||||
|
case .manual:
|
||||||
|
self = .manual(try Block(from: decoder))
|
||||||
|
case .trigger:
|
||||||
|
self = .trigger(try Trigger(from: decoder))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
switch self {
|
||||||
|
case .script(let step):
|
||||||
|
try step.encode(to: encoder)
|
||||||
|
case .waiter(let step):
|
||||||
|
try step.encode(to: encoder)
|
||||||
|
case .manual(let step):
|
||||||
|
try step.encode(to: encoder)
|
||||||
|
case .trigger(let step):
|
||||||
|
try step.encode(to: encoder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case type
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Command: Codable, Equatable {
|
||||||
|
public struct AgentRef: Codable, Equatable {
|
||||||
public var id: UUID
|
public var id: UUID
|
||||||
public var type: String
|
public var name: String
|
||||||
public var name: String?
|
public var url: URL
|
||||||
public var stepKey: String?
|
}
|
||||||
public var agentQueryRules: [String]
|
public let type = "script"
|
||||||
public var state: String?
|
public var id: UUID
|
||||||
public var buildUrl: URL?
|
public var name: String
|
||||||
public var webUrl: URL?
|
|
||||||
public var logUrl: URL?
|
|
||||||
public var rawLogUrl: URL?
|
|
||||||
public var artifactsUrl: URL?
|
|
||||||
public var command: String?
|
public var command: String?
|
||||||
|
public var stepKey: String?
|
||||||
|
public var buildUrl: URL
|
||||||
|
public var webUrl: URL
|
||||||
|
public var logUrl: URL
|
||||||
|
public var rawLogUrl: URL
|
||||||
|
public var artifactsUrl: URL
|
||||||
public var softFailed: Bool
|
public var softFailed: Bool
|
||||||
public var exitStatus: Int?
|
public var exitStatus: Int
|
||||||
public var artifactPaths: String?
|
public var artifactPaths: String?
|
||||||
public var agent: Agent?
|
public var agentQueryRules: [String]
|
||||||
|
public var agent: AgentRef?
|
||||||
public var createdAt: Date
|
public var createdAt: Date
|
||||||
public var scheduledAt: Date
|
public var scheduledAt: Date
|
||||||
public var runnableAt: Date?
|
public var runnableAt: Date?
|
||||||
|
@ -45,10 +88,16 @@ public struct Job: Codable, Equatable {
|
||||||
public var retriesCount: Int?
|
public var retriesCount: Int?
|
||||||
public var parallelGroupIndex: Int?
|
public var parallelGroupIndex: Int?
|
||||||
public var parallelGroupTotal: Int?
|
public var parallelGroupTotal: Int?
|
||||||
|
}
|
||||||
|
|
||||||
public struct Unblocked: Codable, Equatable {
|
public struct Wait: Codable, Equatable {
|
||||||
|
public let type = "waiter"
|
||||||
|
public var id: UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Block: Codable, Equatable {
|
||||||
|
public let type = "manual"
|
||||||
public var id: UUID
|
public var id: UUID
|
||||||
public var type: String
|
|
||||||
public var label: String
|
public var label: String
|
||||||
public var state: String
|
public var state: String
|
||||||
public var webUrl: URL?
|
public var webUrl: URL?
|
||||||
|
@ -58,6 +107,26 @@ public struct Job: Codable, Equatable {
|
||||||
public var unblockUrl: URL
|
public var unblockUrl: URL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct Trigger: Codable, Equatable {
|
||||||
|
public let type = "trigger"
|
||||||
|
public var name: String
|
||||||
|
public var state: String
|
||||||
|
public var buildUrl: URL
|
||||||
|
public var webUrl: URL
|
||||||
|
public var createdAt: Date
|
||||||
|
public var scheduledAt: Date
|
||||||
|
public var finishedAt: Date?
|
||||||
|
public var runnableAt: Date?
|
||||||
|
public var triggeredBuild: TriggeredBuild
|
||||||
|
|
||||||
|
public struct TriggeredBuild: Codable, Equatable {
|
||||||
|
public var id: UUID
|
||||||
|
public var number: Int
|
||||||
|
public var url: URL
|
||||||
|
public var webUrl: URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct LogOutput: Codable {
|
public struct LogOutput: Codable {
|
||||||
public var url: URL
|
public var url: URL
|
||||||
public var content: String
|
public var content: String
|
||||||
|
|
|
@ -159,7 +159,6 @@ extension Pipeline {
|
||||||
public var triggerBranch: String?
|
public var triggerBranch: String?
|
||||||
public var triggerAsync: Bool?
|
public var triggerAsync: Bool?
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// Constants.swift
|
// Formatters.swift
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// Created by Aaron Sky on 3/23/20.
|
// Created by Aaron Sky on 3/23/20.
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
//
|
||||||
|
// Pagination.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Aaron Sky on 5/5/20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
#if canImport(FoundationNetworking)
|
||||||
|
import FoundationNetworking
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public struct Page {
|
||||||
|
var nextPage: Int?
|
||||||
|
var previousPage: Int?
|
||||||
|
var firstPage: Int?
|
||||||
|
var lastPage: Int?
|
||||||
|
|
||||||
|
init?(for header: String) {
|
||||||
|
guard !header.isEmpty else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for link in header.split(separator: ",") {
|
||||||
|
let segments = link
|
||||||
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
.split(separator: ";")
|
||||||
|
guard
|
||||||
|
segments.count <= 2,
|
||||||
|
|
||||||
|
let urlString = segments.first,
|
||||||
|
urlString.hasPrefix("<") && urlString.hasSuffix(">"),
|
||||||
|
|
||||||
|
let url = URLComponents(string: String(urlString.dropFirst().dropLast())),
|
||||||
|
|
||||||
|
let pageString = url.queryItems?.first(where: { $0.name == "page" })?.value,
|
||||||
|
let page = Int(pageString) else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for segment in segments.dropFirst() {
|
||||||
|
switch segment.trimmingCharacters(in: .whitespacesAndNewlines) {
|
||||||
|
case "rel=\"next\"":
|
||||||
|
nextPage = page
|
||||||
|
case "rel=\"prev\"":
|
||||||
|
previousPage = page
|
||||||
|
case "rel=\"first\"":
|
||||||
|
firstPage = page
|
||||||
|
case "rel=\"last\"":
|
||||||
|
lastPage = page
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct PageOptions {
|
||||||
|
public var page: Int
|
||||||
|
public var perPage: Int
|
||||||
|
|
||||||
|
public init(page: Int, perPage: Int) {
|
||||||
|
self.page = page
|
||||||
|
self.perPage = perPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Array where Element == URLQueryItem {
|
||||||
|
init(options: PageOptions) {
|
||||||
|
self.init()
|
||||||
|
append(URLQueryItem(name: "page", value: String(options.page)))
|
||||||
|
append(URLQueryItem(name: "per_page", value: String(options.perPage)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -32,11 +32,6 @@ extension Resource {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension URLRequest {
|
extension URLRequest {
|
||||||
init<R: Resource & HasRequestBody>(_ resource: R, configuration: Configuration, encoder: JSONEncoder) throws {
|
|
||||||
self.init(resource, configuration: configuration)
|
|
||||||
httpBody = try encoder.encode(resource.body)
|
|
||||||
}
|
|
||||||
|
|
||||||
init<R: Resource>(_ resource: R, configuration: Configuration) {
|
init<R: Resource>(_ resource: R, configuration: Configuration) {
|
||||||
let url = configuration.url(for: resource.path)
|
let url = configuration.url(for: resource.path)
|
||||||
var request = URLRequest(url: url)
|
var request = URLRequest(url: url)
|
||||||
|
@ -44,4 +39,9 @@ extension URLRequest {
|
||||||
resource.transformRequest(&request)
|
resource.transformRequest(&request)
|
||||||
self = request
|
self = request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init<R: Resource & HasRequestBody>(_ resource: R, configuration: Configuration, encoder: JSONEncoder) throws {
|
||||||
|
self.init(resource, configuration: configuration)
|
||||||
|
httpBody = try encoder.encode(resource.body)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,15 @@ enum ResponseError: Error {
|
||||||
public struct Response<T> {
|
public struct Response<T> {
|
||||||
public let content: T
|
public let content: T
|
||||||
public let response: URLResponse
|
public let response: URLResponse
|
||||||
|
public let page: Page?
|
||||||
|
|
||||||
init(content: T, response: URLResponse) {
|
init(content: T, response: URLResponse) {
|
||||||
self.content = content
|
self.content = content
|
||||||
self.response = response
|
self.response = response
|
||||||
|
if let response = response as? HTTPURLResponse, let link = response.allHeaderFields["Link"] as? String {
|
||||||
|
page = Page(for: link)
|
||||||
|
} else {
|
||||||
|
page = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ extension Agent.Resources {
|
||||||
/// organization slug
|
/// organization slug
|
||||||
public var organization: String
|
public var organization: String
|
||||||
|
|
||||||
|
public var pageOptions: PageOptions?
|
||||||
|
|
||||||
public var path: String {
|
public var path: String {
|
||||||
"organizations/\(organization)/agents"
|
"organizations/\(organization)/agents"
|
||||||
}
|
}
|
||||||
|
@ -31,6 +33,17 @@ extension Agent.Resources {
|
||||||
public init(organization: String) {
|
public init(organization: String) {
|
||||||
self.organization = organization
|
self.organization = organization
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func transformRequest(_ request: inout URLRequest) {
|
||||||
|
guard let url = request.url,
|
||||||
|
var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let options = pageOptions {
|
||||||
|
components.queryItems = [URLQueryItem](options: options)
|
||||||
|
}
|
||||||
|
request.url = components.url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Get: Resource, HasResponseBody {
|
public struct Get: Resource, HasResponseBody {
|
||||||
|
|
|
@ -28,6 +28,8 @@ extension Annotation.Resources {
|
||||||
/// build number
|
/// build number
|
||||||
public var build: Int
|
public var build: Int
|
||||||
|
|
||||||
|
public var pageOptions: PageOptions?
|
||||||
|
|
||||||
public var path: String {
|
public var path: String {
|
||||||
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/annotations"
|
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/annotations"
|
||||||
}
|
}
|
||||||
|
@ -37,5 +39,16 @@ extension Annotation.Resources {
|
||||||
self.pipeline = pipeline
|
self.pipeline = pipeline
|
||||||
self.build = build
|
self.build = build
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func transformRequest(_ request: inout URLRequest) {
|
||||||
|
guard let url = request.url,
|
||||||
|
var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let options = pageOptions {
|
||||||
|
components.queryItems = [URLQueryItem](options: options)
|
||||||
|
}
|
||||||
|
request.url = components.url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,23 +12,25 @@ import FoundationNetworking
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extension Artifact {
|
extension Artifact {
|
||||||
enum Resources { }
|
public enum Resources { }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Artifact.Resources {
|
extension Artifact.Resources {
|
||||||
/// List artifacts for a build
|
/// List artifacts for a build
|
||||||
///
|
///
|
||||||
/// Returns a paginated list of a build’s artifacts across all of its jobs.
|
/// Returns a paginated list of a build’s artifacts across all of its jobs.
|
||||||
struct ListByBuild: Resource, HasResponseBody {
|
public struct ListByBuild: Resource, HasResponseBody {
|
||||||
typealias Content = [Artifact]
|
public typealias Content = [Artifact]
|
||||||
/// organization slug
|
/// organization slug
|
||||||
var organization: String
|
public var organization: String
|
||||||
/// pipeline slug
|
/// pipeline slug
|
||||||
var pipeline: String
|
public var pipeline: String
|
||||||
/// build number
|
/// build number
|
||||||
var build: Int
|
public var build: Int
|
||||||
|
|
||||||
var path: String {
|
public var pageOptions: PageOptions?
|
||||||
|
|
||||||
|
public var path: String {
|
||||||
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/artifacts"
|
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/artifacts"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,23 +39,36 @@ extension Artifact.Resources {
|
||||||
self.pipeline = pipeline
|
self.pipeline = pipeline
|
||||||
self.build = build
|
self.build = build
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func transformRequest(_ request: inout URLRequest) {
|
||||||
|
guard let url = request.url,
|
||||||
|
var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let options = pageOptions {
|
||||||
|
components.queryItems = [URLQueryItem](options: options)
|
||||||
|
}
|
||||||
|
request.url = components.url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List artifacts for a job
|
/// List artifacts for a job
|
||||||
///
|
///
|
||||||
/// Returns a paginated list of a job’s artifacts.
|
/// Returns a paginated list of a job’s artifacts.
|
||||||
struct ListByJob: Resource, HasResponseBody {
|
public struct ListByJob: Resource, HasResponseBody {
|
||||||
typealias Content = [Artifact]
|
public typealias Content = [Artifact]
|
||||||
/// organization slug
|
/// organization slug
|
||||||
var organization: String
|
public var organization: String
|
||||||
/// pipeline slug
|
/// pipeline slug
|
||||||
var pipeline: String
|
public var pipeline: String
|
||||||
/// build number
|
/// build number
|
||||||
var build: Int
|
public var build: Int
|
||||||
/// job ID
|
/// job ID
|
||||||
var jobId: UUID
|
public var jobId: UUID
|
||||||
|
|
||||||
var path: String {
|
public var pageOptions: PageOptions?
|
||||||
|
|
||||||
|
public var path: String {
|
||||||
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(jobId)/artifacts"
|
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(jobId)/artifacts"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,23 +78,34 @@ extension Artifact.Resources {
|
||||||
self.build = build
|
self.build = build
|
||||||
self.jobId = jobId
|
self.jobId = jobId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func transformRequest(_ request: inout URLRequest) {
|
||||||
|
guard let url = request.url,
|
||||||
|
var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let options = pageOptions {
|
||||||
|
components.queryItems = [URLQueryItem](options: options)
|
||||||
|
}
|
||||||
|
request.url = components.url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an artifact
|
/// Get an artifact
|
||||||
struct Get: Resource, HasResponseBody {
|
public struct Get: Resource, HasResponseBody {
|
||||||
typealias Content = Artifact
|
public typealias Content = Artifact
|
||||||
/// organization slug
|
/// organization slug
|
||||||
var organization: String
|
public var organization: String
|
||||||
/// pipeline slug
|
/// pipeline slug
|
||||||
var pipeline: String
|
public var pipeline: String
|
||||||
/// build number
|
/// build number
|
||||||
var build: Int
|
public var build: Int
|
||||||
/// job ID
|
/// job ID
|
||||||
var jobId: UUID
|
public var jobId: UUID
|
||||||
/// artifact ID
|
/// artifact ID
|
||||||
var artifactId: UUID
|
public var artifactId: UUID
|
||||||
|
|
||||||
var path: String {
|
public var path: String {
|
||||||
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(jobId)/artifacts/\(artifactId)"
|
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(jobId)/artifacts/\(artifactId)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,20 +121,20 @@ extension Artifact.Resources {
|
||||||
/// Download an artifact
|
/// Download an artifact
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
struct Download: Resource {
|
public struct Download: Resource, HasResponseBody {
|
||||||
typealias Content = Artifact.URLs
|
public typealias Content = Artifact.URLs
|
||||||
/// organization slug
|
/// organization slug
|
||||||
var organization: String
|
public var organization: String
|
||||||
/// pipeline slug
|
/// pipeline slug
|
||||||
var pipeline: String
|
public var pipeline: String
|
||||||
/// build number
|
/// build number
|
||||||
var build: Int
|
public var build: Int
|
||||||
/// job ID
|
/// job ID
|
||||||
var jobId: UUID
|
public var jobId: UUID
|
||||||
/// artifact ID
|
/// artifact ID
|
||||||
var artifactId: UUID
|
public var artifactId: UUID
|
||||||
|
|
||||||
var path: String {
|
public var path: String {
|
||||||
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(jobId)/artifacts/\(artifactId)/download"
|
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(jobId)/artifacts/\(artifactId)/download"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,27 +150,23 @@ extension Artifact.Resources {
|
||||||
/// Delete an artifact
|
/// Delete an artifact
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
struct Delete: Resource {
|
public struct Delete: Resource {
|
||||||
typealias Content = Void
|
public typealias Content = Void
|
||||||
/// organization slug
|
/// organization slug
|
||||||
var organization: String
|
public var organization: String
|
||||||
/// pipeline slug
|
/// pipeline slug
|
||||||
var pipeline: String
|
public var pipeline: String
|
||||||
/// build number
|
/// build number
|
||||||
var build: Int
|
public var build: Int
|
||||||
/// job ID
|
/// job ID
|
||||||
var jobId: UUID
|
public var jobId: UUID
|
||||||
/// artifact ID
|
/// artifact ID
|
||||||
var artifactId: UUID
|
public var artifactId: UUID
|
||||||
|
|
||||||
var path: String {
|
public var path: String {
|
||||||
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(jobId)/artifacts/\(artifactId)"
|
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(jobId)/artifacts/\(artifactId)"
|
||||||
}
|
}
|
||||||
|
|
||||||
func transformRequest(_ request: inout URLRequest) {
|
|
||||||
request.httpMethod = "DELETE"
|
|
||||||
}
|
|
||||||
|
|
||||||
public init(organization: String, pipeline: String, build: Int, jobId: UUID, artifactId: UUID) {
|
public init(organization: String, pipeline: String, build: Int, jobId: UUID, artifactId: UUID) {
|
||||||
self.organization = organization
|
self.organization = organization
|
||||||
self.pipeline = pipeline
|
self.pipeline = pipeline
|
||||||
|
@ -152,5 +174,9 @@ extension Artifact.Resources {
|
||||||
self.jobId = jobId
|
self.jobId = jobId
|
||||||
self.artifactId = artifactId
|
self.artifactId = artifactId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func transformRequest(_ request: inout URLRequest) {
|
||||||
|
request.httpMethod = "DELETE"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,10 +24,13 @@ extension Build.Resources {
|
||||||
public typealias Content = [Build]
|
public typealias Content = [Build]
|
||||||
public let path = "builds"
|
public let path = "builds"
|
||||||
|
|
||||||
public var options: QueryOptions?
|
public var queryOptions: QueryOptions?
|
||||||
|
|
||||||
public init(options: Build.Resources.QueryOptions? = nil) {
|
public var pageOptions: PageOptions?
|
||||||
self.options = options
|
|
||||||
|
public init(queryOptions: Build.Resources.QueryOptions? = nil, pageOptions: PageOptions? = nil) {
|
||||||
|
self.queryOptions = queryOptions
|
||||||
|
self.pageOptions = pageOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
public func transformRequest(_ request: inout URLRequest) {
|
public func transformRequest(_ request: inout URLRequest) {
|
||||||
|
@ -35,9 +38,14 @@ extension Build.Resources {
|
||||||
var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
|
var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let options = options {
|
var queryItems: [URLQueryItem] = []
|
||||||
components.queryItems = [URLQueryItem](options: options)
|
if let options = queryOptions {
|
||||||
|
queryItems.append(contentsOf: [URLQueryItem](options: options))
|
||||||
}
|
}
|
||||||
|
if let options = pageOptions {
|
||||||
|
queryItems.append(contentsOf: [URLQueryItem](options: options))
|
||||||
|
}
|
||||||
|
components.queryItems = queryItems
|
||||||
request.url = components.url
|
request.url = components.url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,15 +59,18 @@ extension Build.Resources {
|
||||||
/// organization slug
|
/// organization slug
|
||||||
public var organization: String
|
public var organization: String
|
||||||
|
|
||||||
public var options: QueryOptions?
|
public var queryOptions: QueryOptions?
|
||||||
|
|
||||||
|
public var pageOptions: PageOptions?
|
||||||
|
|
||||||
public var path: String {
|
public var path: String {
|
||||||
"organizations/\(organization)/builds"
|
"organizations/\(organization)/builds"
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(organization: String, options: Build.Resources.QueryOptions? = nil) {
|
public init(organization: String, queryOptions: Build.Resources.QueryOptions? = nil, pageOptions: PageOptions? = nil) {
|
||||||
self.organization = organization
|
self.organization = organization
|
||||||
self.options = options
|
self.queryOptions = queryOptions
|
||||||
|
self.pageOptions = pageOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
public func transformRequest(_ request: inout URLRequest) {
|
public func transformRequest(_ request: inout URLRequest) {
|
||||||
|
@ -67,9 +78,14 @@ extension Build.Resources {
|
||||||
var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
|
var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let options = options {
|
var queryItems: [URLQueryItem] = []
|
||||||
components.queryItems = [URLQueryItem](options: options)
|
if let options = queryOptions {
|
||||||
|
queryItems.append(contentsOf: [URLQueryItem](options: options))
|
||||||
}
|
}
|
||||||
|
if let options = pageOptions {
|
||||||
|
queryItems.append(contentsOf: [URLQueryItem](options: options))
|
||||||
|
}
|
||||||
|
components.queryItems = queryItems
|
||||||
request.url = components.url
|
request.url = components.url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,16 +100,19 @@ extension Build.Resources {
|
||||||
/// pipeline slug
|
/// pipeline slug
|
||||||
public var pipeline: String
|
public var pipeline: String
|
||||||
|
|
||||||
public var options: QueryOptions?
|
public var queryOptions: QueryOptions?
|
||||||
|
|
||||||
|
public var pageOptions: PageOptions?
|
||||||
|
|
||||||
public var path: String {
|
public var path: String {
|
||||||
"organizations/\(organization)/pipelines/\(pipeline)/builds"
|
"organizations/\(organization)/pipelines/\(pipeline)/builds"
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(organization: String, pipeline: String, options: Build.Resources.QueryOptions? = nil) {
|
public init(organization: String, pipeline: String, queryOptions: Build.Resources.QueryOptions? = nil, pageOptions: PageOptions? = nil) {
|
||||||
self.organization = organization
|
self.organization = organization
|
||||||
self.pipeline = pipeline
|
self.pipeline = pipeline
|
||||||
self.options = options
|
self.queryOptions = queryOptions
|
||||||
|
self.pageOptions = pageOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
public func transformRequest(_ request: inout URLRequest) {
|
public func transformRequest(_ request: inout URLRequest) {
|
||||||
|
@ -101,9 +120,14 @@ extension Build.Resources {
|
||||||
var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
|
var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let options = options {
|
var queryItems: [URLQueryItem] = []
|
||||||
components.queryItems = [URLQueryItem](options: options)
|
if let options = queryOptions {
|
||||||
|
queryItems.append(contentsOf: [URLQueryItem](options: options))
|
||||||
}
|
}
|
||||||
|
if let options = pageOptions {
|
||||||
|
queryItems.append(contentsOf: [URLQueryItem](options: options))
|
||||||
|
}
|
||||||
|
components.queryItems = queryItems
|
||||||
request.url = components.url
|
request.url = components.url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -260,18 +284,6 @@ extension Build.Resources {
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct QueryOptions {
|
public struct QueryOptions {
|
||||||
internal init(branches: [String] = [], commit: String? = nil, createdFrom: Date? = nil, createdTo: Date? = nil, creator: UUID? = nil, finishedFrom: Date? = nil, includeRetriedJobs: Bool? = nil, metadata: [String : String] = [:], state: String? = nil) {
|
|
||||||
self.branches = branches
|
|
||||||
self.commit = commit
|
|
||||||
self.createdFrom = createdFrom
|
|
||||||
self.createdTo = createdTo
|
|
||||||
self.creator = creator
|
|
||||||
self.finishedFrom = finishedFrom
|
|
||||||
self.includeRetriedJobs = includeRetriedJobs
|
|
||||||
self.metadata = metadata
|
|
||||||
self.state = state
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Filters the results by the given branch or branches.
|
/// Filters the results by the given branch or branches.
|
||||||
public var branches: [String] = []
|
public var branches: [String] = []
|
||||||
/// Filters the results by the commit (only works for full sha, not for shortened ones).
|
/// Filters the results by the commit (only works for full sha, not for shortened ones).
|
||||||
|
@ -290,6 +302,18 @@ extension Build.Resources {
|
||||||
public var metadata: [String: String] = [:]
|
public var metadata: [String: String] = [:]
|
||||||
/// Filters the results by the given build state. The finished state is a shortcut to automatically search for builds with passed, failed, blocked, canceled states.
|
/// Filters the results by the given build state. The finished state is a shortcut to automatically search for builds with passed, failed, blocked, canceled states.
|
||||||
public var state: String?
|
public var state: String?
|
||||||
|
|
||||||
|
public init(branches: [String] = [], commit: String? = nil, createdFrom: Date? = nil, createdTo: Date? = nil, creator: UUID? = nil, finishedFrom: Date? = nil, includeRetriedJobs: Bool? = nil, metadata: [String : String] = [:], state: String? = nil) {
|
||||||
|
self.branches = branches
|
||||||
|
self.commit = commit
|
||||||
|
self.createdFrom = createdFrom
|
||||||
|
self.createdTo = createdTo
|
||||||
|
self.creator = creator
|
||||||
|
self.finishedFrom = finishedFrom
|
||||||
|
self.includeRetriedJobs = includeRetriedJobs
|
||||||
|
self.metadata = metadata
|
||||||
|
self.state = state
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,22 @@ extension Organization.Resources {
|
||||||
public typealias Content = [Organization]
|
public typealias Content = [Organization]
|
||||||
public let path = "organizations"
|
public let path = "organizations"
|
||||||
|
|
||||||
public init() {}
|
public var pageOptions: PageOptions?
|
||||||
|
|
||||||
|
public init(pageOptions: PageOptions? = nil) {
|
||||||
|
self.pageOptions = pageOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
public func transformRequest(_ request: inout URLRequest) {
|
||||||
|
guard let url = request.url,
|
||||||
|
var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let options = pageOptions {
|
||||||
|
components.queryItems = [URLQueryItem](options: options)
|
||||||
|
}
|
||||||
|
request.url = components.url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an organization
|
/// Get an organization
|
||||||
|
|
|
@ -24,12 +24,26 @@ extension Pipeline.Resources {
|
||||||
/// organization slug
|
/// organization slug
|
||||||
public var organization: String
|
public var organization: String
|
||||||
|
|
||||||
|
public var pageOptions: PageOptions?
|
||||||
|
|
||||||
public var path: String {
|
public var path: String {
|
||||||
"organizations/\(organization)/pipelines"
|
"organizations/\(organization)/pipelines"
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(organization: String) {
|
public init(organization: String, pageOptions: PageOptions? = nil) {
|
||||||
self.organization = organization
|
self.organization = organization
|
||||||
|
self.pageOptions = pageOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
public func transformRequest(_ request: inout URLRequest) {
|
||||||
|
guard let url = request.url,
|
||||||
|
var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let options = pageOptions {
|
||||||
|
components.queryItems = [URLQueryItem](options: options)
|
||||||
|
}
|
||||||
|
request.url = components.url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,30 @@ extension Agent {
|
||||||
version: "3.20.0",
|
version: "3.20.0",
|
||||||
creator: User(),
|
creator: User(),
|
||||||
createdAt: Date(timeIntervalSince1970: 1000),
|
createdAt: Date(timeIntervalSince1970: 1000),
|
||||||
job: Job(),
|
job: Job.script(Job.Command(id: UUID(),
|
||||||
|
name: "📦",
|
||||||
|
command: nil,
|
||||||
|
stepKey: nil,
|
||||||
|
buildUrl: URL(),
|
||||||
|
webUrl: URL(),
|
||||||
|
logUrl: URL(),
|
||||||
|
rawLogUrl: URL(),
|
||||||
|
artifactsUrl: URL(),
|
||||||
|
softFailed: false,
|
||||||
|
exitStatus: 0,
|
||||||
|
artifactPaths: nil,
|
||||||
|
agentQueryRules: [],
|
||||||
|
agent: nil,
|
||||||
|
createdAt: Date(timeIntervalSince1970: 1000),
|
||||||
|
scheduledAt: Date(timeIntervalSince1970: 1000),
|
||||||
|
runnableAt: nil,
|
||||||
|
startedAt: nil,
|
||||||
|
finishedAt: nil,
|
||||||
|
retried: false,
|
||||||
|
retriedInJobId: nil,
|
||||||
|
retriesCount: nil,
|
||||||
|
parallelGroupIndex: nil,
|
||||||
|
parallelGroupTotal: nil) ),
|
||||||
lastJobFinishedAt: nil,
|
lastJobFinishedAt: nil,
|
||||||
priority: nil,
|
priority: nil,
|
||||||
metaData: [])
|
metaData: [])
|
||||||
|
|
|
@ -85,13 +85,15 @@ class ArtifactsTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testArtifactsDownload() throws {
|
func testArtifactsDownload() throws {
|
||||||
let context = MockContext()
|
let expected = Artifact.URLs(url: URL())
|
||||||
|
let context = try MockContext(content: expected)
|
||||||
|
|
||||||
let expectation = XCTestExpectation()
|
let expectation = XCTestExpectation()
|
||||||
|
|
||||||
context.client.send(Artifact.Resources.Download(organization: "buildkite", pipeline: "my-pipeline", build: 1, jobId: UUID(), artifactId: UUID())) { result in
|
context.client.send(Artifact.Resources.Download(organization: "buildkite", pipeline: "my-pipeline", build: 1, jobId: UUID(), artifactId: UUID())) { result in
|
||||||
do {
|
do {
|
||||||
_ = try result.get()
|
let response = try result.get()
|
||||||
|
XCTAssertEqual(expected, response.content)
|
||||||
} catch {
|
} catch {
|
||||||
XCTFail(error.localizedDescription)
|
XCTFail(error.localizedDescription)
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ class BuildsTests: XCTestCase {
|
||||||
let expected = [Build(), Build()]
|
let expected = [Build(), Build()]
|
||||||
let context = try MockContext(content: expected)
|
let context = try MockContext(content: expected)
|
||||||
|
|
||||||
let resource = Build.Resources.ListAll(options: Build.Resources.QueryOptions(branches: ["master"],
|
let resource = Build.Resources.ListAll(queryOptions: Build.Resources.QueryOptions(branches: ["master"],
|
||||||
commit: "HEAD",
|
commit: "HEAD",
|
||||||
createdFrom: Date(timeIntervalSince1970: 1000),
|
createdFrom: Date(timeIntervalSince1970: 1000),
|
||||||
createdTo: Date(timeIntervalSince1970: 1000),
|
createdTo: Date(timeIntervalSince1970: 1000),
|
||||||
|
|
|
@ -13,40 +13,9 @@ import XCTest
|
||||||
import FoundationNetworking
|
import FoundationNetworking
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extension Job {
|
|
||||||
init() {
|
|
||||||
self.init(id: UUID(),
|
|
||||||
type: "script",
|
|
||||||
name: "📦",
|
|
||||||
stepKey: "package",
|
|
||||||
agentQueryRules: [],
|
|
||||||
state: "finished",
|
|
||||||
buildUrl: URL(),
|
|
||||||
webUrl: URL(),
|
|
||||||
logUrl: URL(),
|
|
||||||
rawLogUrl: URL(),
|
|
||||||
artifactsUrl: URL(),
|
|
||||||
command: "echo 1",
|
|
||||||
softFailed: false,
|
|
||||||
exitStatus: 0,
|
|
||||||
artifactPaths: "",
|
|
||||||
agent: nil,
|
|
||||||
createdAt: Date(timeIntervalSince1970: 1000),
|
|
||||||
scheduledAt: Date(timeIntervalSince1970: 1000),
|
|
||||||
runnableAt: nil,
|
|
||||||
startedAt: nil,
|
|
||||||
finishedAt: nil,
|
|
||||||
retried: false,
|
|
||||||
retriedInJobId: UUID(),
|
|
||||||
retriesCount: 0,
|
|
||||||
parallelGroupIndex: nil,
|
|
||||||
parallelGroupTotal: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class JobsTests: XCTestCase {
|
class JobsTests: XCTestCase {
|
||||||
func testJobsRetry() throws {
|
func testJobsRetry() throws {
|
||||||
let expected = Job()
|
let expected = Job.waiter(Job.Wait(id: UUID()))
|
||||||
let context = try MockContext(content: expected)
|
let context = try MockContext(content: expected)
|
||||||
|
|
||||||
let expectation = XCTestExpectation()
|
let expectation = XCTestExpectation()
|
||||||
|
@ -64,7 +33,7 @@ class JobsTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testJobsUnblock() throws {
|
func testJobsUnblock() throws {
|
||||||
let expected = Job()
|
let expected = Job.waiter(Job.Wait(id: UUID()))
|
||||||
let context = try MockContext(content: expected)
|
let context = try MockContext(content: expected)
|
||||||
|
|
||||||
let body = Job.Resources.Unblock.Body()
|
let body = Job.Resources.Unblock.Body()
|
||||||
|
|
Loading…
Reference in New Issue