diff --git a/.github/workflows/e2e-cache.yml b/.github/workflows/e2e-cache.yml index 5f4ef03a..a3c9a2ba 100644 --- a/.github/workflows/e2e-cache.yml +++ b/.github/workflows/e2e-cache.yml @@ -78,8 +78,12 @@ jobs: python-version: pypy-3.11-v7.x - os: ubuntu-22.04 python-version: pypy-3.11-v7.x + - os: ubuntu-22.04-arm + python-version: pypy-3.10-v7.x - os: ubuntu-22.04-arm python-version: pypy-3.11-v7.x + - os: ubuntu-22.04-arm + python-version: pypy-3.10-v7.x steps: - uses: actions/checkout@v4 - name: Setup Python diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 2dc11124..fa478ac6 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -19,7 +19,6 @@ jobs: matrix: operating-system: [ - ubuntu-20.04, windows-latest, ubuntu-22.04, ubuntu-22.04-arm, diff --git a/.github/workflows/test-graalpy.yml b/.github/workflows/test-graalpy.yml index 7dfee4e3..90841c3b 100644 --- a/.github/workflows/test-graalpy.yml +++ b/.github/workflows/test-graalpy.yml @@ -21,7 +21,6 @@ jobs: os: [ macos-latest, - ubuntu-20.04, ubuntu-22.04, ubuntu-22.04-arm, ubuntu-24.04-arm, @@ -77,7 +76,6 @@ jobs: os: [ macos-latest, - ubuntu-20.04, ubuntu-22.04, ubuntu-22.04-arm, ubuntu-24.04-arm, diff --git a/.github/workflows/test-pypy.yml b/.github/workflows/test-pypy.yml index 94a06c8a..54466e52 100644 --- a/.github/workflows/test-pypy.yml +++ b/.github/workflows/test-pypy.yml @@ -24,7 +24,6 @@ jobs: [ macos-latest, windows-latest, - ubuntu-20.04, ubuntu-22.04, ubuntu-22.04-arm, ubuntu-24.04-arm, @@ -142,7 +141,6 @@ jobs: [ macos-latest, windows-latest, - ubuntu-20.04, ubuntu-22.04, ubuntu-22.04-arm, ubuntu-24.04-arm, @@ -177,7 +175,6 @@ jobs: [ macos-latest, windows-latest, - ubuntu-20.04, ubuntu-22.04, ubuntu-22.04-arm, ubuntu-24.04-arm, @@ -220,7 +217,6 @@ jobs: [ macos-latest, windows-latest, - ubuntu-20.04, ubuntu-22.04, ubuntu-22.04-arm, ubuntu-24.04-arm, diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 7f2d9637..d89646f3 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -24,7 +24,6 @@ jobs: [ macos-latest, windows-latest, - ubuntu-20.04, ubuntu-22.04, ubuntu-22.04-arm, macos-13, @@ -69,7 +68,6 @@ jobs: [ macos-latest, windows-latest, - ubuntu-20.04, ubuntu-22.04, ubuntu-22.04-arm, macos-13, @@ -117,7 +115,6 @@ jobs: [ macos-latest, windows-latest, - ubuntu-20.04, ubuntu-22.04, ubuntu-22.04-arm, macos-13, @@ -163,7 +160,6 @@ jobs: [ macos-latest, windows-latest, - ubuntu-20.04, ubuntu-22.04, ubuntu-22.04-arm, macos-13, @@ -214,7 +210,6 @@ jobs: [ macos-latest, windows-latest, - ubuntu-20.04, ubuntu-22.04, ubuntu-22.04-arm, macos-13, @@ -265,7 +260,6 @@ jobs: [ macos-latest, windows-latest, - ubuntu-20.04, ubuntu-22.04, ubuntu-22.04-arm, macos-13, @@ -300,7 +294,6 @@ jobs: [ macos-latest, windows-latest, - ubuntu-20.04, ubuntu-22.04, ubuntu-22.04-arm, macos-13, @@ -419,7 +412,6 @@ jobs: [ macos-latest, windows-latest, - ubuntu-20.04, ubuntu-22.04, ubuntu-22.04-arm, macos-13, diff --git a/__tests__/install-python.test.ts b/__tests__/install-python.test.ts index c3a6e7b4..51f9fa77 100644 --- a/__tests__/install-python.test.ts +++ b/__tests__/install-python.test.ts @@ -8,10 +8,29 @@ import * as tc from '@actions/tool-cache'; jest.mock('@actions/http-client'); jest.mock('@actions/tool-cache'); - -const mockManifest = [{version: '1.0.0'}]; +jest.mock('@actions/tool-cache', () => ({ + getManifestFromRepo: jest.fn() +})); +const mockManifest = [ + { + version: '1.0.0', + stable: true, + files: [ + { + filename: 'tool-v1.0.0-linux-x64.tar.gz', + platform: 'linux', + arch: 'x64', + download_url: 'https://example.com/tool-v1.0.0-linux-x64.tar.gz' + } + ] + } +]; describe('getManifest', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + it('should return manifest from repo', async () => { (tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest); const manifest = await getManifest(); diff --git a/dist/setup/index.js b/dist/setup/index.js index 2cdcfaad..ec5261ef 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -97461,16 +97461,36 @@ function findReleaseFromManifest(semanticVersionSpec, architecture, manifest) { }); } exports.findReleaseFromManifest = findReleaseFromManifest; +function isIToolRelease(obj) { + return (typeof obj === 'object' && + obj !== null && + typeof obj.version === 'string' && + typeof obj.stable === 'boolean' && + Array.isArray(obj.files) && + obj.files.every((file) => typeof file.filename === 'string' && + typeof file.platform === 'string' && + typeof file.arch === 'string' && + typeof file.download_url === 'string')); +} function getManifest() { return __awaiter(this, void 0, void 0, function* () { try { - return yield getManifestFromRepo(); + const repoManifest = yield getManifestFromRepo(); + if (Array.isArray(repoManifest) && + repoManifest.length && + repoManifest.every(isIToolRelease)) { + return repoManifest; + } + throw new Error('The repository manifest is invalid or does not include any valid tool release (IToolRelease) entries.'); } catch (err) { core.debug('Fetching the manifest via the API failed.'); if (err instanceof Error) { core.debug(err.message); } + else { + core.error('An unexpected error occurred while fetching the manifest.'); + } } return yield getManifestFromURL(); }); @@ -97518,6 +97538,9 @@ function installPython(workingDirectory) { } function installCpythonFromRelease(release) { return __awaiter(this, void 0, void 0, function* () { + if (!release.files || release.files.length === 0) { + throw new Error('No files found in the release to download.'); + } const downloadUrl = release.files[0].download_url; core.info(`Download from "${downloadUrl}"`); let pythonPath = ''; @@ -97538,8 +97561,11 @@ function installCpythonFromRelease(release) { catch (err) { if (err instanceof tc.HTTPError) { // Rate limit? - if (err.httpStatusCode === 403 || err.httpStatusCode === 429) { - core.info(`Received HTTP status code ${err.httpStatusCode}. This usually indicates the rate limit has been exceeded`); + if (err.httpStatusCode === 403) { + core.error(`Received HTTP status code 403. This indicates a permission issue or restricted access.`); + } + else if (err.httpStatusCode === 429) { + core.info(`Received HTTP status code 429. This usually indicates the rate limit has been exceeded`); } else { core.info(err.message); diff --git a/src/install-python.ts b/src/install-python.ts index d3421bf8..bef0161c 100644 --- a/src/install-python.ts +++ b/src/install-python.ts @@ -5,6 +5,7 @@ import * as exec from '@actions/exec'; import * as httpm from '@actions/http-client'; import {ExecOptions} from '@actions/exec/lib/interfaces'; import {IS_WINDOWS, IS_LINUX, getDownloadFileName} from './utils'; +import {IToolRelease} from '@actions/tool-cache'; const TOKEN = core.getInput('token'); const AUTH = !TOKEN ? undefined : `token ${TOKEN}`; @@ -31,14 +32,41 @@ export async function findReleaseFromManifest( return foundRelease; } - +function isIToolRelease(obj: any): obj is IToolRelease { + return ( + typeof obj === 'object' && + obj !== null && + typeof obj.version === 'string' && + typeof obj.stable === 'boolean' && + Array.isArray(obj.files) && + obj.files.every( + (file: any) => + typeof file.filename === 'string' && + typeof file.platform === 'string' && + typeof file.arch === 'string' && + typeof file.download_url === 'string' + ) + ); +} export async function getManifest(): Promise { try { - return await getManifestFromRepo(); + const repoManifest = await getManifestFromRepo(); + if ( + Array.isArray(repoManifest) && + repoManifest.length && + repoManifest.every(isIToolRelease) + ) { + return repoManifest; + } + throw new Error( + 'The repository manifest is invalid or does not include any valid tool release (IToolRelease) entries.' + ); } catch (err) { core.debug('Fetching the manifest via the API failed.'); if (err instanceof Error) { core.debug(err.message); + } else { + core.error('An unexpected error occurred while fetching the manifest.'); } } return await getManifestFromURL(); @@ -93,6 +121,9 @@ async function installPython(workingDirectory: string) { } export async function installCpythonFromRelease(release: tc.IToolRelease) { + if (!release.files || release.files.length === 0) { + throw new Error('No files found in the release to download.'); + } const downloadUrl = release.files[0].download_url; core.info(`Download from "${downloadUrl}"`); @@ -113,9 +144,13 @@ export async function installCpythonFromRelease(release: tc.IToolRelease) { } catch (err) { if (err instanceof tc.HTTPError) { // Rate limit? - if (err.httpStatusCode === 403 || err.httpStatusCode === 429) { + if (err.httpStatusCode === 403) { + core.error( + `Received HTTP status code 403. This indicates a permission issue or restricted access.` + ); + } else if (err.httpStatusCode === 429) { core.info( - `Received HTTP status code ${err.httpStatusCode}. This usually indicates the rate limit has been exceeded` + `Received HTTP status code 429. This usually indicates the rate limit has been exceeded` ); } else { core.info(err.message);