From 385d3aa94f91957b010a631eae136cac51807fd6 Mon Sep 17 00:00:00 2001 From: Peter Murray <681306+peter-murray@users.noreply.github.com> Date: Fri, 9 Sep 2022 13:05:37 +0000 Subject: [PATCH] Adding ability to specify the GitHub Server URL and allowing for it to differ from the Actions workflow host --- .gitignore | 3 +- README.md | 5 ++ __test__/git-auth-helper.test.ts | 3 +- action.yml | 3 + dist/index.js | 114 +++++++++++++++++++++++-------- src/git-auth-helper.ts | 2 +- src/git-source-provider.ts | 9 ++- src/git-source-settings.ts | 5 ++ src/github-api-helper.ts | 20 +++--- src/input-helper.ts | 5 ++ src/octokit-helper.ts | 23 +++++++ src/ref-helper.ts | 18 ++--- src/url-helper.ts | 34 ++++++--- 13 files changed, 182 insertions(+), 62 deletions(-) create mode 100644 src/octokit-helper.ts diff --git a/.gitignore b/.gitignore index 8baa767ff..cd1f03c78 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ __test__/_temp _temp/ lib/ -node_modules/ \ No newline at end of file +node_modules/ +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index 7619c06a7..22e9a4994 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,11 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl # config --global --add safe.directory ` # Default: true set-safe-directory: '' + + # The base URL for the GitHub instance that you are trying to clone from, will use + # environment defaults to fetch from the same instance that the workflow is + # running from unless specified. + github-server-url: '' ``` diff --git a/__test__/git-auth-helper.test.ts b/__test__/git-auth-helper.test.ts index a6731c2b4..64d49cc83 100644 --- a/__test__/git-auth-helper.test.ts +++ b/__test__/git-auth-helper.test.ts @@ -778,7 +778,8 @@ async function setup(testName: string): Promise { sshKnownHosts: '', sshStrict: true, workflowOrganizationId: 123456, - setSafeDirectory: true + setSafeDirectory: true, + githubServerUrl: undefined } } diff --git a/action.yml b/action.yml index 96c535ef7..12c5f4c48 100644 --- a/action.yml +++ b/action.yml @@ -71,6 +71,9 @@ inputs: set-safe-directory: description: Add repository path as safe.directory for Git global config by running `git config --global --add safe.directory ` default: true + github-server-url: + description: The base URL for the GitHub instance that you are trying to clone from, will use environment defaults to fetch from the same instance that the workflow is running from unless specified. + required: false runs: using: node16 main: dist/index.js diff --git a/dist/index.js b/dist/index.js index c7bc6957b..edde37e02 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1295,6 +1295,51 @@ module.exports._parse = parse; module.exports._enoent = enoent; +/***/ }), + +/***/ 24: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getOctokit = exports.Octokit = void 0; +const github = __importStar(__webpack_require__(469)); +const url_helper_1 = __webpack_require__(81); +// Centralize all Octokit references by re-exporting +var rest_1 = __webpack_require__(0); +Object.defineProperty(exports, "Octokit", { enumerable: true, get: function () { return rest_1.Octokit; } }); +function getOctokit(authToken, opts) { + const options = { + baseUrl: (0, url_helper_1.getServerApiUrl)(opts.baseUrl) + }; + if (opts.userAgent) { + options.userAgent = opts.userAgent; + } + return new github.GitHub(authToken, options); +} +exports.getOctokit = getOctokit; + + /***/ }), /***/ 26: @@ -1935,13 +1980,13 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.getServerUrl = exports.getFetchUrl = void 0; +exports.isGhes = exports.getServerApiUrl = exports.getServerUrl = exports.getFetchUrl = void 0; const assert = __importStar(__webpack_require__(357)); const url_1 = __webpack_require__(835); function getFetchUrl(settings) { assert.ok(settings.repositoryOwner, 'settings.repositoryOwner must be defined'); assert.ok(settings.repositoryName, 'settings.repositoryName must be defined'); - const serviceUrl = getServerUrl(); + const serviceUrl = getServerUrl(settings.githubServerUrl); const encodedOwner = encodeURIComponent(settings.repositoryOwner); const encodedName = encodeURIComponent(settings.repositoryName); if (settings.sshKey) { @@ -1951,13 +1996,27 @@ function getFetchUrl(settings) { return `${serviceUrl.origin}/${encodedOwner}/${encodedName}`; } exports.getFetchUrl = getFetchUrl; -function getServerUrl() { - // todo: remove GITHUB_URL after support for GHES Alpha is no longer needed - return new url_1.URL(process.env['GITHUB_SERVER_URL'] || - process.env['GITHUB_URL'] || - 'https://github.com'); +function getServerUrl(url) { + let urlValue = url && url.trim().length > 0 + ? url + : process.env['GITHUB_SERVER_URL'] || 'https://github.com'; + return new url_1.URL(urlValue); } exports.getServerUrl = getServerUrl; +function getServerApiUrl(url) { + let apiUrl = 'https://api.github.com'; + if (isGhes(url)) { + const serverUrl = getServerUrl(url); + apiUrl = new url_1.URL(`${serverUrl.origin}/api/v3`).toString(); + } + return apiUrl; +} +exports.getServerApiUrl = getServerApiUrl; +function isGhes(url) { + const ghUrl = getServerUrl(url); + return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM'; +} +exports.isGhes = isGhes; /***/ }), @@ -4279,9 +4338,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }; Object.defineProperty(exports, "__esModule", { value: true }); exports.checkCommitInfo = exports.testRef = exports.getRefSpec = exports.getRefSpecForAllHistory = exports.getCheckoutInfo = exports.tagsRefSpec = void 0; -const url_1 = __webpack_require__(835); const core = __importStar(__webpack_require__(470)); const github = __importStar(__webpack_require__(469)); +const octokit_helper_1 = __webpack_require__(24); +const url_helper_1 = __webpack_require__(81); exports.tagsRefSpec = '+refs/tags/*:refs/tags/*'; function getCheckoutInfo(git, ref, commit) { return __awaiter(this, void 0, void 0, function* () { @@ -4431,12 +4491,12 @@ function testRef(git, ref, commit) { }); } exports.testRef = testRef; -function checkCommitInfo(token, commitInfo, repositoryOwner, repositoryName, ref, commit) { +function checkCommitInfo(token, commitInfo, repositoryOwner, repositoryName, ref, commit, baseUrl) { var _a, _b; return __awaiter(this, void 0, void 0, function* () { try { // GHES? - if (isGhes()) { + if ((0, url_helper_1.isGhes)(baseUrl)) { return; } // Auth token? @@ -4481,7 +4541,8 @@ function checkCommitInfo(token, commitInfo, repositoryOwner, repositoryName, ref const actualHeadSha = match[1]; if (actualHeadSha !== expectedHeadSha) { core.debug(`Expected head sha ${expectedHeadSha}; actual head sha ${actualHeadSha}`); - const octokit = new github.GitHub(token, { + const octokit = (0, octokit_helper_1.getOctokit)(token, { + baseUrl: baseUrl, userAgent: `actions-checkout-tracepoint/1.0 (code=STALE_MERGE;owner=${repositoryOwner};repo=${repositoryName};pr=${fromPayload('number')};run_id=${process.env['GITHUB_RUN_ID']};expected_head_sha=${expectedHeadSha};actual_head_sha=${actualHeadSha})` }); yield octokit.repos.get({ owner: repositoryOwner, repo: repositoryName }); @@ -4507,10 +4568,6 @@ function select(obj, path) { const key = path.substr(0, i); return select(obj[key], path.substr(i + 1)); } -function isGhes() { - const ghUrl = new url_1.URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com'); - return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM'; -} /***/ }), @@ -6561,7 +6618,7 @@ class GitAuthHelper { this.git = gitCommandManager; this.settings = gitSourceSettings || {}; // Token auth header - const serverUrl = urlHelper.getServerUrl(); + const serverUrl = urlHelper.getServerUrl(this.settings.githubServerUrl); this.tokenConfigKey = `http.${serverUrl.origin}/.extraheader`; // "origin" is SCHEME://HOSTNAME[:PORT] const basicCredential = Buffer.from(`x-access-token:${this.settings.authToken}`, 'utf8').toString('base64'); core.setSecret(basicCredential); @@ -7382,7 +7439,7 @@ function getSource(settings) { else if (settings.sshKey) { throw new Error(`Input 'ssh-key' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`); } - yield githubApiHelper.downloadRepository(settings.authToken, settings.repositoryOwner, settings.repositoryName, settings.ref, settings.commit, settings.repositoryPath); + yield githubApiHelper.downloadRepository(settings.authToken, settings.repositoryOwner, settings.repositoryName, settings.ref, settings.commit, settings.repositoryPath, settings.githubServerUrl); return; } // Save state for POST action @@ -7415,7 +7472,7 @@ function getSource(settings) { settings.ref = yield git.getDefaultBranch(repositoryUrl); } else { - settings.ref = yield githubApiHelper.getDefaultBranch(settings.authToken, settings.repositoryOwner, settings.repositoryName); + settings.ref = yield githubApiHelper.getDefaultBranch(settings.authToken, settings.repositoryOwner, settings.repositoryName, settings.githubServerUrl); } core.endGroup(); } @@ -7481,7 +7538,7 @@ function getSource(settings) { // Log commit sha yield git.log1("--format='%H'"); // Check for incorrect pull request merge commit - yield refHelper.checkCommitInfo(settings.authToken, commitInfo, settings.repositoryOwner, settings.repositoryName, settings.ref, settings.commit); + yield refHelper.checkCommitInfo(settings.authToken, commitInfo, settings.repositoryOwner, settings.repositoryName, settings.ref, settings.commit, settings.githubServerUrl); } finally { // Remove auth @@ -10966,24 +11023,24 @@ exports.getDefaultBranch = exports.downloadRepository = void 0; const assert = __importStar(__webpack_require__(357)); const core = __importStar(__webpack_require__(470)); const fs = __importStar(__webpack_require__(747)); -const github = __importStar(__webpack_require__(469)); const io = __importStar(__webpack_require__(1)); const path = __importStar(__webpack_require__(622)); const retryHelper = __importStar(__webpack_require__(587)); const toolCache = __importStar(__webpack_require__(533)); const v4_1 = __importDefault(__webpack_require__(826)); +const octokit_helper_1 = __webpack_require__(24); const IS_WINDOWS = process.platform === 'win32'; -function downloadRepository(authToken, owner, repo, ref, commit, repositoryPath) { +function downloadRepository(authToken, owner, repo, ref, commit, repositoryPath, baseUrl) { return __awaiter(this, void 0, void 0, function* () { // Determine the default branch if (!ref && !commit) { core.info('Determining the default branch'); - ref = yield getDefaultBranch(authToken, owner, repo); + ref = yield getDefaultBranch(authToken, owner, repo, baseUrl); } // Download the archive let archiveData = yield retryHelper.execute(() => __awaiter(this, void 0, void 0, function* () { core.info('Downloading the archive'); - return yield downloadArchive(authToken, owner, repo, ref, commit); + return yield downloadArchive(authToken, owner, repo, ref, commit, baseUrl); })); // Write archive to disk core.info('Writing archive to disk'); @@ -11027,12 +11084,12 @@ exports.downloadRepository = downloadRepository; /** * Looks up the default branch name */ -function getDefaultBranch(authToken, owner, repo) { +function getDefaultBranch(authToken, owner, repo, baseUrl) { return __awaiter(this, void 0, void 0, function* () { return yield retryHelper.execute(() => __awaiter(this, void 0, void 0, function* () { var _a; core.info('Retrieving the default branch name'); - const octokit = new github.GitHub(authToken); + const octokit = (0, octokit_helper_1.getOctokit)(authToken, { baseUrl: baseUrl }); let result; try { // Get the default branch from the repo info @@ -11062,9 +11119,9 @@ function getDefaultBranch(authToken, owner, repo) { }); } exports.getDefaultBranch = getDefaultBranch; -function downloadArchive(authToken, owner, repo, ref, commit) { +function downloadArchive(authToken, owner, repo, ref, commit, baseUrl) { return __awaiter(this, void 0, void 0, function* () { - const octokit = new github.GitHub(authToken); + const octokit = (0, octokit_helper_1.getOctokit)(authToken, { baseUrl: baseUrl }); const params = { owner: owner, repo: repo, @@ -17330,6 +17387,9 @@ function getInputs() { // Set safe.directory in git global config. result.setSafeDirectory = (core.getInput('set-safe-directory') || 'true').toUpperCase() === 'TRUE'; + // Determine the GitHub URL that the repository is being hosted from + result.githubServerUrl = core.getInput('github-server-url'); + core.debug(`GitHub Host URL = ${result.githubServerUrl}`); return result; }); } diff --git a/src/git-auth-helper.ts b/src/git-auth-helper.ts index 8a1c7c379..3c6db8e77 100644 --- a/src/git-auth-helper.ts +++ b/src/git-auth-helper.ts @@ -52,7 +52,7 @@ class GitAuthHelper { this.settings = gitSourceSettings || (({} as unknown) as IGitSourceSettings) // Token auth header - const serverUrl = urlHelper.getServerUrl() + const serverUrl = urlHelper.getServerUrl(this.settings.githubServerUrl) this.tokenConfigKey = `http.${serverUrl.origin}/.extraheader` // "origin" is SCHEME://HOSTNAME[:PORT] const basicCredential = Buffer.from( `x-access-token:${this.settings.authToken}`, diff --git a/src/git-source-provider.ts b/src/git-source-provider.ts index 545a7a3bd..48f20da28 100644 --- a/src/git-source-provider.ts +++ b/src/git-source-provider.ts @@ -93,7 +93,8 @@ export async function getSource(settings: IGitSourceSettings): Promise { settings.repositoryName, settings.ref, settings.commit, - settings.repositoryPath + settings.repositoryPath, + settings.githubServerUrl ) return } @@ -138,7 +139,8 @@ export async function getSource(settings: IGitSourceSettings): Promise { settings.ref = await githubApiHelper.getDefaultBranch( settings.authToken, settings.repositoryOwner, - settings.repositoryName + settings.repositoryName, + settings.githubServerUrl ) } core.endGroup() @@ -232,7 +234,8 @@ export async function getSource(settings: IGitSourceSettings): Promise { settings.repositoryOwner, settings.repositoryName, settings.ref, - settings.commit + settings.commit, + settings.githubServerUrl ) } finally { // Remove auth diff --git a/src/git-source-settings.ts b/src/git-source-settings.ts index 6fa3960fd..2da562266 100644 --- a/src/git-source-settings.ts +++ b/src/git-source-settings.ts @@ -83,4 +83,9 @@ export interface IGitSourceSettings { * Indicates whether to add repositoryPath as safe.directory in git global config */ setSafeDirectory: boolean + + /** + * User override on the GitHub Server/Host URL that hosts the repository to be cloned + */ + githubServerUrl: string | undefined } diff --git a/src/github-api-helper.ts b/src/github-api-helper.ts index 45b979a16..5c0ee1e03 100644 --- a/src/github-api-helper.ts +++ b/src/github-api-helper.ts @@ -1,13 +1,12 @@ import * as assert from 'assert' import * as core from '@actions/core' import * as fs from 'fs' -import * as github from '@actions/github' import * as io from '@actions/io' import * as path from 'path' import * as retryHelper from './retry-helper' import * as toolCache from '@actions/tool-cache' import {default as uuid} from 'uuid/v4' -import {Octokit} from '@octokit/rest' +import {getOctokit, Octokit} from './octokit-helper' const IS_WINDOWS = process.platform === 'win32' @@ -17,18 +16,19 @@ export async function downloadRepository( repo: string, ref: string, commit: string, - repositoryPath: string + repositoryPath: string, + baseUrl?: string ): Promise { // Determine the default branch if (!ref && !commit) { core.info('Determining the default branch') - ref = await getDefaultBranch(authToken, owner, repo) + ref = await getDefaultBranch(authToken, owner, repo, baseUrl) } // Download the archive let archiveData = await retryHelper.execute(async () => { core.info('Downloading the archive') - return await downloadArchive(authToken, owner, repo, ref, commit) + return await downloadArchive(authToken, owner, repo, ref, commit, baseUrl) }) // Write archive to disk @@ -79,11 +79,12 @@ export async function downloadRepository( export async function getDefaultBranch( authToken: string, owner: string, - repo: string + repo: string, + baseUrl?: string ): Promise { return await retryHelper.execute(async () => { core.info('Retrieving the default branch name') - const octokit = new github.GitHub(authToken) + const octokit = getOctokit(authToken, {baseUrl: baseUrl}) let result: string try { // Get the default branch from the repo info @@ -121,9 +122,10 @@ async function downloadArchive( owner: string, repo: string, ref: string, - commit: string + commit: string, + baseUrl?: string ): Promise { - const octokit = new github.GitHub(authToken) + const octokit = getOctokit(authToken, {baseUrl: baseUrl}) const params: Octokit.ReposGetArchiveLinkParams = { owner: owner, repo: repo, diff --git a/src/input-helper.ts b/src/input-helper.ts index 8c2f90157..237b06aff 100644 --- a/src/input-helper.ts +++ b/src/input-helper.ts @@ -125,5 +125,10 @@ export async function getInputs(): Promise { // Set safe.directory in git global config. result.setSafeDirectory = (core.getInput('set-safe-directory') || 'true').toUpperCase() === 'TRUE' + + // Determine the GitHub URL that the repository is being hosted from + result.githubServerUrl = core.getInput('github-server-url') + core.debug(`GitHub Host URL = ${result.githubServerUrl}`) + return result } diff --git a/src/octokit-helper.ts b/src/octokit-helper.ts new file mode 100644 index 000000000..4fb128574 --- /dev/null +++ b/src/octokit-helper.ts @@ -0,0 +1,23 @@ +import * as github from '@actions/github' +import {Octokit} from '@octokit/rest' +import {getServerApiUrl} from './url-helper' + +// Centralize all Octokit references by re-exporting +export {Octokit} from '@octokit/rest' + +export type OctokitOptions = { + baseUrl?: string + userAgent?: string +} + +export function getOctokit(authToken: string, opts: OctokitOptions) { + const options: Octokit.Options = { + baseUrl: getServerApiUrl(opts.baseUrl) + } + + if (opts.userAgent) { + options.userAgent = opts.userAgent + } + + return new github.GitHub(authToken, options) +} diff --git a/src/ref-helper.ts b/src/ref-helper.ts index 209f49d70..823f929e5 100644 --- a/src/ref-helper.ts +++ b/src/ref-helper.ts @@ -1,7 +1,8 @@ -import {URL} from 'url' import {IGitCommandManager} from './git-command-manager' import * as core from '@actions/core' import * as github from '@actions/github' +import {getOctokit} from './octokit-helper' +import {isGhes} from './url-helper' export const tagsRefSpec = '+refs/tags/*:refs/tags/*' @@ -183,11 +184,12 @@ export async function checkCommitInfo( repositoryOwner: string, repositoryName: string, ref: string, - commit: string + commit: string, + baseUrl?: string ): Promise { try { // GHES? - if (isGhes()) { + if (isGhes(baseUrl)) { return } @@ -243,7 +245,8 @@ export async function checkCommitInfo( core.debug( `Expected head sha ${expectedHeadSha}; actual head sha ${actualHeadSha}` ) - const octokit = new github.GitHub(token, { + const octokit = getOctokit(token, { + baseUrl: baseUrl, userAgent: `actions-checkout-tracepoint/1.0 (code=STALE_MERGE;owner=${repositoryOwner};repo=${repositoryName};pr=${fromPayload( 'number' )};run_id=${ @@ -276,10 +279,3 @@ function select(obj: any, path: string): any { const key = path.substr(0, i) return select(obj[key], path.substr(i + 1)) } - -function isGhes(): boolean { - const ghUrl = new URL( - process.env['GITHUB_SERVER_URL'] || 'https://github.com' - ) - return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM' -} diff --git a/src/url-helper.ts b/src/url-helper.ts index 05f1cbd67..6807b7f8e 100644 --- a/src/url-helper.ts +++ b/src/url-helper.ts @@ -1,6 +1,6 @@ import * as assert from 'assert' -import {IGitSourceSettings} from './git-source-settings' import {URL} from 'url' +import {IGitSourceSettings} from './git-source-settings' export function getFetchUrl(settings: IGitSourceSettings): string { assert.ok( @@ -8,7 +8,7 @@ export function getFetchUrl(settings: IGitSourceSettings): string { 'settings.repositoryOwner must be defined' ) assert.ok(settings.repositoryName, 'settings.repositoryName must be defined') - const serviceUrl = getServerUrl() + const serviceUrl = getServerUrl(settings.githubServerUrl) const encodedOwner = encodeURIComponent(settings.repositoryOwner) const encodedName = encodeURIComponent(settings.repositoryName) if (settings.sshKey) { @@ -19,11 +19,27 @@ export function getFetchUrl(settings: IGitSourceSettings): string { return `${serviceUrl.origin}/${encodedOwner}/${encodedName}` } -export function getServerUrl(): URL { - // todo: remove GITHUB_URL after support for GHES Alpha is no longer needed - return new URL( - process.env['GITHUB_SERVER_URL'] || - process.env['GITHUB_URL'] || - 'https://github.com' - ) +export function getServerUrl(url?: string): URL { + let urlValue = + url && url.trim().length > 0 + ? url + : process.env['GITHUB_SERVER_URL'] || 'https://github.com' + return new URL(urlValue) +} + +export function getServerApiUrl(url?: string): string { + let apiUrl = 'https://api.github.com' + + if (isGhes(url)) { + const serverUrl = getServerUrl(url) + apiUrl = new URL(`${serverUrl.origin}/api/v3`).toString() + } + + return apiUrl +} + +export function isGhes(url?: string): boolean { + const ghUrl = getServerUrl(url) + + return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM' }