committed by
Fabio Niephaus
parent
1a4a7ccb68
commit
2240cb3432
@@ -15,6 +15,9 @@ export const JDK_HOME_SUFFIX = IS_MACOS ? '/Contents/Home' : ''
|
||||
|
||||
export const MANDREL_NAMESPACE = 'mandrel-'
|
||||
|
||||
export const GDS_BASE = 'https://gds.oracle.com/api/20220101'
|
||||
export const GDS_GRAALVM_PRODUCT_ID = 'D53FAE8052773FFAE0530F15000AA6C6'
|
||||
|
||||
export type LatestReleaseResponse =
|
||||
otypes.Endpoints['GET /repos/{owner}/{repo}/releases/latest']['response']
|
||||
|
||||
|
||||
220
src/gds.ts
Normal file
220
src/gds.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
import * as c from './constants'
|
||||
import * as core from '@actions/core'
|
||||
import * as fs from 'fs'
|
||||
import * as httpClient from '@actions/http-client'
|
||||
import * as io from '@actions/io'
|
||||
import * as path from 'path'
|
||||
import * as stream from 'stream'
|
||||
import * as util from 'util'
|
||||
import {IHeaders} from '@actions/http-client/interfaces'
|
||||
import {IncomingHttpHeaders} from 'http'
|
||||
import {RetryHelper} from '@actions/tool-cache/lib/retry-helper'
|
||||
import {calculateSHA256} from './utils'
|
||||
import {ok} from 'assert'
|
||||
import uuidV4 from 'uuid/v4'
|
||||
|
||||
interface GDSArtifactsResponse {
|
||||
readonly items: GDSArtifact[]
|
||||
}
|
||||
|
||||
interface GDSArtifact {
|
||||
readonly id: string
|
||||
readonly checksum: string
|
||||
}
|
||||
|
||||
interface GDSErrorResponse {
|
||||
readonly code: string
|
||||
readonly message: string
|
||||
}
|
||||
|
||||
export async function downloadGraalVMEE(
|
||||
gdsToken: string,
|
||||
version: string,
|
||||
javaVersion: string
|
||||
): Promise<string> {
|
||||
const userAgent = `GraalVMGitHubAction/1.0.4 (arch:${c.GRAALVM_ARCH}; os:${c.GRAALVM_PLATFORM}; java:${javaVersion})`
|
||||
const baseArtifact = await fetchArtifact(
|
||||
userAgent,
|
||||
'isBase:True',
|
||||
version,
|
||||
javaVersion
|
||||
)
|
||||
return downloadArtifact(gdsToken, userAgent, baseArtifact)
|
||||
}
|
||||
|
||||
export async function fetchArtifact(
|
||||
userAgent: string,
|
||||
metadata: string,
|
||||
version: string,
|
||||
javaVersion: string
|
||||
): Promise<GDSArtifact> {
|
||||
const http = new httpClient.HttpClient(userAgent)
|
||||
|
||||
let filter
|
||||
if (version === c.VERSION_LATEST) {
|
||||
filter = `sortBy=timeCreated&limit=1` // latest and only one item
|
||||
} else {
|
||||
filter = `metadata=version:${version}`
|
||||
}
|
||||
|
||||
const response = await http.get(
|
||||
`${c.GDS_BASE}/artifacts?productId=${c.GDS_GRAALVM_PRODUCT_ID}&${filter}&metadata=java:jdk${javaVersion}&metadata=os:${c.GRAALVM_PLATFORM}&metadata=arch:${c.GRAALVM_ARCH}&metadata=${metadata}&status=PUBLISHED&responseFields=id&responseFields=checksum`,
|
||||
{accept: 'application/json'}
|
||||
)
|
||||
if (response.message.statusCode !== 200) {
|
||||
throw new Error(
|
||||
`Unable to find JDK${javaVersion}-based GraalVM EE ${version}`
|
||||
)
|
||||
}
|
||||
const artifactResponse = JSON.parse(
|
||||
await response.readBody()
|
||||
) as GDSArtifactsResponse
|
||||
if (artifactResponse.items.length !== 1) {
|
||||
throw new Error(`Found more than one GDS artifact`)
|
||||
}
|
||||
return artifactResponse.items[0]
|
||||
}
|
||||
|
||||
async function downloadArtifact(
|
||||
gdsToken: string,
|
||||
userAgent: string,
|
||||
artifact: GDSArtifact
|
||||
): Promise<string> {
|
||||
let downloadPath
|
||||
try {
|
||||
downloadPath = await downloadTool(
|
||||
`${c.GDS_BASE}/artifacts/${artifact.id}/content`,
|
||||
userAgent,
|
||||
{
|
||||
accept: 'application/x-yaml',
|
||||
'x-download-token': gdsToken
|
||||
}
|
||||
)
|
||||
} catch (err) {
|
||||
if (err instanceof HTTPError && err.httpStatusCode) {
|
||||
if (err.httpStatusCode === 401) {
|
||||
throw new Error(
|
||||
`The provided "gds-token" was rejected (reason: "${err.gdsError.message}", opc-request-id: ${err.headers['opc-request-id']})`
|
||||
)
|
||||
}
|
||||
}
|
||||
throw err
|
||||
}
|
||||
const sha256 = calculateSHA256(downloadPath)
|
||||
if (sha256.toLowerCase() !== artifact.checksum.toLowerCase()) {
|
||||
throw new Error(
|
||||
`Checksum does not match (expected: "${artifact.checksum}", got: "${sha256}")`
|
||||
)
|
||||
}
|
||||
return downloadPath
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified fork of tool-cache's downloadTool [1] with the ability to set a custom user agent.
|
||||
* [1] https://github.com/actions/toolkit/blob/2f164000dcd42fb08287824a3bc3030dbed33687/packages/tool-cache/src/tool-cache.ts
|
||||
*/
|
||||
|
||||
class HTTPError extends Error {
|
||||
constructor(
|
||||
readonly httpStatusCode: number | undefined,
|
||||
readonly gdsError: GDSErrorResponse,
|
||||
readonly headers: IncomingHttpHeaders
|
||||
) {
|
||||
super(`Unexpected HTTP response: ${httpStatusCode}`)
|
||||
Object.setPrototypeOf(this, new.target.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadTool(
|
||||
url: string,
|
||||
userAgent: string,
|
||||
headers?: IHeaders
|
||||
): Promise<string> {
|
||||
const dest = path.join(getTempDirectory(), uuidV4())
|
||||
await io.mkdirP(path.dirname(dest))
|
||||
core.debug(`Downloading ${url}`)
|
||||
core.debug(`Destination ${dest}`)
|
||||
|
||||
const maxAttempts = 3
|
||||
const minSeconds = 10
|
||||
const maxSeconds = 20
|
||||
const retryHelper = new RetryHelper(maxAttempts, minSeconds, maxSeconds)
|
||||
return await retryHelper.execute(
|
||||
async () => {
|
||||
return await downloadToolAttempt(url, userAgent, dest || '', headers)
|
||||
},
|
||||
(err: Error) => {
|
||||
if (err instanceof HTTPError && err.httpStatusCode) {
|
||||
// Don't retry anything less than 500, except 408 Request Timeout and 429 Too Many Requests
|
||||
if (
|
||||
err.httpStatusCode < 500 &&
|
||||
err.httpStatusCode !== 408 &&
|
||||
err.httpStatusCode !== 429
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise retry
|
||||
return true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async function downloadToolAttempt(
|
||||
url: string,
|
||||
userAgent: string,
|
||||
dest: string,
|
||||
headers?: IHeaders
|
||||
): Promise<string> {
|
||||
if (fs.existsSync(dest)) {
|
||||
throw new Error(`Destination file path ${dest} already exists`)
|
||||
}
|
||||
|
||||
// Get the response headers
|
||||
const http = new httpClient.HttpClient(userAgent, [], {
|
||||
allowRetries: false
|
||||
})
|
||||
|
||||
const response: httpClient.HttpClientResponse = await http.get(url, headers)
|
||||
if (response.message.statusCode !== 200) {
|
||||
const errorResponse = JSON.parse(
|
||||
await response.readBody()
|
||||
) as GDSErrorResponse
|
||||
const err = new HTTPError(
|
||||
response.message.statusCode,
|
||||
errorResponse,
|
||||
response.message.headers
|
||||
)
|
||||
core.debug(
|
||||
`Failed to download from "${url}". Code(${response.message.statusCode}) Message(${response.message.statusMessage})`
|
||||
)
|
||||
throw err
|
||||
}
|
||||
|
||||
// Download the response body
|
||||
const pipeline = util.promisify(stream.pipeline)
|
||||
let succeeded = false
|
||||
try {
|
||||
await pipeline(response.message, fs.createWriteStream(dest))
|
||||
core.debug('Download complete')
|
||||
succeeded = true
|
||||
return dest
|
||||
} finally {
|
||||
// Error, delete dest before retry
|
||||
if (!succeeded) {
|
||||
core.debug('Download failed')
|
||||
try {
|
||||
await io.rmRF(dest)
|
||||
} catch (err) {
|
||||
core.debug(`Failed to delete '${dest}'. ${err}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getTempDirectory(): string {
|
||||
const tempDirectory = process.env['RUNNER_TEMP'] || ''
|
||||
ok(tempDirectory, 'Expected RUNNER_TEMP to be defined')
|
||||
return tempDirectory
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
downloadExtractAndCacheJDK,
|
||||
getLatestRelease
|
||||
} from './utils'
|
||||
import {downloadGraalVMEE} from './gds'
|
||||
import {downloadTool} from '@actions/tool-cache'
|
||||
|
||||
const GRAALVM_CE_DL_BASE =
|
||||
'https://github.com/graalvm/graalvm-ce-builds/releases/download'
|
||||
@@ -11,7 +13,13 @@ const GRAALVM_REPO_DEV_BUILDS = 'graalvm-ce-dev-builds'
|
||||
const GRAALVM_REPO_RELEASES = 'graalvm-ce-builds'
|
||||
const GRAALVM_TAG_PREFIX = 'vm-'
|
||||
|
||||
export async function setUpGraalVMLatest(javaVersion: string): Promise<string> {
|
||||
export async function setUpGraalVMLatest(
|
||||
gdsToken: string,
|
||||
javaVersion: string
|
||||
): Promise<string> {
|
||||
if (gdsToken.length > 0) {
|
||||
return setUpGraalVMRelease(gdsToken, c.VERSION_LATEST, javaVersion)
|
||||
}
|
||||
const latestRelease = await getLatestRelease(GRAALVM_REPO_RELEASES)
|
||||
const tag_name = latestRelease.tag_name
|
||||
if (tag_name.startsWith(GRAALVM_TAG_PREFIX)) {
|
||||
@@ -19,16 +27,24 @@ export async function setUpGraalVMLatest(javaVersion: string): Promise<string> {
|
||||
GRAALVM_TAG_PREFIX.length,
|
||||
tag_name.length
|
||||
)
|
||||
return setUpGraalVMRelease(latestVersion, javaVersion)
|
||||
return setUpGraalVMRelease(gdsToken, latestVersion, javaVersion)
|
||||
}
|
||||
throw new Error(`Could not find latest GraalVM release: ${tag_name}`)
|
||||
}
|
||||
|
||||
export async function setUpGraalVMDevBuild(
|
||||
gdsToken: string,
|
||||
javaVersion: string
|
||||
): Promise<string> {
|
||||
if (gdsToken.length > 0) {
|
||||
throw new Error('Downloading GraalVM EE dev builds is not supported')
|
||||
}
|
||||
const latestDevBuild = await getLatestRelease(GRAALVM_REPO_DEV_BUILDS)
|
||||
const graalVMIdentifier = determineGraalVMIdentifier('dev', javaVersion)
|
||||
const graalVMIdentifier = determineGraalVMIdentifier(
|
||||
false,
|
||||
'dev',
|
||||
javaVersion
|
||||
)
|
||||
const expectedFileName = `${graalVMIdentifier}${c.GRAALVM_FILE_EXTENSION}`
|
||||
for (const asset of latestDevBuild.assets) {
|
||||
if (asset.name === expectedFileName) {
|
||||
@@ -39,22 +55,39 @@ export async function setUpGraalVMDevBuild(
|
||||
}
|
||||
|
||||
export async function setUpGraalVMRelease(
|
||||
gdsToken: string,
|
||||
version: string,
|
||||
javaVersion: string
|
||||
): Promise<string> {
|
||||
const graalVMIdentifier = determineGraalVMIdentifier(version, javaVersion)
|
||||
const downloadUrl = `${GRAALVM_CE_DL_BASE}/${GRAALVM_TAG_PREFIX}${version}/${graalVMIdentifier}${c.GRAALVM_FILE_EXTENSION}`
|
||||
const toolName = determineToolName(javaVersion)
|
||||
return downloadExtractAndCacheJDK(downloadUrl, toolName, version)
|
||||
const isEE = gdsToken.length > 0
|
||||
const graalVMIdentifier = determineGraalVMIdentifier(
|
||||
isEE,
|
||||
version,
|
||||
javaVersion
|
||||
)
|
||||
const toolName = determineToolName(isEE, javaVersion)
|
||||
let downloader: () => Promise<string>
|
||||
if (isEE) {
|
||||
downloader = async () => downloadGraalVMEE(gdsToken, version, javaVersion)
|
||||
} else {
|
||||
const downloadUrl = `${GRAALVM_CE_DL_BASE}/${GRAALVM_TAG_PREFIX}${version}/${graalVMIdentifier}${c.GRAALVM_FILE_EXTENSION}`
|
||||
downloader = async () => downloadTool(downloadUrl)
|
||||
}
|
||||
return downloadExtractAndCacheJDK(downloader, toolName, version)
|
||||
}
|
||||
|
||||
function determineGraalVMIdentifier(
|
||||
isEE: boolean,
|
||||
version: string,
|
||||
javaVersion: string
|
||||
): string {
|
||||
return `graalvm-ce-java${javaVersion}-${c.GRAALVM_PLATFORM}-${c.GRAALVM_ARCH}-${version}`
|
||||
return `graalvm-${isEE ? 'ee' : 'ce'}-java${javaVersion}-${
|
||||
c.GRAALVM_PLATFORM
|
||||
}-${c.GRAALVM_ARCH}-${version}`
|
||||
}
|
||||
|
||||
function determineToolName(javaVersion: string): string {
|
||||
return `graalvm-ce-java${javaVersion}-${c.GRAALVM_PLATFORM}`
|
||||
function determineToolName(isEE: boolean, javaVersion: string): string {
|
||||
return `graalvm-${isEE ? 'ee' : 'ce'}-java${javaVersion}-${
|
||||
c.GRAALVM_PLATFORM
|
||||
}`
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import {GRAALVM_PLATFORM} from './constants'
|
||||
import {exec} from '@actions/exec'
|
||||
import {join} from 'path'
|
||||
|
||||
const BASE_FLAGS = ['--non-interactive', 'install', '--no-progress']
|
||||
const COMPONENT_TO_POST_INSTALL_HOOK = new Map<string, Map<string, string>>([
|
||||
[
|
||||
'linux',
|
||||
@@ -21,10 +22,16 @@ const COMPONENT_TO_POST_INSTALL_HOOK = new Map<string, Map<string, string>>([
|
||||
])
|
||||
|
||||
export async function setUpGUComponents(
|
||||
gdsToken: string,
|
||||
graalVMHome: string,
|
||||
components: string[]
|
||||
): Promise<void> {
|
||||
await exec('gu', ['install', '--no-progress'].concat(components))
|
||||
const optionalFlags = []
|
||||
if (gdsToken.length > 0) {
|
||||
optionalFlags.push('--token', gdsToken)
|
||||
}
|
||||
|
||||
await exec('gu', BASE_FLAGS.concat(optionalFlags, components))
|
||||
|
||||
const platformHooks = COMPONENT_TO_POST_INSTALL_HOOK.get(GRAALVM_PLATFORM)
|
||||
if (platformHooks) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {setUpWindowsEnvironment} from './msvc'
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
const graalvmVersion = core.getInput('version', {required: true})
|
||||
const gdsToken = core.getInput('gds-token')
|
||||
const javaVersion = core.getInput('java-version', {required: true})
|
||||
const componentsString: string = core.getInput('components')
|
||||
const components: string[] =
|
||||
@@ -30,16 +31,17 @@ async function run(): Promise<void> {
|
||||
let graalVMHome
|
||||
switch (graalvmVersion) {
|
||||
case c.VERSION_LATEST:
|
||||
graalVMHome = await graalvm.setUpGraalVMLatest(javaVersion)
|
||||
graalVMHome = await graalvm.setUpGraalVMLatest(gdsToken, javaVersion)
|
||||
break
|
||||
case c.VERSION_DEV:
|
||||
graalVMHome = await graalvm.setUpGraalVMDevBuild(javaVersion)
|
||||
graalVMHome = await graalvm.setUpGraalVMDevBuild(gdsToken, javaVersion)
|
||||
break
|
||||
default:
|
||||
if (graalvmVersion.startsWith(c.MANDREL_NAMESPACE)) {
|
||||
graalVMHome = await setUpMandrel(graalvmVersion, javaVersion)
|
||||
} else {
|
||||
graalVMHome = await graalvm.setUpGraalVMRelease(
|
||||
gdsToken,
|
||||
graalvmVersion,
|
||||
javaVersion
|
||||
)
|
||||
@@ -62,7 +64,7 @@ async function run(): Promise<void> {
|
||||
`Mandrel does not support GraalVM components: ${componentsString}`
|
||||
)
|
||||
} else {
|
||||
await setUpGUComponents(graalVMHome, components)
|
||||
await setUpGUComponents(gdsToken, graalVMHome, components)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as c from './constants'
|
||||
import {downloadExtractAndCacheJDK, getLatestRelease} from './utils'
|
||||
import {downloadTool} from '@actions/tool-cache'
|
||||
|
||||
const MANDREL_REPO = 'mandrel'
|
||||
const MANDREL_TAG_PREFIX = c.MANDREL_NAMESPACE
|
||||
@@ -47,7 +48,11 @@ async function setUpMandrelRelease(
|
||||
const identifier = determineMandrelIdentifier(version, javaVersion)
|
||||
const downloadUrl = `${MANDREL_DL_BASE}/${MANDREL_TAG_PREFIX}${version}/${identifier}${c.GRAALVM_FILE_EXTENSION}`
|
||||
const toolName = determineToolName(javaVersion)
|
||||
return downloadExtractAndCacheJDK(downloadUrl, toolName, version)
|
||||
return downloadExtractAndCacheJDK(
|
||||
async () => downloadTool(downloadUrl),
|
||||
toolName,
|
||||
version
|
||||
)
|
||||
}
|
||||
|
||||
function determineMandrelIdentifier(
|
||||
|
||||
28
src/utils.ts
28
src/utils.ts
@@ -2,9 +2,10 @@ import * as c from './constants'
|
||||
import * as core from '@actions/core'
|
||||
import * as httpClient from '@actions/http-client'
|
||||
import * as tc from '@actions/tool-cache'
|
||||
import {readFileSync, readdirSync} from 'fs'
|
||||
import {Octokit} from '@octokit/core'
|
||||
import {createHash} from 'crypto'
|
||||
import {join} from 'path'
|
||||
import {readdirSync} from 'fs'
|
||||
|
||||
// Set up Octokit in the same way as @actions/github (see https://git.io/Jy9YP)
|
||||
const baseUrl = process.env['GITHUB_API_URL'] || 'https://api.github.com'
|
||||
@@ -32,11 +33,13 @@ export async function getLatestRelease(
|
||||
export async function downloadAndExtractJDK(
|
||||
downloadUrl: string
|
||||
): Promise<string> {
|
||||
return findJavaHomeInSubfolder(await downloadAndExtract(downloadUrl))
|
||||
return findJavaHomeInSubfolder(
|
||||
await extract(await tc.downloadTool(downloadUrl))
|
||||
)
|
||||
}
|
||||
|
||||
export async function downloadExtractAndCacheJDK(
|
||||
downloadUrl: string,
|
||||
downloader: () => Promise<string>,
|
||||
toolName: string,
|
||||
version: string
|
||||
): Promise<string> {
|
||||
@@ -45,21 +48,28 @@ export async function downloadExtractAndCacheJDK(
|
||||
if (toolPath) {
|
||||
core.info(`Found ${toolName} ${version} in tool-cache @ ${toolPath}`)
|
||||
} else {
|
||||
const extractDir = await downloadAndExtract(downloadUrl)
|
||||
const extractDir = await extract(await downloader())
|
||||
core.info(`Adding ${toolName} ${version} to tool-cache ...`)
|
||||
toolPath = await tc.cacheDir(extractDir, toolName, semVersion)
|
||||
}
|
||||
return findJavaHomeInSubfolder(toolPath)
|
||||
}
|
||||
|
||||
async function downloadAndExtract(downloadUrl: string): Promise<string> {
|
||||
const downloadPath = await tc.downloadTool(downloadUrl)
|
||||
if (downloadUrl.endsWith('.tar.gz')) {
|
||||
export function calculateSHA256(filePath: string): string {
|
||||
const hashSum = createHash('sha256')
|
||||
hashSum.update(readFileSync(filePath))
|
||||
return hashSum.digest('hex')
|
||||
}
|
||||
|
||||
async function extract(downloadPath: string): Promise<string> {
|
||||
if (c.GRAALVM_FILE_EXTENSION === '.tar.gz') {
|
||||
return await tc.extractTar(downloadPath)
|
||||
} else if (downloadUrl.endsWith('.zip')) {
|
||||
} else if (c.GRAALVM_FILE_EXTENSION === '.zip') {
|
||||
return await tc.extractZip(downloadPath)
|
||||
} else {
|
||||
throw new Error(`Unexpected filetype downloaded: ${downloadUrl}`)
|
||||
throw new Error(
|
||||
`Unexpected filetype downloaded: ${c.GRAALVM_FILE_EXTENSION}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user