From 23b20767928cd4e28b6f4418924f17c7d3675943 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:16:57 +0100 Subject: [PATCH] releases: fallback to github api if github cdn fails Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- __tests__/buildx/install.test.itg.ts | 5 +- __tests__/buildx/install.test.ts | 3 + __tests__/compose/install.test.itg.ts | 5 +- __tests__/compose/install.test.ts | 3 + __tests__/docker/install.test.itg.ts | 5 +- __tests__/docker/install.test.ts | 3 + __tests__/github.test.ts | 25 +-------- __tests__/github.unmock.test.ts | 73 +++++++++++++++++++++++++ __tests__/regclient/install.test.itg.ts | 5 +- __tests__/regclient/install.test.ts | 3 + __tests__/undock/install.test.itg.ts | 5 +- __tests__/undock/install.test.ts | 3 + __tests__/undock/undock.test.itg.ts | 5 +- src/github.ts | 26 +++++++++ 14 files changed, 139 insertions(+), 30 deletions(-) create mode 100644 __tests__/github.unmock.test.ts diff --git a/__tests__/buildx/install.test.itg.ts b/__tests__/buildx/install.test.itg.ts index 3041f2de..be80de0f 100644 --- a/__tests__/buildx/install.test.itg.ts +++ b/__tests__/buildx/install.test.itg.ts @@ -14,13 +14,16 @@ * limitations under the License. */ -import {describe, expect, test} from '@jest/globals'; +import {describe, expect, jest, test} from '@jest/globals'; import * as fs from 'fs'; import {Install} from '../../src/buildx/install'; const maybe = !process.env.GITHUB_ACTIONS || (process.env.GITHUB_ACTIONS === 'true' && process.env.ImageOS && process.env.ImageOS.startsWith('ubuntu')) ? describe : describe.skip; +// needs GitHub REST API to get releases JSON +jest.unmock('@actions/github'); + maybe('download', () => { // prettier-ignore test.each(['latest'])( diff --git a/__tests__/buildx/install.test.ts b/__tests__/buildx/install.test.ts index 20430266..06603969 100644 --- a/__tests__/buildx/install.test.ts +++ b/__tests__/buildx/install.test.ts @@ -25,6 +25,9 @@ import {Install} from '../../src/buildx/install'; const tmpDir = fs.mkdtempSync(path.join(process.env.TEMP || os.tmpdir(), 'buildx-install-')); +// needs GitHub REST API to get releases JSON +jest.unmock('@actions/github'); + afterEach(function () { rimraf.sync(tmpDir); }); diff --git a/__tests__/compose/install.test.itg.ts b/__tests__/compose/install.test.itg.ts index 3c330a39..74980e0b 100644 --- a/__tests__/compose/install.test.itg.ts +++ b/__tests__/compose/install.test.itg.ts @@ -14,13 +14,16 @@ * limitations under the License. */ -import {describe, expect, test} from '@jest/globals'; +import {describe, expect, jest, test} from '@jest/globals'; import * as fs from 'fs'; import {Install} from '../../src/compose/install'; const maybe = !process.env.GITHUB_ACTIONS || (process.env.GITHUB_ACTIONS === 'true' && process.env.ImageOS && process.env.ImageOS.startsWith('ubuntu')) ? describe : describe.skip; +// needs GitHub REST API to get releases JSON +jest.unmock('@actions/github'); + maybe('download', () => { // prettier-ignore test.each(['latest'])( diff --git a/__tests__/compose/install.test.ts b/__tests__/compose/install.test.ts index c87ea0cd..9aa09648 100644 --- a/__tests__/compose/install.test.ts +++ b/__tests__/compose/install.test.ts @@ -25,6 +25,9 @@ import {Install} from '../../src/compose/install'; const tmpDir = fs.mkdtempSync(path.join(process.env.TEMP || os.tmpdir(), 'compose-install-')); +// needs GitHub REST API to get releases JSON +jest.unmock('@actions/github'); + afterEach(function () { rimraf.sync(tmpDir); }); diff --git a/__tests__/docker/install.test.itg.ts b/__tests__/docker/install.test.itg.ts index e3766897..22df927f 100644 --- a/__tests__/docker/install.test.itg.ts +++ b/__tests__/docker/install.test.itg.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import {beforeAll, describe, test, expect} from '@jest/globals'; +import {beforeAll, describe, jest, test, expect} from '@jest/globals'; import fs from 'fs'; import os from 'os'; import path from 'path'; @@ -27,6 +27,9 @@ import {Exec} from '../../src/exec'; const tmpDir = () => fs.mkdtempSync(path.join(process.env.TEMP || os.tmpdir(), 'docker-install-itg-')); +// needs GitHub REST API to get releases JSON +jest.unmock('@actions/github'); + beforeAll(async () => { const undockInstall = new UndockInstall(); const undockBinPath = await undockInstall.download('v0.10.0', true); diff --git a/__tests__/docker/install.test.ts b/__tests__/docker/install.test.ts index 7b1d1ce3..b7706399 100644 --- a/__tests__/docker/install.test.ts +++ b/__tests__/docker/install.test.ts @@ -25,6 +25,9 @@ import {Install, InstallSourceArchive, InstallSourceImage} from '../../src/docke const tmpDir = fs.mkdtempSync(path.join(process.env.TEMP || os.tmpdir(), 'docker-install-')); +// needs GitHub REST API to get releases JSON +jest.unmock('@actions/github'); + afterEach(function () { rimraf.sync(tmpDir); }); diff --git a/__tests__/github.test.ts b/__tests__/github.test.ts index 7ec77481..4f78cd97 100644 --- a/__tests__/github.test.ts +++ b/__tests__/github.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import {describe, expect, jest, it, beforeEach, afterEach, test} from '@jest/globals'; +import {describe, expect, jest, it, beforeEach, afterEach} from '@jest/globals'; import * as fs from 'fs'; import * as path from 'path'; import * as core from '@actions/core'; @@ -43,29 +43,6 @@ describe('context', () => { }); }); -describe('releases', () => { - // prettier-ignore - test.each([ - ['.github/buildx-lab-releases.json'], - ['.github/buildx-releases.json'], - ['.github/compose-lab-releases.json'], - ['.github/compose-releases.json'], - ['.github/docker-releases.json'], - ['.github/regclient-releases.json'], - ['.github/undock-releases.json'], - ])('returns %p', async (path: string) => { - const github = new GitHub(); - const releases = await github.releases('App', { - owner: 'docker', - repo: 'actions-toolkit', - ref: 'main', - path: path - }); - expect(releases).toBeDefined(); - expect(Object.keys(releases).length).toBeGreaterThan(0); - }); -}); - describe('serverURL', () => { const originalEnv = process.env; beforeEach(() => { diff --git a/__tests__/github.unmock.test.ts b/__tests__/github.unmock.test.ts new file mode 100644 index 00000000..5ded311c --- /dev/null +++ b/__tests__/github.unmock.test.ts @@ -0,0 +1,73 @@ +/** + * Copyright 2025 actions-toolkit authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {describe, expect, jest, it, test} from '@jest/globals'; + +import {GitHub} from '../src/github'; + +// needs GitHub REST API to get releases JSON +jest.unmock('@actions/github'); + +describe('releases', () => { + it('returns Undock releases JSON', async () => { + const github = new GitHub(); + const releases = await github.releases('Undock', { + owner: 'docker', + repo: 'actions-toolkit', + ref: 'main', + path: '.github/undock-releases.json' + }); + expect(releases).toBeDefined(); + expect(Object.keys(releases).length).toBeGreaterThan(0); + }); +}); + +describe('releasesRaw', () => { + // prettier-ignore + test.each([ + ['.github/buildx-lab-releases.json'], + ['.github/buildx-releases.json'], + ['.github/compose-lab-releases.json'], + ['.github/compose-releases.json'], + ['.github/docker-releases.json'], + ['.github/regclient-releases.json'], + ['.github/undock-releases.json'], + ])('returns %p using GitHub CDN', async (path: string) => { + const github = new GitHub(); + const releases = await github.releasesRaw('Undock', { + owner: 'docker', + repo: 'actions-toolkit', + ref: 'main', + path: path + }); + expect(releases).toBeDefined(); + expect(Object.keys(releases).length).toBeGreaterThan(0); + }); +}); + +describe('releasesAPI', () => { + it('returns Undock releases JSON using GitHub API', async () => { + const github = new GitHub(); + const releases = await github.releasesAPI('Undock', { + owner: 'docker', + repo: 'actions-toolkit', + ref: 'main', + path: '.github/undock-releases.json' + }); + expect(releases).toBeDefined(); + expect(Object.keys(releases).length).toBeGreaterThan(0); + }); +}); diff --git a/__tests__/regclient/install.test.itg.ts b/__tests__/regclient/install.test.itg.ts index 93231864..6272bc3f 100644 --- a/__tests__/regclient/install.test.itg.ts +++ b/__tests__/regclient/install.test.itg.ts @@ -14,11 +14,14 @@ * limitations under the License. */ -import {describe, expect, test} from '@jest/globals'; +import {describe, expect, jest, test} from '@jest/globals'; import * as fs from 'fs'; import {Install} from '../../src/regclient/install'; +// needs GitHub REST API to get releases JSON +jest.unmock('@actions/github'); + describe('download', () => { // prettier-ignore test.each(['latest'])( diff --git a/__tests__/regclient/install.test.ts b/__tests__/regclient/install.test.ts index 132fd7a0..533bd800 100644 --- a/__tests__/regclient/install.test.ts +++ b/__tests__/regclient/install.test.ts @@ -25,6 +25,9 @@ import {Install} from '../../src/regclient/install'; const tmpDir = fs.mkdtempSync(path.join(process.env.TEMP || os.tmpdir(), 'regclient-install-')); +// needs GitHub REST API to get releases JSON +jest.unmock('@actions/github'); + afterEach(function () { rimraf.sync(tmpDir); }); diff --git a/__tests__/undock/install.test.itg.ts b/__tests__/undock/install.test.itg.ts index b5aaafe0..69acc02c 100644 --- a/__tests__/undock/install.test.itg.ts +++ b/__tests__/undock/install.test.itg.ts @@ -14,11 +14,14 @@ * limitations under the License. */ -import {describe, expect, test} from '@jest/globals'; +import {describe, expect, jest, test} from '@jest/globals'; import * as fs from 'fs'; import {Install} from '../../src/undock/install'; +// needs GitHub REST API to get releases JSON +jest.unmock('@actions/github'); + describe('download', () => { // prettier-ignore test.each(['latest'])( diff --git a/__tests__/undock/install.test.ts b/__tests__/undock/install.test.ts index fe604cff..8f2a53f5 100644 --- a/__tests__/undock/install.test.ts +++ b/__tests__/undock/install.test.ts @@ -25,6 +25,9 @@ import {Install} from '../../src/undock/install'; const tmpDir = fs.mkdtempSync(path.join(process.env.TEMP || os.tmpdir(), 'undock-install-')); +// needs GitHub REST API to get releases JSON +jest.unmock('@actions/github'); + afterEach(function () { rimraf.sync(tmpDir); }); diff --git a/__tests__/undock/undock.test.itg.ts b/__tests__/undock/undock.test.itg.ts index 120adfb5..323c1d88 100644 --- a/__tests__/undock/undock.test.itg.ts +++ b/__tests__/undock/undock.test.itg.ts @@ -14,13 +14,16 @@ * limitations under the License. */ -import {describe, expect, it} from '@jest/globals'; +import {describe, expect, jest, it} from '@jest/globals'; import fs from 'fs'; import os from 'os'; import {Undock} from '../../src/undock/undock'; import {Install as UndockInstall} from '../../src/undock/install'; +// needs GitHub REST API to get releases JSON +jest.unmock('@actions/github'); + describe('run', () => { it('extracts moby/moby-bin:26.1.5', async () => { const install = new UndockInstall(); diff --git a/src/github.ts b/src/github.ts index 8d285c4d..a14d8337 100644 --- a/src/github.ts +++ b/src/github.ts @@ -58,6 +58,20 @@ export class GitHub { } public async releases(name: string, opts: GitHubContentOpts): Promise> { + let releases: Record; + try { + releases = await this.releasesRaw(name, opts); + } catch (error) { + try { + releases = await this.releasesAPI(name, opts); + } catch (error) { + throw new Error(`Failed to get ${name} releases: ${error instanceof Error ? error.message : error}`); + } + } + return releases; + } + + public async releasesRaw(name: string, opts: GitHubContentOpts): Promise> { const url = `https://raw.githubusercontent.com/${opts.owner}/${opts.repo}/${opts.ref}/${opts.path}`; const http: httpm.HttpClient = new httpm.HttpClient('docker-actions-toolkit'); // prettier-ignore @@ -72,6 +86,18 @@ export class GitHub { return >JSON.parse(dt); } + public async releasesAPI(name: string, opts: GitHubContentOpts): Promise> { + const apiResp = await this.octokit.rest.repos.getContent({ + owner: opts.owner, + repo: opts.repo, + ref: opts.ref, + path: opts.path + }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const dt = Buffer.from((apiResp.data as any).content, (apiResp.data as any).encoding).toString(); + return >JSON.parse(dt); + } + static get context(): Context { return github.context; }