diff --git a/package.json b/package.json index 25c1428e..a67e7cfa 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,12 @@ "description": "The name of the repository's git remote that points to GitHub", "default": "origin", "scope": "window" + }, + "github-actions.use-enterprise": { + "type": "boolean", + "markdownDescription": "If this is set to true, use the auth provider for the GitHub Enterprise URL configured in `github-enterprise.uri`", + "default": false, + "scope": "window" } } }, diff --git a/src/api/api.ts b/src/api/api.ts index 660166ae..2f59c24d 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -1,11 +1,13 @@ import {Octokit} from "@octokit/rest"; import {version} from "../../package.json"; +import {getGitHubApiUri} from "../configuration/configuration"; export const userAgent = `VS Code GitHub Actions (${version})`; export function getClient(token: string): Octokit { return new Octokit({ auth: token, - userAgent: userAgent + userAgent: userAgent, + baseUrl: `${getGitHubApiUri()}` }); } diff --git a/src/auth/auth.ts b/src/auth/auth.ts index 2dbe9b43..2fa60ef4 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -1,6 +1,8 @@ import * as vscode from "vscode"; +import {isUseEnterprise} from "../configuration/configuration"; const AUTH_PROVIDER_ID = "github"; +const AUTH_PROVIDER_ID_ENTERPRISE = "github-enterprise"; const DEFAULT_SCOPES = ["repo", "workflow"]; let signInPrompted = false; @@ -67,7 +69,8 @@ async function getSessionInternal( typeof createOrForceMessage === "string" ? {forceNewSession: {detail: createOrForceMessage}} : {createIfNone: createOrForceMessage}; - return await vscode.authentication.getSession(AUTH_PROVIDER_ID, getScopes(), options); + const authProviderId = isUseEnterprise() ? AUTH_PROVIDER_ID_ENTERPRISE : AUTH_PROVIDER_ID; + return await vscode.authentication.getSession(authProviderId, getScopes(), options); } function getScopes(): string[] { diff --git a/src/configuration/configuration.ts b/src/configuration/configuration.ts index a7c0e331..375eb3b1 100644 --- a/src/configuration/configuration.ts +++ b/src/configuration/configuration.ts @@ -1,12 +1,23 @@ import * as vscode from "vscode"; +import {deactivateLanguageServer, initLanguageServer} from "../workflow/languageServer"; +import {resetGitHubContext} from "../git/repository"; const settingsKey = "github-actions"; +const DEFAULT_GITHUB_API = "https://api.github.com"; export function initConfiguration(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.workspace.onDidChangeConfiguration(e => { if (e.affectsConfiguration(getSettingsKey("workflows.pinned"))) { pinnedWorkflowsChangeHandlers.forEach(h => h()); + } else if ( + e.affectsConfiguration(getSettingsKey("use-enterprise")) || + (isUseEnterprise() && + (e.affectsConfiguration("github-enterprise.uri") || e.affectsConfiguration(getSettingsKey("remote-name")))) + ) { + updateLanguageServerApiUrl(context); + resetGitHubContext(); + vscode.commands.executeCommand("github-actions.explorer.refresh"); } }) ); @@ -52,3 +63,19 @@ export function pinnedWorkflowsRefreshInterval(): number { export function getRemoteName(): string { return getConfiguration().get(getSettingsKey("remote-name"), "origin"); } + +export function isUseEnterprise(): boolean { + return getConfiguration().get(getSettingsKey("use-enterprise"), false); +} + +export function getGitHubApiUri(): string { + if (!isUseEnterprise()) return DEFAULT_GITHUB_API; + const base = getConfiguration().get("github-enterprise.uri", DEFAULT_GITHUB_API).replace(/\/$/, ""); + return base === DEFAULT_GITHUB_API ? base : `${base}/api/v3`; +} + +function updateLanguageServerApiUrl(context: vscode.ExtensionContext) { + deactivateLanguageServer(); + + initLanguageServer(context); +} diff --git a/src/git/repository.ts b/src/git/repository.ts index 75ffa605..6980efed 100644 --- a/src/git/repository.ts +++ b/src/git/repository.ts @@ -4,7 +4,7 @@ import {Octokit} from "@octokit/rest"; import {canReachGitHubAPI} from "../api/canReachGitHubAPI"; import {handleSamlError} from "../api/handleSamlError"; import {getSession} from "../auth/auth"; -import {getRemoteName} from "../configuration/configuration"; +import {getRemoteName, isUseEnterprise} from "../configuration/configuration"; import {Protocol} from "../external/protocol"; import {logDebug, logError} from "../log"; import {API, GitExtension, RefType, RepositoryState} from "../typings/git"; @@ -74,7 +74,7 @@ export async function getGitHubUrls(): Promise { remote = [r.state.remotes[0]]; } - if (remote.length > 0 && remote[0].pushUrl?.indexOf("github.com") !== -1) { + if (remote.length > 0 && (remote[0].pushUrl?.indexOf("github.com") !== -1 || isUseEnterprise())) { const url = remote[0].pushUrl; return { @@ -145,6 +145,8 @@ export interface GitHubContext { reposByUri: Map; reposByOwnerAndName: Map; username: string; + + enterpriseServerVersion: string | undefined; } let gitHubContext: Promise | undefined; @@ -208,11 +210,21 @@ export async function getGitHubContext(): Promise { ); }); + const meta = await handleSamlError(session, async (client: Octokit) => { + // using the dynamic request method which returns any, because this could be either of + // - the main GitHub meta endpoint + // (https://docs.github.com/en/rest/meta?apiVersion=2022-11-28#get-github-meta-information) + // - the GitHub Enterprise Server meta endpoint + // (https://docs.github.com/en/enterprise-server@3.5/rest/meta#get-github-enterprise-server-meta-information) + return client.request({url: "/meta", method: "GET"}); + }); + gitHubContext = Promise.resolve({ repos, reposByUri: new Map(repos.map(r => [r.workspaceUri.toString(), r])), reposByOwnerAndName: new Map(repos.map(r => [`${r.owner}/${r.name}`.toLocaleLowerCase(), r])), - username + username, + enterpriseServerVersion: meta?.data?.installed_version }); } catch (e) { // Reset the context so the next attempt will try this flow again @@ -282,3 +294,10 @@ export function getCurrentBranch(state: RepositoryState | undefined): string | u return head.name; } + +export function hasVariables(context: GitHubContext): boolean { + if (!context.enterpriseServerVersion) return true; + const split = context.enterpriseServerVersion.split("."); + if (split.length < 2) return true; + return +split[0] >= 3 && +split[1] >= 8; +} diff --git a/src/treeViews/settings.ts b/src/treeViews/settings.ts index 3ad4c966..12c9e3a7 100644 --- a/src/treeViews/settings.ts +++ b/src/treeViews/settings.ts @@ -42,10 +42,10 @@ export class SettingsTreeProvider implements vscode.TreeDataProvider 0) { if (gitHubContext.repos.length == 1) { - return getSettingNodes(gitHubContext.repos[0]); + return getSettingNodes(gitHubContext.repos[0], gitHubContext); } - return gitHubContext.repos.map(r => new SettingsRepoNode(r)); + return gitHubContext.repos.map(r => new SettingsRepoNode(r, gitHubContext)); } } diff --git a/src/treeViews/settings/settingsRepoNode.ts b/src/treeViews/settings/settingsRepoNode.ts index b75a145a..18bd6567 100644 --- a/src/treeViews/settings/settingsRepoNode.ts +++ b/src/treeViews/settings/settingsRepoNode.ts @@ -1,32 +1,34 @@ import * as vscode from "vscode"; import {EnvironmentsNode} from "./environmentsNode"; -import {GitHubRepoContext} from "../../git/repository"; +import {GitHubContext, GitHubRepoContext, hasVariables} from "../../git/repository"; import {hasWritePermission} from "../../git/repository-permissions"; import {SecretsNode} from "./secretsNode"; import {SettingsExplorerNode} from "./types"; import {VariablesNode} from "./variablesNode"; export class SettingsRepoNode extends vscode.TreeItem { - constructor(public readonly gitHubRepoContext: GitHubRepoContext) { + constructor(public readonly gitHubRepoContext: GitHubRepoContext, public readonly gitHubContext: GitHubContext) { super(gitHubRepoContext.name, vscode.TreeItemCollapsibleState.Collapsed); this.contextValue = "settings-repo"; } getSettings(): SettingsExplorerNode[] { - return getSettingNodes(this.gitHubRepoContext); + return getSettingNodes(this.gitHubRepoContext, this.gitHubContext); } } -export function getSettingNodes(gitHubContext: GitHubRepoContext): SettingsExplorerNode[] { +export function getSettingNodes(repoContext: GitHubRepoContext, gitHubContext: GitHubContext): SettingsExplorerNode[] { const nodes: SettingsExplorerNode[] = []; - nodes.push(new EnvironmentsNode(gitHubContext)); + nodes.push(new EnvironmentsNode(repoContext)); - if (hasWritePermission(gitHubContext.permissionLevel)) { - nodes.push(new SecretsNode(gitHubContext)); - nodes.push(new VariablesNode(gitHubContext)); + if (hasWritePermission(repoContext.permissionLevel)) { + nodes.push(new SecretsNode(repoContext)); + if (hasVariables(gitHubContext)) { + nodes.push(new VariablesNode(repoContext)); + } } return nodes; diff --git a/src/workflow/languageServer.ts b/src/workflow/languageServer.ts index 5aa8869c..010d1c7b 100644 --- a/src/workflow/languageServer.ts +++ b/src/workflow/languageServer.ts @@ -11,6 +11,7 @@ import {userAgent} from "../api/api"; import {getSession} from "../auth/auth"; import {getGitHubContext} from "../git/repository"; import {WorkflowSelector} from "./documentSelector"; +import {getGitHubApiUri, isUseEnterprise} from "../configuration/configuration"; let client: BaseLanguageClient; @@ -26,6 +27,7 @@ export async function initLanguageServer(context: vscode.ExtensionContext) { const initializationOptions: InitializationOptions = { sessionToken: session?.accessToken, userAgent: userAgent, + githubApiUrl: isUseEnterprise() ? getGitHubApiUri() : undefined, repos: ghContext?.repos.map(repo => ({ id: repo.id, owner: repo.owner,