Skip to content

Commit 8eae339

Browse files
committed
update registry publishing
motivation: update based on recent spec changes: * do not request server requirements, deduce configuration based on use provided flags * fix issues in publishing logic per testing * simplify metadata handling
1 parent ceb9050 commit 8eae339

File tree

4 files changed

+64
-287
lines changed

4 files changed

+64
-287
lines changed

Sources/PackageRegistry/RegistryClient.swift

Lines changed: 29 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -648,74 +648,6 @@ public final class RegistryClient: Cancellable {
648648
}
649649
}
650650

651-
public func getPublishRequirements(
652-
registryURL: URL,
653-
timeout: DispatchTimeInterval? = .none,
654-
observabilityScope: ObservabilityScope,
655-
callbackQueue: DispatchQueue,
656-
completion: @escaping (Result<PublishRequirements, Error>) -> Void
657-
) {
658-
let completion = self.makeAsync(completion, on: callbackQueue)
659-
660-
guard var components = URLComponents(url: registryURL, resolvingAgainstBaseURL: true) else {
661-
return completion(.failure(RegistryError.invalidURL(registryURL)))
662-
}
663-
components.appendPathComponents("publish-requirements")
664-
guard let url = components.url else {
665-
return completion(.failure(RegistryError.invalidURL(registryURL)))
666-
}
667-
668-
let request = LegacyHTTPClient.Request(
669-
method: .get,
670-
url: url,
671-
headers: [
672-
"Accept": self.acceptHeader(mediaType: .json),
673-
],
674-
options: self.defaultRequestOptions(timeout: timeout, callbackQueue: callbackQueue)
675-
)
676-
677-
self.httpClient.execute(request, observabilityScope: observabilityScope, progress: nil) { result in
678-
completion(
679-
result.tryMap { response in
680-
switch response.statusCode {
681-
case 200:
682-
let publishRequirements = try response.parseJSON(
683-
Serialization.PublishRequirements.self,
684-
decoder: self.jsonDecoder
685-
)
686-
687-
return PublishRequirements(
688-
metadata: .init(
689-
location: publishRequirements.metadata.location.map {
690-
switch $0 {
691-
case .archive:
692-
return .archive
693-
case .request:
694-
return .request
695-
}
696-
}
697-
),
698-
signing: .init(
699-
required: publishRequirements.signing.required,
700-
acceptedSignatureFormats: publishRequirements.signing.acceptedSignatureFormats.map {
701-
switch $0 {
702-
case .CMS_1_0_0:
703-
return .CMS_1_0_0
704-
}
705-
},
706-
trustedRootCertificates: publishRequirements.signing.trustedRootCertificates
707-
)
708-
)
709-
default:
710-
throw self.unexpectedStatusError(response, expectedStatus: [200])
711-
}
712-
}.mapError {
713-
RegistryError.failedRetrievingRegistryPublishRequirements($0)
714-
}
715-
)
716-
}
717-
}
718-
719651
public func publish(
720652
registryURL: URL,
721653
packageIdentity: PackageIdentity,
@@ -759,43 +691,46 @@ public final class RegistryClient: Cancellable {
759691
}
760692

761693
// TODO: add generic support for upload requests in Basics
762-
var body = Data()
763694
let boundary = UUID().uuidString
695+
var parts = [String]()
764696

765697
// archive field
766-
body.append(contentsOf: """
767-
--\(boundary)
698+
parts.append("""
768699
Content-Disposition: form-data; name=\"source-archive\"\r
769700
Content-Type: application/zip\r
770701
Content-Transfer-Encoding: base64\r
771702
Content-Length: \(packageArchiveContent.count)\r
772703
\r
773-
\(packageArchiveContent.base64EncodedString())
774-
""".utf8)
704+
\(packageArchiveContent.base64EncodedString())\r
705+
""")
775706

776707
if let metadataContent = metadataContent {
777-
// spacer
778-
body.append(contentsOf: "\r\n".utf8)
779-
// metadata fiels
780-
body.append(contentsOf: """
781-
--\(boundary)\r
708+
parts.append("""
782709
Content-Disposition: form-data; name=\"metadata\"\r
783710
Content-Type: application/json\r
784711
Content-Transfer-Encoding: quoted-printable\r
785712
Content-Length: \(metadataContent.count)\r
786713
\r
787-
\(metadataContent)
788-
""".utf8)
714+
\(metadataContent)\r
715+
""")
789716
}
790717

718+
let bodyString = """
719+
--\(boundary)\r
720+
\(parts.joined(separator: "\n--\(boundary)\r\n"))
721+
--\(boundary)--\r\n
722+
"""
723+
791724
let request = LegacyHTTPClient.Request(
792725
method: .put,
793726
url: url,
794727
headers: [
728+
"Content-Type": "multipart/form-data;boundary=\"\(boundary)\"",
795729
"Accept": self.acceptHeader(mediaType: .json),
730+
"Expect": "100-continue",
796731
"Prefer": "respond-async",
797732
],
798-
body: body,
733+
body: Data(bodyString.utf8),
799734
options: self.defaultRequestOptions(timeout: timeout, callbackQueue: callbackQueue)
800735
)
801736

@@ -1024,23 +959,27 @@ extension RegistryClient {
1024959
}
1025960

1026961
extension RegistryClient {
1027-
public struct PublishRequirements {
1028-
public let metadata: Metadata
962+
public struct PublishConfiguration {
1029963
public let signing: Signing
1030964

1031-
public struct Metadata {
1032-
public let location: [MetadataLocation]
1033-
}
1034-
1035-
public enum MetadataLocation {
1036-
case request
1037-
case archive
965+
public init(signing: Signing) {
966+
self.signing = signing
1038967
}
1039968

1040969
public struct Signing {
1041970
public let required: Bool
1042971
public let acceptedSignatureFormats: [SignatureFormat]
1043972
public let trustedRootCertificates: [String]
973+
974+
public init(
975+
required: Bool,
976+
acceptedSignatureFormats: [SignatureFormat],
977+
trustedRootCertificates: [String]
978+
) {
979+
self.required = required
980+
self.acceptedSignatureFormats = acceptedSignatureFormats
981+
self.trustedRootCertificates = trustedRootCertificates
982+
}
1044983
}
1045984
}
1046985
}

Sources/PackageRegistryTool/PackageRegistryTool+Publish.swift

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ extension SwiftPackageRegistryTool {
3333
@OptionGroup(visibility: .hidden)
3434
var globalOptions: GlobalOptions
3535

36-
@Option(name: .customLong("id"), help: "The package identity")
36+
@Option(name: [.customLong("id"), .customLong("package-id")] , help: "The package identity")
3737
var packageIdentity: PackageIdentity
3838

39-
@Option(name: .customLong("version"), help: "The package version")
39+
@Option(name: [.customLong("version"), .customLong("package-version")], help: "The package version")
4040
var packageVersion: Version
4141

42-
@Option(name: .customLong("url"), help: "The registry URL")
42+
@Option(name: [.customLong("url"), .customLong("registry-url")], help: "The registry URL")
4343
var registryURL: URL?
4444

4545
@Option(
@@ -66,7 +66,7 @@ extension SwiftPackageRegistryTool {
6666
@Option(
6767
help: "Paths to all of the certificates (DER-encoded) in the chain. The certificate used for signing must be listed first and the root certificate last."
6868
)
69-
var certificateChainPaths: [AbsolutePath]
69+
var certificateChainPaths: [AbsolutePath] = []
7070

7171
func run(_ swiftTool: SwiftTool) throws {
7272
let configuration = try getRegistriesConfig(swiftTool).configuration
@@ -106,6 +106,12 @@ extension SwiftPackageRegistryTool {
106106
}
107107
}
108108

109+
let workingDirectory = customWorkingDirectory ?? Workspace.DefaultLocations
110+
.scratchDirectory(forRootPackage: packageDirectory).appending(components: ["registry", "publish"])
111+
if localFileSystem.exists(workingDirectory) {
112+
try localFileSystem.removeFileTree(workingDirectory)
113+
}
114+
109115
// validate custom metadata path
110116
if let customMetadataPath = self.customMetadataPath {
111117
guard localFileSystem.exists(customMetadataPath) else {
@@ -124,39 +130,37 @@ extension SwiftPackageRegistryTool {
124130
authorizationProvider: authorizationProvider
125131
)
126132

127-
// step 1: get registry publishing requirements
128-
swiftTool.observabilityScope.emit(info: "retrieving '\(registryURL)' publishing requirements")
129-
let publishRequirements = try tsc_await { callback in
130-
registryClient.getPublishRequirements(
131-
registryURL: registryURL,
132-
observabilityScope: swiftTool.observabilityScope,
133-
callbackQueue: .sharedConcurrent,
134-
completion: callback
135-
)
136-
}
137-
138-
// step 2: generate source archive for the package release
133+
// step 1: publishing configuration
139134
let metadataPath = self.customMetadataPath ?? packageDirectory.appending(component: Self.metadataFilename)
140135
guard localFileSystem.exists(metadataPath) else {
141136
throw StringError(
142137
"Publishing to '\(registryURL)' requires metadata file but none was found at '\(metadataPath)'."
143138
)
144139
}
145140

141+
let publishConfiguration = RegistryClient.PublishConfiguration(
142+
signing: .init(
143+
required: self.signingIdentity != nil || self.privateKeyPath != nil,
144+
acceptedSignatureFormats: [.CMS_1_0_0],
145+
trustedRootCertificates: []
146+
)
147+
)
148+
149+
// step 2: generate source archive for the package release
150+
146151
swiftTool.observabilityScope.emit(info: "archiving the source at '\(packageDirectory)'")
147152
let archivePath = try self.archiveSource(
148153
packageIdentity: self.packageIdentity,
149154
packageVersion: self.packageVersion,
150155
packageDirectory: packageDirectory,
151-
metadataPath: publishRequirements.metadata.location.contains(.archive) ? metadataPath : .none,
152-
customWorkingDirectory: self.customWorkingDirectory,
156+
workingDirectory: workingDirectory,
153157
cancellator: swiftTool.cancellator,
154158
observabilityScope: swiftTool.observabilityScope
155159
)
156160

157161
// step 3: sign the source archive if needed
158162
var signature: Data? = .none
159-
if publishRequirements.signing.required {
163+
if publishConfiguration.signing.required {
160164
swiftTool.observabilityScope.emit(info: "signing the archive at '\(archivePath)'")
161165
signature = try self.sign(
162166
archivePath: archivePath,
@@ -177,7 +181,7 @@ extension SwiftPackageRegistryTool {
177181
packageIdentity: self.packageIdentity,
178182
packageVersion: self.packageVersion,
179183
packageArchive: archivePath,
180-
packageMetadata: publishRequirements.metadata.location.contains(.request) ? metadataPath : .none,
184+
packageMetadata: self.customMetadataPath,
181185
signature: signature,
182186
fileSystem: localFileSystem,
183187
observabilityScope: swiftTool.observabilityScope,
@@ -200,14 +204,10 @@ extension SwiftPackageRegistryTool {
200204
packageIdentity: PackageIdentity,
201205
packageVersion: Version,
202206
packageDirectory: AbsolutePath,
203-
metadataPath: AbsolutePath?,
204-
customWorkingDirectory: AbsolutePath?,
207+
workingDirectory: AbsolutePath,
205208
cancellator: Cancellator?,
206209
observabilityScope: ObservabilityScope
207210
) throws -> AbsolutePath {
208-
let workingDirectory = customWorkingDirectory ?? Workspace.DefaultLocations
209-
.scratchDirectory(forRootPackage: packageDirectory).appending(components: ["registry", "publish"])
210-
211211
let archivePath = workingDirectory.appending(component: "\(packageIdentity)-\(packageVersion).zip")
212212

213213
// create temp location for sources
@@ -224,16 +224,6 @@ extension SwiftPackageRegistryTool {
224224
)
225225
}
226226

227-
// include metadata from non-standard location in the archive
228-
if let metadataPath = metadataPath,
229-
metadataPath != packageDirectory.appending(component: Self.metadataFilename)
230-
{
231-
try localFileSystem.copy(
232-
from: metadataPath,
233-
to: sourceDirectory.appending(component: Self.metadataFilename)
234-
)
235-
}
236-
237227
try SwiftPackageTool.archiveSource(
238228
at: sourceDirectory,
239229
to: archivePath,

0 commit comments

Comments
 (0)