mirror of
https://github.com/actions/setup-java.git
synced 2026-06-29 18:40:33 +03:00
b150355f04
* Add verify-signature plumbing and Temurin verification support * Rebuild dist after signature verification changes * Refine signature verification errors and regenerate dist * refactor: make gpg.ts generic, move Adoptium-specific constant to temurin distribution * fix: mock renameWinArchive in temurin tests and add signature e2e job * refactor: bundle Adoptium public key, replace keyserver lookup with local import * feat: add verify-signature-public-key input to allow custom GPG key override * refactor: extract Adoptium public key to adoptium-key.ts; tighten gpg.ts cleanup scope * Add verify-signature plumbing and Temurin verification support * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Add Microsoft signature verification support * Regenerate dist bundles for Microsoft signature checks * Harden Microsoft signature URL handling * Add setup-java-microsoft-signature-verification e2e job * chore: regenerate dist files * Fix e2e-versions: remove duplicate job, update signature jobs to checkout@v7 with env vars * Fix Prettier formatting in test files * fix: mock renameWinArchive in microsoft-installer tests to fix Windows CI failure * fix: use --homedir flag instead of GNUPGHOME env var for Windows GPG compatibility The Git-bundled GPG on Windows (MSYS2-based) does not automatically convert Windows-style paths in environment variables like GNUPGHOME. This caused GPG to fail with exit code 2 when verifying Microsoft JDK signatures on Windows, because the GNUPGHOME path (D:\a\_temp\...) was not recognized as a valid POSIX path. Fix: pass --homedir as an explicit command-line argument to both gpg --import and gpg --verify. MSYS2 does correctly convert Windows paths in command-line arguments, so this approach works reliably on Windows, Linux, and macOS. * fix: convert Windows paths to POSIX format for MSYS2 GPG on Windows The Git-bundled GPG on Windows (C:\Program Files\Git\usr\bin\gpg.exe) is an MSYS2-based binary that uses POSIX path conventions internally. When Windows-style paths with backslashes and drive letters (D:\a\_temp\...) are passed as arguments, GPG may fail to resolve them correctly, resulting in a fatal error (exit code 2). Fix: add a toGpgPath() helper that converts Windows paths to MSYS2 POSIX format (/d/a/_temp/...) before passing them to any gpg command. On Linux and macOS the helper is a no-op. Applied to all four paths used in verifyPackageSignature: - gpgHome (--homedir argument) - publicKeyFile (--import argument) - signaturePath (--verify signature argument) - archivePath (--verify data argument) * Fix gpg test formatting --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Bruno Borges <brborges@microsoft.com>
396 lines
12 KiB
TypeScript
396 lines
12 KiB
TypeScript
import {HttpClient} from '@actions/http-client';
|
|
import * as tc from '@actions/tool-cache';
|
|
import fs from 'fs';
|
|
import os from 'os';
|
|
import {
|
|
TemurinDistribution,
|
|
TemurinImplementation,
|
|
ADOPTIUM_PUBLIC_KEY
|
|
} from '../../src/distributions/temurin/installer';
|
|
import {JavaInstallerOptions} from '../../src/distributions/base-models';
|
|
import * as util from '../../src/util';
|
|
import * as gpg from '../../src/gpg';
|
|
|
|
import manifestData from '../data/temurin.json';
|
|
import * as core from '@actions/core';
|
|
|
|
describe('getAvailableVersions', () => {
|
|
let spyHttpClient: jest.SpyInstance;
|
|
let spyCoreError: jest.SpyInstance;
|
|
let spyCoreWarning: jest.SpyInstance;
|
|
|
|
beforeEach(() => {
|
|
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
|
spyHttpClient.mockReturnValue({
|
|
statusCode: 200,
|
|
headers: {},
|
|
result: []
|
|
});
|
|
// Mock core.error to suppress error logs
|
|
spyCoreError = jest.spyOn(core, 'error');
|
|
spyCoreError.mockImplementation(() => {});
|
|
spyCoreWarning = jest.spyOn(core, 'warning');
|
|
spyCoreWarning.mockImplementation(() => {});
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
jest.clearAllMocks();
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
it.each([
|
|
[
|
|
{
|
|
version: '16',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
TemurinImplementation.Hotspot,
|
|
'os=mac&architecture=x64&image_type=jdk&release_type=ga&jvm_impl=hotspot&page_size=20&page=0'
|
|
],
|
|
[
|
|
{
|
|
version: '16',
|
|
architecture: 'x86',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
TemurinImplementation.Hotspot,
|
|
'os=mac&architecture=x86&image_type=jdk&release_type=ga&jvm_impl=hotspot&page_size=20&page=0'
|
|
],
|
|
[
|
|
{
|
|
version: '16',
|
|
architecture: 'x64',
|
|
packageType: 'jre',
|
|
checkLatest: false
|
|
},
|
|
TemurinImplementation.Hotspot,
|
|
'os=mac&architecture=x64&image_type=jre&release_type=ga&jvm_impl=hotspot&page_size=20&page=0'
|
|
],
|
|
[
|
|
{
|
|
version: '16-ea',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
TemurinImplementation.Hotspot,
|
|
'os=mac&architecture=x64&image_type=jdk&release_type=ea&jvm_impl=hotspot&page_size=20&page=0'
|
|
]
|
|
])(
|
|
'build correct url for %s',
|
|
async (
|
|
installerOptions: JavaInstallerOptions,
|
|
impl: TemurinImplementation,
|
|
expectedParameters
|
|
) => {
|
|
const distribution = new TemurinDistribution(installerOptions, impl);
|
|
const baseUrl =
|
|
'https://api.adoptium.net/v3/assets/version/%5B1.0,100.0%5D';
|
|
const expectedUrl = `${baseUrl}?project=jdk&vendor=adoptium&heap_size=normal&sort_method=DEFAULT&sort_order=DESC&${expectedParameters}`;
|
|
distribution['getPlatformOption'] = () => 'mac';
|
|
|
|
await distribution['getAvailableVersions']();
|
|
|
|
expect(spyHttpClient.mock.calls).toHaveLength(1);
|
|
expect(spyHttpClient.mock.calls[0][0]).toBe(expectedUrl);
|
|
}
|
|
);
|
|
|
|
it('load available versions', async () => {
|
|
const nextPageUrl =
|
|
'https://api.adoptium.net/v3/assets/version/%5B1.0,100.0%5D?page=1&page_size=20';
|
|
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
|
spyHttpClient
|
|
.mockReturnValueOnce({
|
|
statusCode: 200,
|
|
headers: {link: `<${nextPageUrl}>; rel="next"`},
|
|
result: manifestData as any
|
|
})
|
|
.mockReturnValueOnce({
|
|
statusCode: 200,
|
|
headers: {},
|
|
result: manifestData as any
|
|
});
|
|
|
|
const distribution = new TemurinDistribution(
|
|
{
|
|
version: '8',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
TemurinImplementation.Hotspot
|
|
);
|
|
const availableVersions = await distribution['getAvailableVersions']();
|
|
expect(availableVersions).not.toBeNull();
|
|
expect(availableVersions.length).toBe(manifestData.length * 2);
|
|
expect(spyHttpClient).toHaveBeenNthCalledWith(2, nextPageUrl);
|
|
});
|
|
|
|
it('stops pagination after 1000 pages as a safeguard', async () => {
|
|
const nextPageUrl =
|
|
'https://api.adoptium.net/v3/assets/version/%5B1.0,100.0%5D?page=2&page_size=20';
|
|
spyHttpClient.mockReturnValue({
|
|
statusCode: 200,
|
|
headers: {link: `<${nextPageUrl}>; rel="next"`},
|
|
result: [{version_data: {semver: '17.0.1'}, binaries: []}] as any
|
|
});
|
|
|
|
const distribution = new TemurinDistribution(
|
|
{
|
|
version: '8',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
TemurinImplementation.Hotspot
|
|
);
|
|
|
|
await distribution['getAvailableVersions']();
|
|
|
|
expect(spyHttpClient).toHaveBeenCalledTimes(1000);
|
|
expect(spyCoreWarning).toHaveBeenCalledWith(
|
|
expect.stringContaining('Reached pagination safeguard limit (1000 pages)')
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
[TemurinImplementation.Hotspot, 'jdk', 'Java_Temurin-Hotspot_jdk'],
|
|
[TemurinImplementation.Hotspot, 'jre', 'Java_Temurin-Hotspot_jre']
|
|
])(
|
|
'find right toolchain folder',
|
|
(impl: TemurinImplementation, packageType: string, expected: string) => {
|
|
const distribution = new TemurinDistribution(
|
|
{
|
|
version: '8',
|
|
architecture: 'x64',
|
|
packageType: packageType,
|
|
checkLatest: false
|
|
},
|
|
impl
|
|
);
|
|
|
|
// @ts-ignore - because it is protected
|
|
expect(distribution.toolcacheFolderName).toBe(expected);
|
|
}
|
|
);
|
|
|
|
it.each([
|
|
['amd64', 'x64'],
|
|
['arm64', 'aarch64']
|
|
])(
|
|
'defaults to os.arch(): %s mapped to distro arch: %s',
|
|
async (osArch: string, distroArch: string) => {
|
|
jest
|
|
.spyOn(os, 'arch')
|
|
.mockReturnValue(osArch as ReturnType<typeof os.arch>);
|
|
|
|
const installerOptions: JavaInstallerOptions = {
|
|
version: '17',
|
|
architecture: '',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
};
|
|
|
|
const expectedParameters = `os=mac&architecture=${distroArch}&image_type=jdk&release_type=ga&jvm_impl=hotspot&page_size=20&page=0`;
|
|
|
|
const distribution = new TemurinDistribution(
|
|
installerOptions,
|
|
TemurinImplementation.Hotspot
|
|
);
|
|
const baseUrl =
|
|
'https://api.adoptium.net/v3/assets/version/%5B1.0,100.0%5D';
|
|
const expectedUrl = `${baseUrl}?project=jdk&vendor=adoptium&heap_size=normal&sort_method=DEFAULT&sort_order=DESC&${expectedParameters}`;
|
|
distribution['getPlatformOption'] = () => 'mac';
|
|
|
|
await distribution['getAvailableVersions']();
|
|
|
|
expect(spyHttpClient.mock.calls).toHaveLength(1);
|
|
expect(spyHttpClient.mock.calls[0][0]).toBe(expectedUrl);
|
|
}
|
|
);
|
|
});
|
|
|
|
describe('findPackageForDownload', () => {
|
|
it.each([
|
|
['8', '8.0.302+8'],
|
|
['16', '16.0.2+7'],
|
|
['16.0', '16.0.2+7'],
|
|
['16.0.2', '16.0.2+7'],
|
|
['8.x', '8.0.302+8'],
|
|
['x', '16.0.2+7']
|
|
])('version is resolved correctly %s -> %s', async (input, expected) => {
|
|
const distribution = new TemurinDistribution(
|
|
{
|
|
version: '8',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
TemurinImplementation.Hotspot
|
|
);
|
|
distribution['getAvailableVersions'] = async () => manifestData as any;
|
|
const resolvedVersion = await distribution['findPackageForDownload'](input);
|
|
expect(resolvedVersion.version).toBe(expected);
|
|
expect(resolvedVersion.signatureUrl).toBeDefined();
|
|
});
|
|
|
|
it('version is found but binaries list is empty', async () => {
|
|
const distribution = new TemurinDistribution(
|
|
{
|
|
version: '9.0.8',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
TemurinImplementation.Hotspot
|
|
);
|
|
distribution['getAvailableVersions'] = async () => manifestData as any;
|
|
await expect(
|
|
distribution['findPackageForDownload']('9.0.8')
|
|
).rejects.toThrow(/No matching version found for SemVer */);
|
|
});
|
|
|
|
it('version is not found', async () => {
|
|
const distribution = new TemurinDistribution(
|
|
{
|
|
version: '7.x',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
TemurinImplementation.Hotspot
|
|
);
|
|
distribution['getAvailableVersions'] = async () => manifestData as any;
|
|
await expect(distribution['findPackageForDownload']('7.x')).rejects.toThrow(
|
|
/No matching version found for SemVer */
|
|
);
|
|
});
|
|
|
|
it('version list is empty', async () => {
|
|
const distribution = new TemurinDistribution(
|
|
{
|
|
version: '8',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
TemurinImplementation.Hotspot
|
|
);
|
|
distribution['getAvailableVersions'] = async () => [];
|
|
await expect(distribution['findPackageForDownload']('8')).rejects.toThrow(
|
|
/No matching version found for SemVer */
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('downloadTool', () => {
|
|
let spyDownloadTool: jest.SpyInstance;
|
|
let spyVerifySignature: jest.SpyInstance;
|
|
let spyExtractJdkFile: jest.SpyInstance;
|
|
let spyCacheDir: jest.SpyInstance;
|
|
let spyReadDirSync: jest.SpyInstance;
|
|
let spyRenameWinArchive: jest.SpyInstance;
|
|
|
|
beforeEach(() => {
|
|
spyDownloadTool = jest.spyOn(tc, 'downloadTool');
|
|
spyDownloadTool.mockResolvedValue('/tmp/jdk.tar.gz');
|
|
spyVerifySignature = jest.spyOn(gpg, 'verifyPackageSignature');
|
|
spyVerifySignature.mockResolvedValue(undefined);
|
|
spyExtractJdkFile = jest.spyOn(util, 'extractJdkFile');
|
|
spyExtractJdkFile.mockResolvedValue('/tmp/extracted');
|
|
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
|
spyCacheDir.mockResolvedValue('/tmp/toolcache');
|
|
spyReadDirSync = jest.spyOn(fs, 'readdirSync');
|
|
spyReadDirSync.mockReturnValue(['jdk-17'] as any);
|
|
spyRenameWinArchive = jest.spyOn(util, 'renameWinArchive');
|
|
spyRenameWinArchive.mockReturnValue('/tmp/jdk.tar.gz.zip');
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
jest.clearAllMocks();
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
it('verifies signature when enabled', async () => {
|
|
const distribution = new TemurinDistribution(
|
|
{
|
|
version: '17',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false,
|
|
verifySignature: true
|
|
},
|
|
TemurinImplementation.Hotspot
|
|
);
|
|
|
|
await distribution['downloadTool']({
|
|
version: '17.0.14+7',
|
|
url: 'https://example.com/jdk.tar.gz',
|
|
signatureUrl: 'https://example.com/jdk.tar.gz.sig'
|
|
});
|
|
|
|
expect(spyVerifySignature).toHaveBeenCalledWith(
|
|
'/tmp/jdk.tar.gz',
|
|
'https://example.com/jdk.tar.gz.sig',
|
|
ADOPTIUM_PUBLIC_KEY
|
|
);
|
|
});
|
|
|
|
it('fails when signature is missing and verification is enabled', async () => {
|
|
const distribution = new TemurinDistribution(
|
|
{
|
|
version: '17',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false,
|
|
verifySignature: true
|
|
},
|
|
TemurinImplementation.Hotspot
|
|
);
|
|
|
|
await expect(
|
|
distribution['downloadTool']({
|
|
version: '17.0.14+7',
|
|
url: 'https://example.com/jdk.tar.gz'
|
|
})
|
|
).rejects.toThrow(
|
|
"Input 'verify-signature' is enabled, but no signature URL was found"
|
|
);
|
|
expect(spyVerifySignature).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('uses custom public key when verifySignaturePublicKey is provided', async () => {
|
|
const customKey =
|
|
'-----BEGIN PGP PUBLIC KEY BLOCK-----\ncustom\n-----END PGP PUBLIC KEY BLOCK-----';
|
|
const distribution = new TemurinDistribution(
|
|
{
|
|
version: '17',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false,
|
|
verifySignature: true,
|
|
verifySignaturePublicKey: customKey
|
|
},
|
|
TemurinImplementation.Hotspot
|
|
);
|
|
|
|
await distribution['downloadTool']({
|
|
version: '17.0.14+7',
|
|
url: 'https://example.com/jdk.tar.gz',
|
|
signatureUrl: 'https://example.com/jdk.tar.gz.sig'
|
|
});
|
|
|
|
expect(spyVerifySignature).toHaveBeenCalledWith(
|
|
'/tmp/jdk.tar.gz',
|
|
'https://example.com/jdk.tar.gz.sig',
|
|
customKey
|
|
);
|
|
});
|
|
});
|