Skip to content

Remove all usage of the GitHub API #172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 3 additions & 11 deletions Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ swiftly [--version] [--help]
Install a new toolchain.

```
swiftly install <version> [--use] [--token=<token>] [--verify] [--post-install-file=<post-install-file>] [--version] [--help]
swiftly install <version> [--use] [--verify] [--post-install-file=<post-install-file>] [--version] [--help]
```

**version:**
Expand Down Expand Up @@ -59,14 +59,6 @@ Likewise, the latest snapshot associated with a given development branch can be
*Mark the newly installed toolchain as in-use.*


**--token=\<token\>:**

*A GitHub authentication token to use for any GitHub API requests.*

This is useful to avoid GitHub's low rate limits. If an installation
fails with an "unauthorized" status code, it likely means the rate limit has been hit.


**--verify:**

*Verify the toolchain's PGP signature before proceeding with installation.*
Expand Down Expand Up @@ -120,9 +112,9 @@ Likewise, the available toolchains associated with a given minor version can be
The installed snapshots for a given devlopment branch can be listed by specifying the branch as the selector:

$ swiftly list-available main-snapshot
$ swiftly list-available 6.0-snapshot
$ swiftly list-available x.y-snapshot

Note that listing available snapshots before 6.0 is unsupported.
Note that listing available snapshots before the latest release (major and minor number) is unsupported.


**--version:**
Expand Down
6 changes: 1 addition & 5 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

import PackageDescription

let ghApiCacheResources = (1...16).map { Resource.embedInCode("gh-api-cache/swift-tags-page\($0).json") }
let ghApiCacheExcludedResources = (17...27).map { "gh-api-cache/swift-tags-page\($0).json" }

