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:
parent
8549b9f8f5
commit
f30f2fee26
7 changed files with 559 additions and 35 deletions
91
dist/setup/index.js
vendored
91
dist/setup/index.js
vendored
|
|
@ -98693,12 +98693,56 @@ function _unique(values) {
|
|||
|
||||
|
||||
|
||||
const TOKEN = getInput('token');
|
||||
const AUTH = !TOKEN ? undefined : `token ${TOKEN}`;
|
||||
const MANIFEST_REPO_OWNER = 'actions';
|
||||
const MANIFEST_REPO_NAME = 'python-versions';
|
||||
const MANIFEST_REPO_BRANCH = 'main';
|
||||
const 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 getInput('token');
|
||||
}
|
||||
function getMirrorToken() {
|
||||
return getInput('mirror-token');
|
||||
}
|
||||
function getMirror() {
|
||||
const raw = (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;
|
||||
}
|
||||
function getLinuxOsRelease() {
|
||||
try {
|
||||
const content = external_fs_namespaceObject.readFileSync('/etc/os-release', 'utf8');
|
||||
|
|
@ -98846,15 +98890,28 @@ async function getManifest() {
|
|||
}
|
||||
}
|
||||
function install_python_getManifestFromRepo() {
|
||||
core_debug(`Getting manifest from ${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}@${MANIFEST_REPO_BRANCH}`);
|
||||
return 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 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 lib_HttpClient('tool-cache');
|
||||
const response = await http.getJson(MANIFEST_URL);
|
||||
const response = await http.getJson(manifestUrl);
|
||||
if (!response.result) {
|
||||
throw new Error(`Unable to get manifest from ${MANIFEST_URL}`);
|
||||
throw new Error(`Unable to get manifest from ${manifestUrl}`);
|
||||
}
|
||||
return response.result;
|
||||
}
|
||||
|
|
@ -98897,7 +98954,7 @@ async function installCpythonFromRelease(release) {
|
|||
let pythonPath = '';
|
||||
try {
|
||||
const fileName = getDownloadFileName(downloadUrl);
|
||||
pythonPath = await downloadTool(downloadUrl, fileName, AUTH);
|
||||
pythonPath = await downloadTool(downloadUrl, fileName, authForUrl(downloadUrl));
|
||||
info('Extract downloaded archive');
|
||||
let pythonExtractedFolder;
|
||||
if (utils_IS_WINDOWS) {
|
||||
|
|
@ -99015,7 +99072,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: ${MANIFEST_URL}`);
|
||||
msg.push(`The list of all available versions can be found here: ${getManifestUrl()}`);
|
||||
throw new Error(msg.join(external_os_.EOL));
|
||||
}
|
||||
const _binDir = binDir(installDir);
|
||||
|
|
@ -99417,8 +99474,8 @@ function findPyPyInstallDirForWindows(pythonVersion) {
|
|||
|
||||
|
||||
|
||||
const install_graalpy_TOKEN = getInput('token');
|
||||
const install_graalpy_AUTH = !install_graalpy_TOKEN ? undefined : `token ${install_graalpy_TOKEN}`;
|
||||
const TOKEN = getInput('token');
|
||||
const AUTH = !TOKEN ? undefined : `token ${TOKEN}`;
|
||||
async function installGraalPy(graalpyVersion, architecture, allowPreReleases, releases) {
|
||||
let downloadDir;
|
||||
releases = releases ?? (await getAvailableGraalPyVersions());
|
||||
|
|
@ -99441,7 +99498,7 @@ async function installGraalPy(graalpyVersion, architecture, allowPreReleases, re
|
|||
const downloadUrl = `${foundAsset.browser_download_url}`;
|
||||
info(`Downloading GraalPy from "${downloadUrl}" ...`);
|
||||
try {
|
||||
const graalpyPath = await downloadTool(downloadUrl, undefined, install_graalpy_AUTH);
|
||||
const graalpyPath = await downloadTool(downloadUrl, undefined, AUTH);
|
||||
info('Extracting downloaded archive...');
|
||||
if (utils_IS_WINDOWS) {
|
||||
downloadDir = await extractZip(graalpyPath);
|
||||
|
|
@ -99482,8 +99539,8 @@ async function installGraalPy(graalpyVersion, architecture, allowPreReleases, re
|
|||
async function getAvailableGraalPyVersions() {
|
||||
const http = new lib_HttpClient('tool-cache');
|
||||
const headers = {};
|
||||
if (install_graalpy_AUTH) {
|
||||
headers.authorization = install_graalpy_AUTH;
|
||||
if (AUTH) {
|
||||
headers.authorization = AUTH;
|
||||
}
|
||||
/*
|
||||
Get releases first.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue