680845cb1c
Linux build / Determine Swift version (push) Waiting to run
Linux build / Linux compile check (push) Blocked by required conditions
Build containerization / Verify commit signatures (push) Has been skipped
Build containerization / containerization (push) Successful in 0s
86 lines
3.7 KiB
Swift
86 lines
3.7 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// Copyright © 2026 Apple Inc. and the Containerization project authors.
|
|
//
|
|
// 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.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import AsyncHTTPClient
|
|
import ContainerizationError
|
|
import Foundation
|
|
import NIOFoundationCompat
|
|
|
|
private struct CatalogResponse: Sendable, Decodable {
|
|
let repositories: [String]
|
|
}
|
|
|
|
extension RegistryClient {
|
|
/// List repositories in the registry.
|
|
///
|
|
/// Implements GET /v2/_catalog from the OCI Distribution Spec with pagination.
|
|
/// When prefix is provided, pagination skips ahead to the relevant portion of
|
|
/// the lexically-sorted catalog and stops once results move past the prefix.
|
|
///
|
|
/// - Parameter prefix: Optional prefix to filter repository names. Must be at least
|
|
/// two characters long to enable the skip-ahead optimization; shorter values are
|
|
/// treated as no prefix.
|
|
/// - Returns: An array of repository names matching the prefix (or all repositories
|
|
/// if no prefix is given).
|
|
public func catalog(prefix: String? = nil) async throws -> [String] {
|
|
let effectivePrefix = prefix.flatMap { $0.count >= 2 ? $0 : nil }
|
|
|
|
var allRepos: [String] = []
|
|
// When a prefix is provided, skip ahead in the lexically-sorted catalog
|
|
// by setting last to one position before the prefix. The OCI spec
|
|
// returns entries that sort after last, so dropping the last character
|
|
// of the prefix positions the cursor just before matching entries.
|
|
var last: String? = effectivePrefix.map { String($0.dropLast()) }
|
|
let pageSize = 100
|
|
|
|
while true {
|
|
var components = base
|
|
components.path = "/v2/_catalog"
|
|
var queryItems = [URLQueryItem(name: "n", value: String(pageSize))]
|
|
if let last {
|
|
queryItems.append(URLQueryItem(name: "last", value: last))
|
|
}
|
|
components.queryItems = queryItems
|
|
|
|
let repos: [String] = try await request(components: components) { response in
|
|
guard response.status == .ok else {
|
|
let url = components.url?.absoluteString ?? "unknown"
|
|
let reason = await ErrorResponse.fromResponseBody(response.body)?.jsonString
|
|
throw Error.invalidStatus(url: url, response.status, reason: reason)
|
|
}
|
|
|
|
let buffer = try await response.body.collect(upTo: self.bufferSize)
|
|
return try JSONDecoder().decode(CatalogResponse.self, from: buffer).repositories
|
|
}
|
|
|
|
if let effectivePrefix {
|
|
let matching = repos.filter { $0.hasPrefix(effectivePrefix) }
|
|
allRepos.append(contentsOf: matching)
|
|
if let lastRepo = repos.last, !lastRepo.hasPrefix(effectivePrefix) && lastRepo > effectivePrefix {
|
|
break
|
|
}
|
|
} else {
|
|
allRepos.append(contentsOf: repos)
|
|
}
|
|
|
|
if repos.count < pageSize { break }
|
|
last = repos.last
|
|
}
|
|
|
|
return allRepos
|
|
}
|
|
}
|