10
0
Fork 0
mirror of https://github.com/actions/setup-python.git synced 2026-04-06 18:06:53 +00:00

feat: Add mirror and mirror-token inputs for custom Python distribution sources

Users who need custom CPython builds (internal mirrors, GHES-hosted forks,
special build configurations, compliance builds, air-gapped runners) could not
previously point setup-python at anything other than actions/python-versions.

Adds two new inputs:
- `mirror`: base URL hosting versions-manifest.json and the Python
  distributions it references. Defaults to the existing
  https://raw.githubusercontent.com/actions/python-versions/main.
- `mirror-token`: optional token used to authenticate requests to the mirror.

If `mirror` is a raw.githubusercontent.com/{owner}/{repo}/{branch} URL, the
manifest is fetched via the GitHub REST API (authenticated rate limit applies);
otherwise the action falls back to a direct GET of {mirror}/versions-manifest.json.

Token interaction
-----------------

`token` is never forwarded to arbitrary hosts. Auth resolution is per-URL:

  1. if mirror-token is set, use mirror-token
  2. else if token is set AND the target host is github.com,
     *.github.com, or *.githubusercontent.com, use token
  3. else send no auth

Cases:

  Default (no inputs set)
    mirror = default raw.githubusercontent.com URL, mirror-token empty,
    token = github.token.
    → manifest API call and tarball downloads use `token`.
    Identical to prior behavior.

  Custom raw.githubusercontent.com mirror (e.g. personal fork)
    mirror-token empty, token = github.token.
    → manifest API call and tarball downloads use `token`
      (target hosts are GitHub-owned).

  Custom non-GitHub mirror, no mirror-token
    mirror-token empty, token = github.token.
    → manifest fetched via direct URL (no auth attached),
      tarball downloads use no auth.
    `token` is NOT forwarded to the custom host — this is the
    leak-prevention case.

  Custom non-GitHub mirror with mirror-token
    mirror-token set, token may be set.
    → manifest fetch and tarball downloads use `mirror-token`.

  Custom GitHub mirror with both tokens set
    mirror-token wins. Used for both the manifest API call and
    tarball downloads.
This commit is contained in:
Ludovic Henry 2026-04-06 00:59:40 +02:00
parent 28f2168f4d
commit 8b57351c0f
7 changed files with 441 additions and 41 deletions

83
dist/setup/index.js vendored
View file

@ -83007,7 +83007,7 @@ async function useCpythonVersion(version, architecture, updateEnvironment, check
if (freethreaded) {
msg.push(`Free threaded versions are only available for Python 3.13.0 and later.`);
}
msg.push(`The list of all available versions can be found here: ${installer.MANIFEST_URL}`);
msg.push(`The list of all available versions can be found here: ${installer.getManifestUrl()}`);
throw new Error(msg.join(os.EOL));
}
const _binDir = binDir(installDir);
@ -83617,7 +83617,7 @@ var __importStar = (this && this.__importStar) || (function () {
};
})();
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.MANIFEST_URL = void 0;
exports.getManifestUrl = getManifestUrl;
exports.findReleaseFromManifest = findReleaseFromManifest;
exports.getManifest = getManifest;
exports.getManifestFromRepo = getManifestFromRepo;
@ -83629,12 +83629,56 @@ const tc = __importStar(__nccwpck_require__(33472));
const exec = __importStar(__nccwpck_require__(95236));
const httpm = __importStar(__nccwpck_require__(54844));
const utils_1 = __nccwpck_require__(71798);
const TOKEN = core.getInput('token');
const AUTH = !TOKEN ? undefined : `token ${TOKEN}`;
const MANIFEST_REPO_OWNER = 'actions';
const MANIFEST_REPO_NAME = 'python-versions';
const MANIFEST_REPO_BRANCH = 'main';
exports.MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`;
const DEFAULT_REPO_OWNER = 'actions';
const DEFAULT_REPO_NAME = 'python-versions';
const DEFAULT_REPO_BRANCH = 'main';
const DEFAULT_MIRROR = `https://raw.githubusercontent.com/${DEFAULT_REPO_OWNER}/${DEFAULT_REPO_NAME}/${DEFAULT_REPO_BRANCH}`;
// Matches https://raw.githubusercontent.com/{owner}/{repo}/{branch}
const REPO_COORDS_RE = /^https:\/\/raw\.githubusercontent\.com\/([^/]+)\/([^/]+)\/([^/]+)\/?$/;
function getToken() {
return core.getInput('token');
}
function getMirrorToken() {
return core.getInput('mirror-token');
}
function getMirror() {
const raw = (core.getInput('mirror') || DEFAULT_MIRROR)
.trim()
.replace(/\/+$/, '');
try {
new URL(raw);
}
catch {
throw new Error(`Invalid 'mirror' URL: "${raw}"`);
}
return raw;
}
function getManifestUrl() {
return `${getMirror()}/versions-manifest.json`;
}
function resolveRepoCoords() {
const m = REPO_COORDS_RE.exec(getMirror());
return m ? { owner: m[1], repo: m[2], branch: m[3] } : null;
}
function authForUrl(url) {
const mirrorToken = getMirrorToken();
if (mirrorToken)
return `token ${mirrorToken}`;
let host;
try {
host = new URL(url).host;
}
catch {
return undefined;
}
const token = getToken();
if (token &&
(host === 'github.com' ||
host.endsWith('.github.com') ||
host.endsWith('.githubusercontent.com')))
return `token ${token}`;
return undefined;
}
async function findReleaseFromManifest(semanticVersionSpec, architecture, manifest) {
if (!manifest) {
manifest = await getManifest();
@ -83675,15 +83719,28 @@ async function getManifest() {
return await getManifestFromURL();
}
function getManifestFromRepo() {
core.debug(`Getting manifest from ${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}@${MANIFEST_REPO_BRANCH}`);
return tc.getManifestFromRepo(MANIFEST_REPO_OWNER, MANIFEST_REPO_NAME, AUTH, MANIFEST_REPO_BRANCH);
const coords = resolveRepoCoords();
if (!coords) {
throw new Error(`Mirror "${getMirror()}" is not a GitHub repo URL; falling back to raw URL fetch.`);
}
core.debug(`Getting manifest from ${coords.owner}/${coords.repo}@${coords.branch}`);
// api.github.com is a GitHub-owned URL. Prefer MIRROR_TOKEN (the user provided token), fall back to TOKEN.
const token = getToken();
const mirrorToken = getMirrorToken();
const auth = !mirrorToken
? !token
? undefined
: `token ${token}`
: `token ${mirrorToken}`;
return tc.getManifestFromRepo(coords.owner, coords.repo, auth, coords.branch);
}
async function getManifestFromURL() {
core.debug('Falling back to fetching the manifest using raw URL.');
const manifestUrl = getManifestUrl();
const http = new httpm.HttpClient('tool-cache');
const response = await http.getJson(exports.MANIFEST_URL);
const response = await http.getJson(manifestUrl);
if (!response.result) {
throw new Error(`Unable to get manifest from ${exports.MANIFEST_URL}`);
throw new Error(`Unable to get manifest from ${manifestUrl}`);
}
return response.result;
}
@ -83720,7 +83777,7 @@ async function installCpythonFromRelease(release) {
let pythonPath = '';
try {
const fileName = (0, utils_1.getDownloadFileName)(downloadUrl);
pythonPath = await tc.downloadTool(downloadUrl, fileName, AUTH);
pythonPath = await tc.downloadTool(downloadUrl, fileName, authForUrl(downloadUrl));
core.info('Extract downloaded archive');
let pythonExtractedFolder;
if (utils_1.IS_WINDOWS) {