import {JavaBase} from '../base-installer'; import { JavaDownloadRelease, JavaInstallerOptions, JavaInstallerResults } from '../base-models'; import { extractJdkFile, getDownloadArchiveExtension, getGitHubHttpHeaders, isVersionSatisfies, renameWinArchive } from '../../util'; import * as gpg from '../../gpg'; import {MICROSOFT_PUBLIC_KEY} from './microsoft-key'; import * as core from '@actions/core'; import * as tc from '@actions/tool-cache'; import fs from 'fs'; import path from 'path'; import semver from 'semver'; import {TypedResponse} from '@actions/http-client/lib/interfaces'; export {MICROSOFT_PUBLIC_KEY} from './microsoft-key'; export class MicrosoftDistributions extends JavaBase { constructor(installerOptions: JavaInstallerOptions) { super('Microsoft', installerOptions); } protected async downloadTool( javaRelease: JavaDownloadRelease ): Promise { core.info( `Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...` ); let javaArchivePath = await tc.downloadTool(javaRelease.url); if (this.verifySignature) { if (!javaRelease.signatureUrl) { throw new Error( `Input 'verify-signature' is enabled, but no signature URL was found for Microsoft Build of OpenJDK version ${javaRelease.version}.` ); } core.info(`Verifying Java package signature...`); try { await gpg.verifyPackageSignature( javaArchivePath, javaRelease.signatureUrl, this.verifySignaturePublicKey ?? MICROSOFT_PUBLIC_KEY ); } catch (error) { throw new Error( `Failed to verify signature for Microsoft Build of OpenJDK version ${javaRelease.version}. Signature URL: ${javaRelease.signatureUrl}. Error: ${(error as Error).message}` ); } } core.info(`Extracting Java archive...`); const extension = getDownloadArchiveExtension(); if (process.platform === 'win32') { javaArchivePath = renameWinArchive(javaArchivePath); } const extractedJavaPath = await extractJdkFile(javaArchivePath, extension); const archiveName = fs.readdirSync(extractedJavaPath)[0]; const archivePath = path.join(extractedJavaPath, archiveName); const javaPath = await tc.cacheDir( archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture ); return {version: javaRelease.version, path: javaPath}; } protected async findPackageForDownload( range: string ): Promise { const arch = this.distributionArchitecture(); if (arch !== 'x64' && arch !== 'aarch64') { throw new Error(`Unsupported architecture: ${this.architecture}`); } if (!this.stable) { throw new Error('Early access versions are not supported'); } if (this.packageType !== 'jdk') { throw new Error( 'Microsoft Build of OpenJDK provides only the `jdk` package type' ); } const manifest = await this.getAvailableVersions(); if (!manifest) { throw new Error('Could not load manifest for Microsoft Build of OpenJDK'); } let foundRelease: tc.IToolRelease | undefined; if (this.isRunningOnAlpine() && this.isAlpineSupportedVersion(range)) { foundRelease = this.findFromManifestForPlatform( range, manifest, arch, 'alpine' ); if (foundRelease) { core.debug(`Resolved Alpine package for Microsoft OpenJDK ${range}`); } } else if (this.isRunningOnAlpine()) { core.debug( `No Alpine package found for Microsoft OpenJDK ${range}, defaulting to standard linux package` ); } // Runs if user is not running on alpine, or if no Alpine package was found if (!foundRelease) { foundRelease = await tc.findFromManifest(range, true, manifest, arch); } // Runs if failed to find any matching package if (!foundRelease) { const availableVersionStrings = manifest.map(item => item.version); throw this.createVersionNotFoundError(range, availableVersionStrings); } const file = foundRelease.files[0] as { download_url: string; signature_url?: string; }; const signatureUrl = file.signature_url ?? `${file.download_url}.sig`; return { url: file.download_url, signatureUrl, version: foundRelease.version }; } private findFromManifestForPlatform( range: string, manifest: tc.IToolRelease[], arch: string, platform: string ): tc.IToolRelease | undefined { const matched = manifest .filter(release => release.stable) .filter(release => isVersionSatisfies(range, release.version)) .map(release => { const matchedFile = release.files.find( (file: tc.IToolReleaseFile) => file.arch === arch && file.platform === platform ); if (!matchedFile) { return null; } return { ...release, files: [matchedFile] }; }) .filter((release): release is tc.IToolRelease => release !== null) .sort((a, b) => -semver.compareBuild(a.version, b.version)); return matched[0]; } private isAlpineSupportedVersion(range: string): boolean { const minVersion = semver.minVersion(range); return !!minVersion && (minVersion.major === 11 || minVersion.major === 17); } private isRunningOnAlpine(): boolean { return process.platform === 'linux' && fs.existsSync('/etc/alpine-release'); } protected supportsSignatureVerification(): boolean { return true; } private async getAvailableVersions(): Promise { const owner = 'microsoft'; const repository = 'openjdk-adoptium-marketplace-data'; const branch = 'main'; const filePath = 'general_info/microsoft-openjdk-versions.json'; let releases: tc.IToolRelease[] | null = null; const fileUrl = `https://api.github.com/repos/${owner}/${repository}/contents/${filePath}?ref=${branch}`; const headers = getGitHubHttpHeaders(); let response: TypedResponse | null = null; if (core.isDebug()) { console.time('Retrieving available versions for Microsoft took'); // eslint-disable-line no-console } try { response = await this.http.getJson(fileUrl, headers); if (!response.result) { return null; } } catch (err) { core.debug( `Http request for microsoft-openjdk-versions.json failed with status code: ${response?.statusCode}. Error: ${err}` ); return null; } if (response.result) { releases = response.result; } if (core.isDebug() && releases) { core.startGroup('Print information about available versions'); console.timeEnd('Retrieving available versions for Microsoft took'); // eslint-disable-line no-console core.debug(`Available versions: [${releases.length}]`); core.debug(releases.map(item => item.version).join(', ')); core.endGroup(); } return releases; } }