let package = Package(
name: "swiftly",
platforms: [
Expand Down Expand Up @@ -100,8 +97,7 @@ let package = Package(
.testTarget(
name: "SwiftlyTests",
dependencies: ["Swiftly"],
exclude: ghApiCacheExcludedResources,
resources: ghApiCacheResources + [
resources: [
.embedInCode("mock-signing-key-private.pgp"),
]
),
Expand Down
9 changes: 1 addition & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,13 +273,6 @@ swift-5.7-DEVELOPMENT-SNAPSHOT-2022-08-30-a

The canonical name format was chosen to reduce the keystrokes needed to refer to a snapshot toolchain, but the longer form is also useful when copy/pasting a toolchain name provided from somewhere else.

### Specifying a GitHub access token

swiftly currently uses the GitHub API to look up the available Swift toolchains. To avoid running up against rate limits, you can provide a [GitHub access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) via the `--token` option (the token doesn't need any permissions):

```
$ swiftly install latest --token <GitHub authentication token>
```
## Contributing
Welcome to the Swift community!

Expand All @@ -299,7 +292,7 @@ Swift.org currently provides experimental [`.rpm` and `.deb`](https://forums.swi

swiftenv is an existing Swift version manager which already has much of the functionality that swiftly will eventually have. It's an awesome tool, and if it's part of your workflow then we encourage you to keep using it! That said, swiftly is/will be different a few ways:

- swiftly is being built as a community driven effort led by the Swift server workgroup, and through this collaboration, swiftly will eventually become an official installation tool for Swift toolchains. As first step towards that, swiftly will help inform the creation of API endpoints maintained by the Swift project that it will use to retrieve information about what toolchains are available to install and to verify their expected signatures. swiftenv currently uses a third party API layer for this. Using an official API reduces the avenues for security vulnerabilities and also reduces the risk of downtime affecting Swift installations. Note that this is planned for the future--swiftly currently uses the GitHub API for this purpose.
- swiftly is being built as a community driven effort led by the Swift server workgroup, and through this collaboration, swiftly will eventually become an official installation tool for Swift toolchains. As first step towards that, swiftly will help inform the creation of API endpoints maintained by the Swift project that it will use to retrieve information about what toolchains are available to install and to verify their expected signatures. swiftenv currently uses a third party API layer for this. Using an official API reduces the avenues for security vulnerabilities and also reduces the risk of downtime affecting Swift installations.

- swiftly will be written in Swift, which we think is important for maintainability and encouraging community contributions.

Expand Down
35 changes: 17 additions & 18 deletions Sources/Swiftly/Install.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,6 @@ struct Install: SwiftlyCommand {
@Flag(name: .shortAndLong, help: "Mark the newly installed toolchain as in-use.")
var use: Bool = false

@Option(help: ArgumentHelp(
"A GitHub authentication token to use for any GitHub API requests.",
discussion: """
This is useful to avoid GitHub's low rate limits. If an installation
fails with an \"unauthorized\" status code, it likely means the rate limit has been hit.
"""
))
var token: String?

@Flag(inversion: .prefixedNo, help: "Verify the toolchain's PGP signature before proceeding with installation.")
var verify = true

Expand All @@ -68,15 +59,14 @@ struct Install: SwiftlyCommand {
var postInstallFile: String?

private enum CodingKeys: String, CodingKey {
case version, token, use, verify, postInstallFile
case version, use, verify, postInstallFile
}

mutating func run() async throws {
try validateSwiftly()

let selector = try ToolchainSelector(parsing: self.version)
var config = try Config.load()
SwiftlyCore.httpClient.githubToken = self.token
let toolchainVersion = try await self.resolve(config: config, selector: selector)
let postInstallScript = try await Self.execute(
version: toolchainVersion,
Expand Down Expand Up @@ -224,8 +214,7 @@ struct Install: SwiftlyCommand {
return postInstallScript
}

/// Utilize the GitHub API along with the provided selector to select a toolchain for install.
/// TODO: update this to use an official swift.org API
/// Utilize the swift.org API along with the provided selector to select a toolchain for install.
func resolve(config: Config, selector: ToolchainSelector) async throws -> ToolchainVersion {
switch selector {
case .latest:
Expand Down Expand Up @@ -266,17 +255,27 @@ struct Install: SwiftlyCommand {
}

SwiftlyCore.print("Fetching the latest \(branch) branch snapshot...")

// If a date was not provided, perform a lookup to find the most recent snapshot
// for the given branch.
let snapshot = try await SwiftlyCore.httpClient.getSnapshotToolchains(platform: config.platform, branch: branch, limit: 1) { snapshot in
snapshot.branch == branch
}.first
let snapshots: [ToolchainVersion.Snapshot]
do {
snapshots = try await SwiftlyCore.httpClient.getSnapshotToolchains(platform: config.platform, branch: branch, limit: 1) { snapshot in
snapshot.branch == branch
}
} catch let branchNotFoundErr as SwiftlyHTTPClient.SnapshotBranchNotFoundError {
throw Error(message: "You have requested to install a snapshot toolchain from branch \(branchNotFoundErr.branch). It cannot be found on swift.org. Note that snapshots are only available from the current `main` release and the latest x.y (major.minor) release. Try again with a different branch.")
} catch {
throw error
}

let firstSnapshot = snapshots.first

guard let snapshot else {
guard let firstSnapshot else {
throw Error(message: "No snapshot toolchain found for branch \(branch)")
}

return .snapshot(snapshot)
return .snapshot(firstSnapshot)
}
}
}
14 changes: 8 additions & 6 deletions Sources/Swiftly/ListAvailable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ struct ListAvailable: SwiftlyCommand {
The installed snapshots for a given devlopment branch can be listed by specifying the branch as the selector:

$ swiftly list-available main-snapshot
$ swiftly list-available 6.0-snapshot
$ swiftly list-available x.y-snapshot

Note that listing available snapshots before 6.0 is unsupported.
Note that listing available snapshots before the latest release (major and minor number) is unsupported.
"""
))
var toolchainSelector: String?
Expand All @@ -51,11 +51,13 @@ struct ListAvailable: SwiftlyCommand {

switch selector {
case let .snapshot(branch, _):
if case let .release(major, _) = branch, major < 6 {
throw Error(message: "Listing available snapshots previous to 6.0 is not supported.")
do {
tc = try await SwiftlyCore.httpClient.getSnapshotToolchains(platform: config.platform, branch: branch).map { ToolchainVersion.snapshot($0) }
} catch let branchNotFoundError as SwiftlyHTTPClient.SnapshotBranchNotFoundError {
throw Error(message: "The snapshot branch \(branchNotFoundError.branch) was not found on swift.org. Note that snapshot toolchains are only available for the current `main` release and the previous x.y (major.minor) release.")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to use the word latest here as opposed to previous. In the update command we say

""The branch (branchNotFoundErr.branch) doesn't have any snapshots available on swift.org so this snapshot build cannot be updated. It is possible that there has been a new release on swift.org and the previous release snapshot are no longer available. Install a fresh snapshot toolchain from the either the latest release x.y (major.minor) or from the main branch.""

Where we tell users "previous" are gone, install "latest". But here we say that main and "previous" are available.

Basically just some very small confusion around previous != latest

} catch {
throw error
}

tc = try await SwiftlyCore.httpClient.getSnapshotToolchains(platform: config.platform, branch: branch).map { ToolchainVersion.snapshot($0) }
case .stable, .latest:
tc = try await SwiftlyCore.httpClient.getReleaseToolchains(platform: config.platform).map { ToolchainVersion.stable($0) }
default:
Expand Down
17 changes: 13 additions & 4 deletions Sources/Swiftly/Update.swift
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ struct Update: SwiftlyCommand {
}

/// Tries to find a toolchain version that meets the provided parameters, if one exists.
/// This does not download the toolchain, but it does query the GitHub API to find the suitable toolchain.
/// This does not download the toolchain, but it does query the swift.org API to find the suitable toolchain.
private func lookupNewToolchain(_ config: Config, _ bounds: UpdateParameters) async throws -> ToolchainVersion? {
switch bounds {
case let .stable(old, range):
Expand All @@ -195,9 +195,18 @@ struct Update: SwiftlyCommand {
}
}.first.map(ToolchainVersion.stable)
case let .snapshot(old):
return try await SwiftlyCore.httpClient.getSnapshotToolchains(platform: config.platform, branch: old.branch, limit: 1) { snapshot in
snapshot.branch == old.branch && snapshot.date > old.date
}.first.map(ToolchainVersion.snapshot)
let newerSnapshotToolchains: [ToolchainVersion.Snapshot]
do {
newerSnapshotToolchains = try await SwiftlyCore.httpClient.getSnapshotToolchains(platform: config.platform, branch: old.branch, limit: 1) { snapshot in
snapshot.branch == old.branch && snapshot.date > old.date
}
} catch let branchNotFoundErr as SwiftlyHTTPClient.SnapshotBranchNotFoundError {
throw Error(message: "Snapshot branch \(branchNotFoundErr.branch) cannot be updated. One possible reason for this is that there has been a new release published to swift.org and this snapshot is for an older release. Snapshots are only available for the newest release and the main branch. You can install a fresh snapshot toolchain from the either the latest release x.y (major.minor) with `swiftly install x.y-snapshot` or from the main branch with `swiftly install main-snapshot`.")
} catch {
throw error
}

return newerSnapshotToolchains.first.map(ToolchainVersion.snapshot)
}
}
}
Expand Down
134 changes: 0 additions & 134 deletions Sources/SwiftlyCore/HTTPClient+GitHubAPI.swift

This file was deleted.

Loading