From 345e8311cff0c4673756976fad20712ba246603a Mon Sep 17 00:00:00 2001 From: Mattia Valzelli Date: Sat, 30 Sep 2023 15:46:37 +0200 Subject: [PATCH 1/4] Add ability to store custom GitHub URL --- .../GitHubCredentialsStore.swift | 1 + .../GitHubCredentialsStoreKeychain.swift | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/Packages/GitHub/Sources/GitHubCredentialsStore/GitHubCredentialsStore.swift b/Packages/GitHub/Sources/GitHubCredentialsStore/GitHubCredentialsStore.swift index ae081f9..fab17e3 100644 --- a/Packages/GitHub/Sources/GitHubCredentialsStore/GitHubCredentialsStore.swift +++ b/Packages/GitHub/Sources/GitHubCredentialsStore/GitHubCredentialsStore.swift @@ -1,6 +1,7 @@ import Foundation public protocol GitHubCredentialsStore: AnyObject { + var selfHostedURL: URL? { get async } var organizationName: String? { get async } var repositoryName: String? { get async } var ownerName: String? { get async } diff --git a/Packages/GitHub/Sources/GitHubCredentialsStoreKeychain/GitHubCredentialsStoreKeychain.swift b/Packages/GitHub/Sources/GitHubCredentialsStoreKeychain/GitHubCredentialsStoreKeychain.swift index d1a4954..93dc4a2 100644 --- a/Packages/GitHub/Sources/GitHubCredentialsStoreKeychain/GitHubCredentialsStoreKeychain.swift +++ b/Packages/GitHub/Sources/GitHubCredentialsStoreKeychain/GitHubCredentialsStoreKeychain.swift @@ -5,6 +5,7 @@ import RSAPrivateKey public final actor GitHubCredentialsStoreKeychain: GitHubCredentialsStore { private enum PasswordAccount { + static let selfHostedURL = "github.credentials.selfHostedURL" static let organizationName = "github.credentials.organizationName" static let repositoryName = "github.credentials.repositoryName" static let ownerName = "github.credentials.ownerName" @@ -15,6 +16,12 @@ public final actor GitHubCredentialsStoreKeychain: GitHubCredentialsStore { static let privateKey = "github.credentials.privateKey" } + public var selfHostedURL: URL? { + get async { + return await keychain.password(forAccount: PasswordAccount.selfHostedURL, belongingToService: serviceName) + .flatMap(URL.init(string:)) + } + } public var organizationName: String? { get async { return await keychain.password(forAccount: PasswordAccount.organizationName, belongingToService: serviceName) @@ -49,6 +56,14 @@ public final actor GitHubCredentialsStoreKeychain: GitHubCredentialsStore { self.serviceName = serviceName } + public func setSelfHostedURL(_ selfHostedURL: URL?) async { + if let selfHostedURL { + _ = await keychain.setPassword(selfHostedURL.absoluteString, forAccount: PasswordAccount.selfHostedURL, belongingToService: serviceName) + } else { + await keychain.removePassword(forAccount: PasswordAccount.selfHostedURL, belongingToService: serviceName) + } + } + public func setOrganizationName(_ organizationName: String?) async { if let organizationName { _ = await keychain.setPassword(organizationName, forAccount: PasswordAccount.organizationName, belongingToService: serviceName) From 5f0c9086ed47643d1e0952a8493ce5e35461aae6 Mon Sep 17 00:00:00 2001 From: Mattia Valzelli Date: Sat, 30 Sep 2023 15:55:36 +0200 Subject: [PATCH 2/4] Update `baseURL` used for GitHub service --- .../GitHubServiceLive/GitHubServiceLive.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Packages/GitHub/Sources/GitHubServiceLive/GitHubServiceLive.swift b/Packages/GitHub/Sources/GitHubServiceLive/GitHubServiceLive.swift index 3e18d48..c2066ba 100644 --- a/Packages/GitHub/Sources/GitHubServiceLive/GitHubServiceLive.swift +++ b/Packages/GitHub/Sources/GitHubServiceLive/GitHubServiceLive.swift @@ -34,7 +34,11 @@ private enum GitHubServiceLiveError: LocalizedError { } public final class GitHubServiceLive: GitHubService { - private let baseURL = URL(string: "https://api.github.com")! + private var baseURL: URL { + get async { + await credentialsStore.selfHostedURL ?? .gitHub + } + } private let credentialsStore: GitHubCredentialsStore private let networkingService: NetworkingService @@ -47,7 +51,7 @@ public final class GitHubServiceLive: GitHubService { let appInstallation = try await getAppInstallation(runnerScope: runnerScope) let installationID = String(appInstallation.id) let appID = String(appInstallation.appId) - let url = baseURL.appending(path: "/app/installations/\(installationID)/access_tokens") + let url = await baseURL.appending(path: "/app/installations/\(installationID)/access_tokens") guard let privateKey = await credentialsStore.privateKey else { throw GitHubServiceLiveError.privateKeyUnavailable } @@ -86,7 +90,7 @@ public final class GitHubServiceLive: GitHubService { private extension GitHubServiceLive { private func getAppInstallation(runnerScope: GitHubRunnerScope) async throws -> GitHubAppInstallation { - let url = baseURL.appending(path: "/app/installations") + let url = await baseURL.appending(path: "/app/installations") let token = try await getAppJWTToken() let request = URLRequest(url: url).addingBearerToken(token) let appInstallations = try await networkingService.load([GitHubAppInstallation].self, from: request).map(\.value) @@ -108,6 +112,10 @@ private extension GitHubServiceLive { } } +private extension URL { + static let gitHub = URL(string: "https://api.github.com")! +} + private extension URLRequest { func addingBearerToken(_ token: String) -> URLRequest { var mutableRequest = self From 48578a99fb22f491a049599704b21cea8ae7f9a1 Mon Sep 17 00:00:00 2001 From: Mattia Valzelli Date: Wed, 4 Oct 2023 07:39:02 +0200 Subject: [PATCH 3/4] Add ability to use custom enterprise server --- .../GitHubCredentialsStore.swift | 1 + .../GitHubService/GitHubServiceVersion.swift | 11 +++++++ .../Sources/SettingsStore/SettingsStore.swift | 3 ++ .../GitHubSettings/GitHubSettingsView.swift | 26 ++++++++++++++++ .../GitHubSettingsViewModel.swift | 30 +++++++++++++++++++ 5 files changed, 71 insertions(+) create mode 100644 Packages/GitHub/Sources/GitHubService/GitHubServiceVersion.swift diff --git a/Packages/GitHub/Sources/GitHubCredentialsStore/GitHubCredentialsStore.swift b/Packages/GitHub/Sources/GitHubCredentialsStore/GitHubCredentialsStore.swift index fab17e3..48f8755 100644 --- a/Packages/GitHub/Sources/GitHubCredentialsStore/GitHubCredentialsStore.swift +++ b/Packages/GitHub/Sources/GitHubCredentialsStore/GitHubCredentialsStore.swift @@ -7,6 +7,7 @@ public protocol GitHubCredentialsStore: AnyObject { var ownerName: String? { get async } var appId: String? { get async } var privateKey: Data? { get async } + func setSelfHostedURL(_ selfHostedURL: URL?) async func setOrganizationName(_ organizationName: String?) async func setRepository(_ repositoryName: String?, withOwner ownerName: String?) async func setAppID(_ appID: String?) async diff --git a/Packages/GitHub/Sources/GitHubService/GitHubServiceVersion.swift b/Packages/GitHub/Sources/GitHubService/GitHubServiceVersion.swift new file mode 100644 index 0000000..94ec9d8 --- /dev/null +++ b/Packages/GitHub/Sources/GitHubService/GitHubServiceVersion.swift @@ -0,0 +1,11 @@ +import Foundation + +public enum GitHubServiceVersion { + case dotCom + case enterprise(URL) + + public enum Kind: String, CaseIterable { + case dotCom + case enterprise + } +} diff --git a/Packages/Settings/Sources/SettingsStore/SettingsStore.swift b/Packages/Settings/Sources/SettingsStore/SettingsStore.swift index 13005c8..c05c102 100644 --- a/Packages/Settings/Sources/SettingsStore/SettingsStore.swift +++ b/Packages/Settings/Sources/SettingsStore/SettingsStore.swift @@ -14,6 +14,7 @@ public final class SettingsStore: ObservableObject { static let gitHubRunnerLabels = "gitHubRunnerLabels" static let gitHubRunnerGroup = "gitHubRunnerGroup" static let githubRunnerScope = "githubRunnerScope" + static let githubServiceVersion = "githubServiceVersion" } @AppStorage(AppStorageKey.applicationUIMode) @@ -34,6 +35,8 @@ public final class SettingsStore: ObservableObject { public var gitHubRunnerGroup = "" @AppStorage(AppStorageKey.githubRunnerScope) public var githubRunnerScope: GitHubRunnerScope = .organization + @AppStorage(AppStorageKey.githubServiceVersion) + public var githubServiceVersion: GitHubServiceVersion.Kind = .dotCom public init() {} diff --git a/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubSettingsView.swift b/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubSettingsView.swift index 3a11726..f097298 100644 --- a/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubSettingsView.swift +++ b/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubSettingsView.swift @@ -14,6 +14,21 @@ struct GitHubSettingsView: View { var body: some View { Form { Section { + Picker("Version", selection: $viewModel.version) { + ForEach(GitHubServiceVersion.Kind.allCases, id: \.self) { scope in + Text(scope.title) + } + } + + if viewModel.version == .enterprise { + TextField( + "Self hosted URL", + text: $viewModel.selfHostedRaw, + prompt: Text("Github Service raw value") + ) + .disabled(!viewModel.isSettingsEnabled) + } + Picker(L10n.Settings.Github.runnerScope, selection: $viewModel.runnerScope) { ForEach(GitHubRunnerScope.allCases, id: \.self) { scope in Text(scope.title) @@ -80,3 +95,14 @@ private extension GitHubRunnerScope { } } } + +private extension GitHubServiceVersion.Kind { + var title: String { + switch self { + case .dotCom: + return "github.com" + case .enterprise: + return "github self hosted" + } + } +} diff --git a/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubSettingsViewModel.swift b/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubSettingsViewModel.swift index 047f22e..903835f 100644 --- a/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubSettingsViewModel.swift +++ b/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubSettingsViewModel.swift @@ -13,6 +13,8 @@ final class GitHubSettingsViewModel: ObservableObject { @Published var ownerName: String = "" @Published var appId: String = "" @Published var privateKeyName = "" + @Published var version: GitHubServiceVersion.Kind + @Published var selfHostedRaw: String = "" @Published var runnerScope: GitHubRunnerScope @Published private(set) var isSettingsEnabled = true @@ -29,6 +31,7 @@ final class GitHubSettingsViewModel: ObservableObject { init(settingsStore: SettingsStore, credentialsStore: GitHubCredentialsStore, isSettingsEnabled: AnyPublisher) { self.settingsStore = settingsStore self.credentialsStore = credentialsStore + self.version = settingsStore.githubServiceVersion self.runnerScope = settingsStore.githubRunnerScope isSettingsEnabled.assign(to: \.isSettingsEnabled, on: self).store(in: &cancellables) $appId.debounce(for: 0.5, scheduler: DispatchQueue.main).nilIfEmpty().dropFirst().sink { [weak self] appId in @@ -36,6 +39,26 @@ final class GitHubSettingsViewModel: ObservableObject { await self?.credentialsStore.setAppID(appId) } }.store(in: &cancellables) + + $version + .combineLatest($selfHostedRaw) + .debounce(for: 0.5, scheduler: DispatchQueue.main) + .dropFirst() + .sink { [weak self] version, value in + self?.settingsStore.githubServiceVersion = version + switch version { + case .dotCom: + Task { + await self?.credentialsStore.setSelfHostedURL(nil) + } + case .enterprise: + Task { + await self?.credentialsStore.setSelfHostedURL(URL(string: value)) + } + } + } + .store(in: &cancellables) + $runnerScope .combineLatest( $organizationName.nilIfEmpty(), @@ -80,6 +103,13 @@ final class GitHubSettingsViewModel: ObservableObject { } func loadCredentials() async { + if let selfHostedURL = await credentialsStore.selfHostedURL { + selfHostedRaw = selfHostedURL.absoluteString + version = .enterprise + } else { + selfHostedRaw = "" + version = .dotCom + } organizationName = await credentialsStore.organizationName ?? "" repositoryName = await credentialsStore.repositoryName ?? "" ownerName = await credentialsStore.ownerName ?? "" From 567b8a6462b3edf1fdf7d7ed757f27f73162f824 Mon Sep 17 00:00:00 2001 From: Mattia Valzelli Date: Wed, 4 Oct 2023 08:20:34 +0200 Subject: [PATCH 4/4] Add handling of `enterprise` self hosted runner --- .../GitHubCredentialsStore.swift | 2 + .../GitHubCredentialsStoreKeychain.swift | 14 +++++++ .../GitHubService/GitHubRunnerScope.swift | 1 + .../GitHubServiceLive/GitHubServiceLive.swift | 15 ++++++++ .../GitHubPrivateKeyPicker.swift | 2 + .../GitHubSettings/GitHubSettingsView.swift | 9 +++++ .../GitHubSettingsViewModel.swift | 37 ++++++++++++++++--- ...tualMachineResourcesServiceEphemeral.swift | 21 +++++++---- 8 files changed, 88 insertions(+), 13 deletions(-) diff --git a/Packages/GitHub/Sources/GitHubCredentialsStore/GitHubCredentialsStore.swift b/Packages/GitHub/Sources/GitHubCredentialsStore/GitHubCredentialsStore.swift index 48f8755..fc08cf6 100644 --- a/Packages/GitHub/Sources/GitHubCredentialsStore/GitHubCredentialsStore.swift +++ b/Packages/GitHub/Sources/GitHubCredentialsStore/GitHubCredentialsStore.swift @@ -5,11 +5,13 @@ public protocol GitHubCredentialsStore: AnyObject { var organizationName: String? { get async } var repositoryName: String? { get async } var ownerName: String? { get async } + var enterpriseName: String? { get async } var appId: String? { get async } var privateKey: Data? { get async } func setSelfHostedURL(_ selfHostedURL: URL?) async func setOrganizationName(_ organizationName: String?) async func setRepository(_ repositoryName: String?, withOwner ownerName: String?) async + func setEnterpriseName(_ enterpriseName: String?) async func setAppID(_ appID: String?) async func setPrivateKey(_ privateKeyData: Data?) async } diff --git a/Packages/GitHub/Sources/GitHubCredentialsStoreKeychain/GitHubCredentialsStoreKeychain.swift b/Packages/GitHub/Sources/GitHubCredentialsStoreKeychain/GitHubCredentialsStoreKeychain.swift index 93dc4a2..0305959 100644 --- a/Packages/GitHub/Sources/GitHubCredentialsStoreKeychain/GitHubCredentialsStoreKeychain.swift +++ b/Packages/GitHub/Sources/GitHubCredentialsStoreKeychain/GitHubCredentialsStoreKeychain.swift @@ -9,6 +9,7 @@ public final actor GitHubCredentialsStoreKeychain: GitHubCredentialsStore { static let organizationName = "github.credentials.organizationName" static let repositoryName = "github.credentials.repositoryName" static let ownerName = "github.credentials.ownerName" + static let enterpriseName = "github.credentials.enterpriseName" static let appId = "github.credentials.appId" } @@ -37,6 +38,11 @@ public final actor GitHubCredentialsStoreKeychain: GitHubCredentialsStore { return await keychain.password(forAccount: PasswordAccount.ownerName, belongingToService: serviceName) } } + public var enterpriseName: String? { + get async { + return await keychain.password(forAccount: PasswordAccount.enterpriseName, belongingToService: serviceName) + } + } public var appId: String? { get async { return await keychain.password(forAccount: PasswordAccount.appId, belongingToService: serviceName) @@ -84,6 +90,14 @@ public final actor GitHubCredentialsStoreKeychain: GitHubCredentialsStore { } } + public func setEnterpriseName(_ enterpriseName: String?) async { + if let enterpriseName { + _ = await keychain.setPassword(enterpriseName, forAccount: PasswordAccount.enterpriseName, belongingToService: serviceName) + } else { + await keychain.removePassword(forAccount: PasswordAccount.enterpriseName, belongingToService: serviceName) + } + } + public func setAppID(_ appID: String?) async { if let appID { _ = await keychain.setPassword(appID, forAccount: PasswordAccount.appId, belongingToService: serviceName) diff --git a/Packages/GitHub/Sources/GitHubService/GitHubRunnerScope.swift b/Packages/GitHub/Sources/GitHubService/GitHubRunnerScope.swift index b62157e..00f017e 100644 --- a/Packages/GitHub/Sources/GitHubService/GitHubRunnerScope.swift +++ b/Packages/GitHub/Sources/GitHubService/GitHubRunnerScope.swift @@ -3,4 +3,5 @@ import Foundation public enum GitHubRunnerScope: String, CaseIterable { case organization case repo + case enterpriseServer } diff --git a/Packages/GitHub/Sources/GitHubServiceLive/GitHubServiceLive.swift b/Packages/GitHub/Sources/GitHubServiceLive/GitHubServiceLive.swift index c2066ba..3653bad 100644 --- a/Packages/GitHub/Sources/GitHubServiceLive/GitHubServiceLive.swift +++ b/Packages/GitHub/Sources/GitHubServiceLive/GitHubServiceLive.swift @@ -8,6 +8,7 @@ private enum GitHubServiceLiveError: LocalizedError { case organizationNameUnavailable case repositoryNameUnavailable case repositoryOwnerNameUnavailable + case enterpriseNameUnavailable case appIDUnavailable case privateKeyUnavailable case appIsNotInstalled @@ -21,6 +22,8 @@ private enum GitHubServiceLiveError: LocalizedError { return "The repository name is not available" case .repositoryOwnerNameUnavailable: return "The repository owner name is not available" + case .enterpriseNameUnavailable: + return "The enterprise name is not available" case .appIDUnavailable: return "The app ID is not available" case .privateKeyUnavailable: @@ -141,6 +144,11 @@ private extension GitHubRunnerScope { } return "/repos/\(ownerName)/\(repositoryName)/actions/runners/registration-token" + case .enterpriseServer: + guard let enterpriseName = await credentialsStore.enterpriseName else { + throw GitHubServiceLiveError.enterpriseNameUnavailable + } + return "/enterprises/\(enterpriseName)/actions/runners/registration-token" } } @@ -160,6 +168,11 @@ private extension GitHubRunnerScope { } return "/repos/\(ownerName)/\(repositoryName)/actions/runners/downloads" + case .enterpriseServer: + guard let enterpriseName = await credentialsStore.enterpriseName else { + throw GitHubServiceLiveError.enterpriseNameUnavailable + } + return "/enterprises/\(enterpriseName)/actions/runners/downloads" } } @@ -169,6 +182,8 @@ private extension GitHubRunnerScope { return await credentialsStore.organizationName case .repo: return await credentialsStore.ownerName + case .enterpriseServer: + return await credentialsStore.enterpriseName } } } diff --git a/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubPrivateKeyPicker.swift b/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubPrivateKeyPicker.swift index 055b1aa..840b39a 100644 --- a/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubPrivateKeyPicker.swift +++ b/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubPrivateKeyPicker.swift @@ -12,6 +12,8 @@ struct GitHubPrivateKeyPicker: View { L10n.Settings.Github.PrivateKey.Scopes.organization case .repo: L10n.Settings.Github.PrivateKey.Scopes.repository + case .enterpriseServer: + "Check permissions: `manage_runners:enterprise`" } } diff --git a/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubSettingsView.swift b/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubSettingsView.swift index f097298..132f06a 100644 --- a/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubSettingsView.swift +++ b/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubSettingsView.swift @@ -56,6 +56,13 @@ struct GitHubSettingsView: View { prompt: Text(L10n.Settings.Github.RepositoryName.prompt) ) .disabled(!viewModel.isSettingsEnabled) + case .enterpriseServer: + TextField( + "Enterprise name", + text: $viewModel.enterpriseName, + prompt: Text("Acme Enterprise") + ) + .disabled(!viewModel.isSettingsEnabled) } } Section { @@ -92,6 +99,8 @@ private extension GitHubRunnerScope { L10n.Settings.RunnerScope.organization case .repo: L10n.Settings.RunnerScope.repository + case .enterpriseServer: + "Enterprise" } } } diff --git a/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubSettingsViewModel.swift b/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubSettingsViewModel.swift index 903835f..d2c4968 100644 --- a/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubSettingsViewModel.swift +++ b/Packages/Settings/Sources/SettingsUI/Internal/GitHubSettings/GitHubSettingsViewModel.swift @@ -11,6 +11,7 @@ final class GitHubSettingsViewModel: ObservableObject { @Published var organizationName: String = "" @Published var repositoryName: String = "" @Published var ownerName: String = "" + @Published var enterpriseName: String = "" @Published var appId: String = "" @Published var privateKeyName = "" @Published var version: GitHubServiceVersion.Kind @@ -21,11 +22,13 @@ final class GitHubSettingsViewModel: ObservableObject { private let credentialsStore: GitHubCredentialsStore private var cancellables: Set = [] private var createAppURL: URL { - var url = URL(string: "https://github.com")! - if !organizationName.isEmpty, case .organization = runnerScope { - url = url.appending(path: "/organizations/\(organizationName)") + get async { + var url = await credentialsStore.selfHostedURL ?? .gitHub + if !organizationName.isEmpty, case .organization = runnerScope { + url = url.appending(path: "/organizations/\(organizationName)") + } + return url.appending(path: "/settings/apps") } - return url.appending(path: "/settings/apps") } init(settingsStore: SettingsStore, credentialsStore: GitHubCredentialsStore, isSettingsEnabled: AnyPublisher) { @@ -65,27 +68,44 @@ final class GitHubSettingsViewModel: ObservableObject { $ownerName.nilIfEmpty(), $repositoryName.nilIfEmpty() ) + .combineLatest($enterpriseName.nilIfEmpty()) .debounce(for: 0.5, scheduler: DispatchQueue.main) .dropFirst() - .sink { [weak self] runnerScope, organizationName, ownerName, repositoryName in + .map { tuple, enterpriseName in + let runnerScope = tuple.0 + let organizationName = tuple.1 + let ownerName = tuple.2 + let repositoryName = tuple.3 + return (runnerScope, organizationName, ownerName, repositoryName, enterpriseName) + } + .sink { [weak self] runnerScope, organizationName, ownerName, repositoryName, enterpriseName in self?.settingsStore.githubRunnerScope = runnerScope switch runnerScope { case .organization: Task { await self?.credentialsStore.setOrganizationName(organizationName) await self?.credentialsStore.setRepository(nil, withOwner: nil) + await self?.credentialsStore.setEnterpriseName(nil) } case .repo: Task { await self?.credentialsStore.setOrganizationName(nil) await self?.credentialsStore.setRepository(repositoryName, withOwner: ownerName) } + case .enterpriseServer: + Task { + await self?.credentialsStore.setOrganizationName(nil) + await self?.credentialsStore.setRepository(nil, withOwner: nil) + await self?.credentialsStore.setEnterpriseName(enterpriseName) + } } }.store(in: &cancellables) } func openCreateApp() { - NSWorkspace.shared.open(createAppURL) + Task { + NSWorkspace.shared.open(await createAppURL) + } } func storePrivateKey(at fileURL: URL) async { @@ -113,6 +133,7 @@ final class GitHubSettingsViewModel: ObservableObject { organizationName = await credentialsStore.organizationName ?? "" repositoryName = await credentialsStore.repositoryName ?? "" ownerName = await credentialsStore.ownerName ?? "" + enterpriseName = await credentialsStore.enterpriseName ?? "" appId = await credentialsStore.appId ?? "" let privateKey = await credentialsStore.privateKey privateKeyName = privateKey != nil ? settingsStore.gitHubPrivateKeyName ?? "" : "" @@ -124,3 +145,7 @@ private extension Publisher where Output == String { map { !$0.isEmpty ? $0 : nil }.eraseToAnyPublisher() } } + +private extension URL { + static let gitHub = URL(string: "https://github.com/")! +} diff --git a/Packages/VirtualMachine/Sources/VirtualMachineResourcesServiceEphemeral/VirtualMachineResourcesServiceEphemeral.swift b/Packages/VirtualMachine/Sources/VirtualMachineResourcesServiceEphemeral/VirtualMachineResourcesServiceEphemeral.swift index 2c493b2..4c2b164 100644 --- a/Packages/VirtualMachine/Sources/VirtualMachineResourcesServiceEphemeral/VirtualMachineResourcesServiceEphemeral.swift +++ b/Packages/VirtualMachine/Sources/VirtualMachineResourcesServiceEphemeral/VirtualMachineResourcesServiceEphemeral.swift @@ -102,22 +102,25 @@ public struct VirtualMachineResourcesServiceEphemeral: VirtualMachineResourcesSe private extension VirtualMachineResourcesServiceEphemeral { private func getRunnerURL() async throws -> URL { + let baseURL = await gitHubCredentialsStore.selfHostedURL ?? .gitHub switch runnerScope { case .organization: let organizationName = try await getOrganizationName() - guard let runnerURL = URL(string: "https://github.com/" + organizationName) else { - throw VirtualMachineResourcesServiceEphemeralError.invalidRunnerURL - } - return runnerURL + return baseURL.appending(path: organizationName, directoryHint: .notDirectory) case .repo: guard let ownerName = await gitHubCredentialsStore.ownerName, - let repositoryName = await gitHubCredentialsStore.repositoryName, - let runnerURL = URL(string: "https://github.com/\(ownerName)/\(repositoryName)") + let repositoryName = await gitHubCredentialsStore.repositoryName else { throw VirtualMachineResourcesServiceEphemeralError.invalidRunnerURL } - return runnerURL + return baseURL.appending(path: "\(ownerName)/\(repositoryName)", directoryHint: .notDirectory) + case .enterpriseServer: + guard let enterpriseName = await gitHubCredentialsStore.enterpriseName else { + throw VirtualMachineResourcesServiceEphemeralError.invalidRunnerURL + } +// SEE https://docs.github.com/en/enterprise-server@3.10/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-enterprise + return baseURL.appending(path: enterpriseName, directoryHint: .notDirectory) } } @@ -129,3 +132,7 @@ private extension VirtualMachineResourcesServiceEphemeral { } } } + +private extension URL { + static let gitHub = URL(string: "https://github.com/")! +}