From b3ee3b4fddca5612658362a0acc17d3d366f665b Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Tue, 4 Jun 2024 08:59:53 +0200 Subject: [PATCH 1/5] feat(cli): convert java installer --- package.json | 3 +- pnpm-lock.yaml | 8 + src/cli/install-tool/index.ts | 8 + .../install-tool/install-tool-base.service.ts | 3 + src/cli/install-tool/install-tool.service.ts | 5 +- src/cli/prepare-tool/index.ts | 8 + .../prepare-tool/prepare-tool-base.service.ts | 4 + src/cli/prepare-tool/prepare-tool.service.ts | 4 +- src/cli/services/path.service.spec.ts | 6 + src/cli/services/path.service.ts | 4 + src/cli/tools/java/index.ts | 182 ++++++++++++++++++ src/cli/tools/java/schema.ts | 12 ++ .../local/containerbase/tools/v2/java-jdk.sh | 22 --- .../local/containerbase/tools/v2/java-jre.sh | 22 --- src/usr/local/containerbase/tools/v2/java.sh | 23 --- src/usr/local/containerbase/utils/java.sh | 112 ----------- test/bash/java.bats | 35 ---- 17 files changed, 240 insertions(+), 221 deletions(-) create mode 100644 src/cli/tools/java/index.ts create mode 100644 src/cli/tools/java/schema.ts delete mode 100644 src/usr/local/containerbase/tools/v2/java-jdk.sh delete mode 100644 src/usr/local/containerbase/tools/v2/java-jre.sh delete mode 100644 src/usr/local/containerbase/tools/v2/java.sh diff --git a/package.json b/package.json index 79d298b2e..dec800106 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,8 @@ "semver": "7.6.2", "simple-git": "3.24.0", "tar": "7.1.0", - "typanion": "3.14.0" + "typanion": "3.14.0", + "zod": "3.23.8" }, "devDependencies": { "@semantic-release/exec": "6.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a28b144da..148666663 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -76,6 +76,9 @@ importers: typanion: specifier: 3.14.0 version: 3.14.0 + zod: + specifier: 3.23.8 + version: 3.23.8 devDependencies: '@semantic-release/exec': specifier: 6.0.3 @@ -3577,6 +3580,9 @@ packages: resolution: {integrity: sha512-Ct97huExsu7cWeEjmrXlofevF8CvzUglJ4iGUet5B8xn1oumtAZBpHU4GzYuoE6PVqcZ5hghtBrSlhwHuR1Jmw==} engines: {node: '>=18'} + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + snapshots: '@actions/core@1.10.1': @@ -7122,3 +7128,5 @@ snapshots: yocto-queue@1.0.0: {} yoctocolors@2.0.2: {} + + zod@3.23.8: {} diff --git a/src/cli/install-tool/index.ts b/src/cli/install-tool/index.ts index 7db1d6162..156ac3dda 100644 --- a/src/cli/install-tool/index.ts +++ b/src/cli/install-tool/index.ts @@ -8,6 +8,11 @@ import { InstallDotnetService } from '../tools/dotnet'; import { InstallFlutterService } from '../tools/flutter'; import { InstallFluxService } from '../tools/flux'; import { InstallGleamService } from '../tools/gleam'; +import { + InstallJavaJdkService, + InstallJavaJreService, + InstallJavaService, +} from '../tools/java'; import { InstallMavenService } from '../tools/java/maven'; import { InstallNodeService } from '../tools/node'; import { @@ -49,6 +54,9 @@ function prepareInstallContainer(): Container { container.bind(INSTALL_TOOL_TOKEN).to(InstallDotnetService); container.bind(INSTALL_TOOL_TOKEN).to(InstallFlutterService); container.bind(INSTALL_TOOL_TOKEN).to(InstallFluxService); + container.bind(INSTALL_TOOL_TOKEN).to(InstallJavaService); + container.bind(INSTALL_TOOL_TOKEN).to(InstallJavaJreService); + container.bind(INSTALL_TOOL_TOKEN).to(InstallJavaJdkService); container.bind(INSTALL_TOOL_TOKEN).to(InstallMavenService); container.bind(INSTALL_TOOL_TOKEN).to(InstallNodeService); container.bind(INSTALL_TOOL_TOKEN).to(InstallRenovateService); diff --git a/src/cli/install-tool/install-tool-base.service.ts b/src/cli/install-tool/install-tool-base.service.ts index 4492e5532..70db147d0 100644 --- a/src/cli/install-tool/install-tool-base.service.ts +++ b/src/cli/install-tool/install-tool-base.service.ts @@ -28,6 +28,9 @@ export abstract class InstallToolBaseService { async isInstalled(version: string): Promise { return !!(await this.pathSvc.findVersionedToolPath(this.name, version)); } + async isPrepared(): Promise { + return null !== (await this.pathSvc.findToolPath(this.name)); + } abstract link(version: string): Promise; diff --git a/src/cli/install-tool/install-tool.service.ts b/src/cli/install-tool/install-tool.service.ts index e9a4b3691..8bcf648b9 100644 --- a/src/cli/install-tool/install-tool.service.ts +++ b/src/cli/install-tool/install-tool.service.ts @@ -44,10 +44,7 @@ export class InstallToolService { return; } - if ( - toolSvc.needsPrepare() && - !(await this.pathSvc.findToolPath(tool)) - ) { + if (toolSvc.needsPrepare() && !(await toolSvc.isPrepared())) { logger.debug({ tool }, 'tool not prepared'); const res = await prepareTools([tool], dryRun); if (res) { diff --git a/src/cli/prepare-tool/index.ts b/src/cli/prepare-tool/index.ts index 369f9cbc5..373cc25e3 100644 --- a/src/cli/prepare-tool/index.ts +++ b/src/cli/prepare-tool/index.ts @@ -4,6 +4,11 @@ import { PrepareDartService } from '../tools/dart'; import { PrepareDockerService } from '../tools/docker'; import { PrepareDotnetService } from '../tools/dotnet'; import { PrepareFlutterService } from '../tools/flutter'; +import { + PrepareJavaJdkService, + PrepareJavaJreService, + PrepareJavaService, +} from '../tools/java'; import { logger } from '../utils'; import { PrepareLegacyToolsService } from './prepare-legacy-tools.service'; import { PREPARE_TOOL_TOKEN, PrepareToolService } from './prepare-tool.service'; @@ -22,6 +27,9 @@ function prepareContainer(): Container { container.bind(PREPARE_TOOL_TOKEN).to(PrepareDotnetService); container.bind(PREPARE_TOOL_TOKEN).to(PrepareDockerService); container.bind(PREPARE_TOOL_TOKEN).to(PrepareFlutterService); + container.bind(PREPARE_TOOL_TOKEN).to(PrepareJavaService); + container.bind(PREPARE_TOOL_TOKEN).to(PrepareJavaJreService); + container.bind(PREPARE_TOOL_TOKEN).to(PrepareJavaJdkService); logger.trace('preparing container done'); return container; diff --git a/src/cli/prepare-tool/prepare-tool-base.service.ts b/src/cli/prepare-tool/prepare-tool-base.service.ts index 399bcf25e..edf713bb4 100644 --- a/src/cli/prepare-tool/prepare-tool-base.service.ts +++ b/src/cli/prepare-tool/prepare-tool-base.service.ts @@ -12,6 +12,10 @@ export abstract class PrepareToolBaseService { abstract execute(): Promise | void; + async isPrepared(): Promise { + return null !== (await this.pathSvc.findToolPath(this.name)); + } + toString(): string { return this.name; } diff --git a/src/cli/prepare-tool/prepare-tool.service.ts b/src/cli/prepare-tool/prepare-tool.service.ts index 4973e578d..c13a0bb69 100644 --- a/src/cli/prepare-tool/prepare-tool.service.ts +++ b/src/cli/prepare-tool/prepare-tool.service.ts @@ -39,7 +39,7 @@ export class PrepareToolService { logger.info({ tool }, 'tool ignored'); continue; } - if (await this.pathSvc.findToolPath(tool.name)) { + if (await tool.isPrepared()) { logger.debug({ tool: tool.name }, 'tool already prepared'); continue; } @@ -60,7 +60,7 @@ export class PrepareToolService { } const toolSvc = this.toolSvcs.find((t) => t.name === tool); if (toolSvc) { - if (await this.pathSvc.findToolPath(tool)) { + if (await toolSvc.isPrepared()) { logger.debug({ tool }, 'tool already prepared'); continue; } diff --git a/src/cli/services/path.service.spec.ts b/src/cli/services/path.service.spec.ts index d923a7857..bfa0d9d70 100644 --- a/src/cli/services/path.service.spec.ts +++ b/src/cli/services/path.service.spec.ts @@ -43,6 +43,12 @@ describe('path.service', () => { ); }); + test('sslPath', () => { + expect(child.get(PathService).sslPath).toBe( + rootPath('opt/containerbase/ssl'), + ); + }); + test('versionPath', () => { expect(child.get(PathService).versionPath).toBe( rootPath('opt/containerbase/versions'), diff --git a/src/cli/services/path.service.ts b/src/cli/services/path.service.ts index a31a62286..102e1a43e 100644 --- a/src/cli/services/path.service.ts +++ b/src/cli/services/path.service.ts @@ -44,6 +44,10 @@ export class PathService { return join(this.installDir, 'cache'); } + get sslPath(): string { + return join(this.installDir, 'ssl'); + } + constructor(@inject(EnvService) private envSvc: EnvService) {} async createToolPath(tool: string): Promise { diff --git a/src/cli/tools/java/index.ts b/src/cli/tools/java/index.ts new file mode 100644 index 000000000..654265ec4 --- /dev/null +++ b/src/cli/tools/java/index.ts @@ -0,0 +1,182 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { execa } from 'execa'; +import { inject, injectable } from 'inversify'; +import semver from 'semver'; +import { InstallToolBaseService } from '../../install-tool/install-tool-base.service'; +import { PrepareToolBaseService } from '../../prepare-tool/prepare-tool-base.service'; +import { + CompressionService, + EnvService, + HttpService, + PathService, +} from '../../services'; +import { logger } from '../../utils'; +import type { JavaReleases } from './schema'; + +const baseUrl = + 'https://github.com/joschi/java-metadata/raw/main/docs/metadata/ga/linux'; +const fileUrl = 'hotspot/temurin.json'; + +@injectable() +export class PrepareJavaService extends PrepareToolBaseService { + readonly name: string = 'java'; + + constructor( + @inject(PathService) pathSvc: PathService, + @inject(EnvService) envSvc: EnvService, + @inject(HttpService) private readonly httpSvc: HttpService, + @inject(CompressionService) + private readonly compressionSvc: CompressionService, + ) { + super(pathSvc, envSvc); + } + + override async execute(): Promise { + const ssl = this.pathSvc.sslPath; + + if (await this.isPrepared()) { + // cert store already there + return; + } + + const url = `${baseUrl}/${this.envSvc.arch === 'amd64' ? 'x86_64' : 'aarch64'}/jre/${fileUrl}`; + + const releases = await this.httpSvc.getJson(url); + + // sadly no lts info in metadata, so using latest version + // also ignore alpine builds + const latest = releases + .filter((c) => !c.filename.includes('_alpine-linux_')) + .reduce((prev, curr) => + prev && semver.compare(prev.version, curr.version) > 0 ? prev : curr, + ); + + logger.debug(`downloading cacerts from ${latest.version}`); + + const jre = await this.httpSvc.download({ + url: latest.url, + checksumType: 'sha512', + expectedChecksum: latest.sha512, + fileName: latest.filename, + }); + + const tmp = path.join(this.pathSvc.tmpDir, 'java'); + + await fs.mkdir(tmp, { recursive: true }); + + await this.compressionSvc.extract({ file: jre, cwd: tmp, strip: 1 }); + + await fs.cp( + path.join(tmp, 'lib/security/cacerts'), + path.join(ssl, 'cacerts'), + ); + + // cleanup will be done by caller + } + + override async isPrepared(): Promise { + return await this.pathSvc.fileExists( + path.join(this.pathSvc.sslPath, 'cacerts'), + ); + } +} + +@injectable() +export class PrepareJavaJdkService extends PrepareJavaService { + override readonly name = 'java-jdk'; +} + +@injectable() +export class PrepareJavaJreService extends PrepareJavaService { + override readonly name = 'java-jre'; +} + +@injectable() +export class InstallJavaService extends InstallToolBaseService { + override name: string = 'java'; + + constructor( + @inject(EnvService) envSvc: EnvService, + @inject(PathService) pathSvc: PathService, + @inject(HttpService) private http: HttpService, + @inject(CompressionService) private compress: CompressionService, + ) { + super(pathSvc, envSvc); + } + + override async install(version: string): Promise { + const arch = this.envSvc.arch === 'amd64' ? 'x86_64' : 'aarch64'; + const type = this.name === 'java-jre' ? 'jre' : 'jdk'; + const url = `${baseUrl}/${arch}/${type}/${fileUrl}`; + + const releases = await this.http.getJson(url); + + // Ignore alpine builds + const release = releases + .filter((c) => !c.filename.includes('_alpine-linux_')) + .find((r) => r.version === version); + + if (!release) { + throw new Error(`Version ${version} not found`); + } + + const file = await this.http.download({ + url: release.url, + checksumType: 'sha512', + expectedChecksum: release.sha512, + fileName: release.filename, + }); + + // TODO: create recursive + if (!(await this.pathSvc.findToolPath(this.name))) { + await this.pathSvc.createToolPath(this.name); + } + + const cwd = await this.pathSvc.createVersionedToolPath(this.name, version); + await this.compress.extract({ + file, + cwd, + strip: 1, + }); + + const v = semver.parse(version); + // we've a different cacerts location in java 8 jdk + const cacerts = + v?.major === 8 && this.name !== 'java-jre' + ? path.join(cwd, 'jre/lib/security/cacerts') + : path.join(cwd, 'lib/security/cacerts'); + await fs.rm(cacerts); + await fs.symlink(path.join(this.pathSvc.sslPath, 'cacerts'), cacerts); + } + + override async isPrepared(): Promise { + return await this.pathSvc.fileExists( + path.join(this.pathSvc.sslPath, 'cacerts'), + ); + } + + override async link(version: string): Promise { + const src = path.join( + this.pathSvc.versionedToolPath(this.name, version), + 'bin', + ); + await this.shellwrapper({ srcDir: src, name: 'java' }); + } + + override async test(_version: string): Promise { + await execa('java', ['-version'], { + stdio: ['inherit', 'inherit', 1], + }); + } +} + +@injectable() +export class InstallJavaJreService extends InstallJavaService { + override readonly name = 'java-jre'; +} + +@injectable() +export class InstallJavaJdkService extends InstallJavaService { + override readonly name = 'java-jdk'; +} diff --git a/src/cli/tools/java/schema.ts b/src/cli/tools/java/schema.ts new file mode 100644 index 000000000..eb94baae6 --- /dev/null +++ b/src/cli/tools/java/schema.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; + +const JavaRelease = z.object({ + filename: z.string(), + sha512: z.string(), + url: z.string(), + version: z.string(), +}); +export type JavaRelease = z.infer; + +export const JavaReleases = z.array(JavaRelease); +export type JavaReleases = z.infer; diff --git a/src/usr/local/containerbase/tools/v2/java-jdk.sh b/src/usr/local/containerbase/tools/v2/java-jdk.sh deleted file mode 100644 index e6d921899..000000000 --- a/src/usr/local/containerbase/tools/v2/java-jdk.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -function prepare_tool () { - # shellcheck source=/dev/null - . "$(get_containerbase_path)/utils/java.sh" - - prepare_java -} - -function install_tool () { - # shellcheck source=/dev/null - . "$(get_containerbase_path)/utils/java.sh" - - install_java jdk -} - -function link_tool () { - # shellcheck source=/dev/null - . "$(get_containerbase_path)/utils/java.sh" - - link_java -} diff --git a/src/usr/local/containerbase/tools/v2/java-jre.sh b/src/usr/local/containerbase/tools/v2/java-jre.sh deleted file mode 100644 index f2be62cf3..000000000 --- a/src/usr/local/containerbase/tools/v2/java-jre.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -function prepare_tool () { - # shellcheck source=/dev/null - . "$(get_containerbase_path)/utils/java.sh" - - prepare_java -} - -function install_tool () { - # shellcheck source=/dev/null - . "$(get_containerbase_path)/utils/java.sh" - - install_java jre -} - -function link_tool () { - # shellcheck source=/dev/null - . "$(get_containerbase_path)/utils/java.sh" - - link_java -} diff --git a/src/usr/local/containerbase/tools/v2/java.sh b/src/usr/local/containerbase/tools/v2/java.sh deleted file mode 100644 index fc4321b72..000000000 --- a/src/usr/local/containerbase/tools/v2/java.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -function prepare_tool () { - # shellcheck source=/dev/null - . "$(get_containerbase_path)/utils/java.sh" - - prepare_java -} - -function install_tool () { - # shellcheck source=/dev/null - . "$(get_containerbase_path)/utils/java.sh" - - # TODO: try jre first (#127) - install_java jdk -} - -function link_tool () { - # shellcheck source=/dev/null - . "$(get_containerbase_path)/utils/java.sh" - - link_java -} diff --git a/src/usr/local/containerbase/utils/java.sh b/src/usr/local/containerbase/utils/java.sh index fe9f0e418..c153ea701 100644 --- a/src/usr/local/containerbase/utils/java.sh +++ b/src/usr/local/containerbase/utils/java.sh @@ -39,115 +39,3 @@ EOM chown -R "${USER_ID}" "${USER_HOME}/.m2" chmod -R g+w "${USER_HOME}/.m2" } - -function get_java_install_url () { - local arch - local json - local base_url=https://api.adoptium.net/v3/assets/version - local api_args='heap_size=normal&os=linux&page=0&page_size=1&project=jdk&semver=true' - local version=${1} - local type=${2:-jre} - - if [ -z "${version}" ]; then - echo "Missing Java version" - exit 1 - fi - - # https://github.com/adoptium/api.adoptium.net/issues/468 - arch=$(uname -m) - - if ! json=$(curl --retry 3 -sSLf -H 'accept: application/json' "${base_url}/${version}?architecture=${arch}&image_type=${type}&${api_args}" 2>&1); then - echo "Invalid java version: $version" >&2 - exit 1 - fi - - echo "${json}" | jq --raw-output '.[0].binaries[0].package.link' -} - -function install_java () { - local versioned_tool_path - local file - local url - local type=${1:-jre} - local ssl_dir - - ssl_dir=$(get_ssl_path) - - if [[ ! -f "${ssl_dir}/cacerts" ]]; then - if [[ $(is_root) -ne 0 ]]; then - echo "${TOOL_NAME} not prepared" - exit 1 - fi - prepare_tool - fi - - versioned_tool_path=$(create_versioned_tool_path) - url=$(get_java_install_url "${TOOL_VERSION}" "${type}") - file=$(get_from_url "${url}") - tar --strip 1 -C "${versioned_tool_path}" -xf "${file}" - if [[ "$type" = "jdk" ]] && [[ "${MAJOR}" -eq 8 ]]; then - ln -sf "${ssl_dir}/cacerts" "${versioned_tool_path}/jre/lib/security/cacerts" - else - ln -sf "${ssl_dir}/cacerts" "${versioned_tool_path}/lib/security/cacerts" - fi -} - -function link_java () { - local versioned_tool_path - versioned_tool_path=$(find_versioned_tool_path) - - shell_wrapper java "${versioned_tool_path}/bin" - -# TODO: check if still needed -# reset_tool_env -# export_tool_env JAVA_HOME "${versioned_tool_path}" - - java -version -} - -function prepare_java () { - local ssl_dir - local url - local version - local file - - ssl_dir=$(get_ssl_path) - - if [[ -f "${ssl_dir}/cacerts" ]]; then - # cert store already there - return - fi - - version=$(get_latest_java_version jre) - url=$(get_java_install_url "${version}" jre) - file=$(get_from_url "${url}") - - mkdir -p "${TEMP_DIR}/java" - tar --strip 1 -C "${TEMP_DIR}/java" -xf "${file}" - cp "${TEMP_DIR}/java/lib/security/cacerts" "${ssl_dir}/cacerts" - rm -rf "${TEMP_DIR}/java" -} - -function get_latest_java_version () { - local arch - local version - local base_url=https://api.adoptium.net/v3/info/release_versions - local api_args='heap_size=normal&os=linux&page=0&page_size=1&project=jdk&release_type=ga<s=true&semver=true' - local type=${1:-jre} - - # https://github.com/adoptium/api.adoptium.net/issues/468 - arch=$(uname -m) - curl --retry 3 -sSLf -H 'accept: application/json' "${base_url}?architecture=${arch}&image_type=${type}&${api_args}" \ - | jq --raw-output '.versions[0].semver' -} - -# https://github.com/adoptium/api.adoptium.net/issues/492 -function patch_java_version () { - local version=${1} - if [[ "${version}" =~ ^((0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*))\+([1-9])([0-9][0-9])$ ]]; then - local build=${BASH_REMATCH[5]} - local meta=${BASH_REMATCH[6]} - version="${BASH_REMATCH[1]}.$build+$((meta + 0))" - fi - echo "${version}" -} diff --git a/test/bash/java.bats b/test/bash/java.bats index 0b42bbcdf..6edd01706 100644 --- a/test/bash/java.bats +++ b/test/bash/java.bats @@ -46,41 +46,6 @@ teardown() { assert_output "8.0.345+1" } - -@test "get_java_install_url" { - - run get_java_install_url 18.0.2+9 jre - assert_success - assert_output "https://github.com/adoptium/temurin18-binaries/releases/download/jdk-18.0.2%2B9/OpenJDK18U-jre_x64_linux_hotspot_18.0.2_9.tar.gz" - - run get_java_install_url 8.0.345+1 jre - assert_success - assert_output "https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u345-b01/OpenJDK8U-jre_x64_linux_hotspot_8u345b01.tar.gz" - - run get_java_install_url 18.0.2+101 jre - assert_success - assert_output "https://github.com/adoptium/temurin18-binaries/releases/download/jdk-18.0.2.1%2B1/OpenJDK18U-jre_x64_linux_hotspot_18.0.2.1_1.tar.gz" - - run get_java_install_url 11.0.14+101 jre - assert_success - assert_output "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.14.1%2B1/OpenJDK11U-jre_x64_linux_hotspot_11.0.14.1_1.tar.gz" - - run get_java_install_url 11.0.14+102 jre - assert_failure - assert_output "Invalid java version: 11.0.14+102" - - run get_java_install_url - assert_failure - assert_output "Missing Java version" -} - - -@test "get_latest_java_version" { - run get_latest_java_version - assert_success - assert_output --regexp '^[0-9]+\.[0-9]+\.[0-9]+\+[0-9]+' -} - @test "create_gradle_settings" { run create_gradle_settings assert_success From 75d88b79958de663698bbfc0cff3c031c0e0b55b Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Tue, 4 Jun 2024 09:04:44 +0200 Subject: [PATCH 2/5] test drop obsolete tests --- test/bash/java.bats | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/test/bash/java.bats b/test/bash/java.bats index 6edd01706..ca0d39392 100644 --- a/test/bash/java.bats +++ b/test/bash/java.bats @@ -28,24 +28,6 @@ teardown() { rm -rf "${TEST_ROOT_DIR}" } -@test "patch_java_version" { - run patch_java_version 18.0.2+101 - assert_success - assert_output "18.0.2.1+1" - - run patch_java_version 18.0.2+9 - assert_success - assert_output "18.0.2+9" - - run patch_java_version 11.0.14+101 - assert_success - assert_output "11.0.14.1+1" - - run patch_java_version 8.0.345+1 - assert_success - assert_output "8.0.345+1" -} - @test "create_gradle_settings" { run create_gradle_settings assert_success From b59c9ae88b1997195516344ac8728f977462d07f Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Tue, 4 Jun 2024 09:09:06 +0200 Subject: [PATCH 3/5] fix: revert breaking changes --- src/usr/local/containerbase/utils/java.sh | 112 ++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/src/usr/local/containerbase/utils/java.sh b/src/usr/local/containerbase/utils/java.sh index c153ea701..fe9f0e418 100644 --- a/src/usr/local/containerbase/utils/java.sh +++ b/src/usr/local/containerbase/utils/java.sh @@ -39,3 +39,115 @@ EOM chown -R "${USER_ID}" "${USER_HOME}/.m2" chmod -R g+w "${USER_HOME}/.m2" } + +function get_java_install_url () { + local arch + local json + local base_url=https://api.adoptium.net/v3/assets/version + local api_args='heap_size=normal&os=linux&page=0&page_size=1&project=jdk&semver=true' + local version=${1} + local type=${2:-jre} + + if [ -z "${version}" ]; then + echo "Missing Java version" + exit 1 + fi + + # https://github.com/adoptium/api.adoptium.net/issues/468 + arch=$(uname -m) + + if ! json=$(curl --retry 3 -sSLf -H 'accept: application/json' "${base_url}/${version}?architecture=${arch}&image_type=${type}&${api_args}" 2>&1); then + echo "Invalid java version: $version" >&2 + exit 1 + fi + + echo "${json}" | jq --raw-output '.[0].binaries[0].package.link' +} + +function install_java () { + local versioned_tool_path + local file + local url + local type=${1:-jre} + local ssl_dir + + ssl_dir=$(get_ssl_path) + + if [[ ! -f "${ssl_dir}/cacerts" ]]; then + if [[ $(is_root) -ne 0 ]]; then + echo "${TOOL_NAME} not prepared" + exit 1 + fi + prepare_tool + fi + + versioned_tool_path=$(create_versioned_tool_path) + url=$(get_java_install_url "${TOOL_VERSION}" "${type}") + file=$(get_from_url "${url}") + tar --strip 1 -C "${versioned_tool_path}" -xf "${file}" + if [[ "$type" = "jdk" ]] && [[ "${MAJOR}" -eq 8 ]]; then + ln -sf "${ssl_dir}/cacerts" "${versioned_tool_path}/jre/lib/security/cacerts" + else + ln -sf "${ssl_dir}/cacerts" "${versioned_tool_path}/lib/security/cacerts" + fi +} + +function link_java () { + local versioned_tool_path + versioned_tool_path=$(find_versioned_tool_path) + + shell_wrapper java "${versioned_tool_path}/bin" + +# TODO: check if still needed +# reset_tool_env +# export_tool_env JAVA_HOME "${versioned_tool_path}" + + java -version +} + +function prepare_java () { + local ssl_dir + local url + local version + local file + + ssl_dir=$(get_ssl_path) + + if [[ -f "${ssl_dir}/cacerts" ]]; then + # cert store already there + return + fi + + version=$(get_latest_java_version jre) + url=$(get_java_install_url "${version}" jre) + file=$(get_from_url "${url}") + + mkdir -p "${TEMP_DIR}/java" + tar --strip 1 -C "${TEMP_DIR}/java" -xf "${file}" + cp "${TEMP_DIR}/java/lib/security/cacerts" "${ssl_dir}/cacerts" + rm -rf "${TEMP_DIR}/java" +} + +function get_latest_java_version () { + local arch + local version + local base_url=https://api.adoptium.net/v3/info/release_versions + local api_args='heap_size=normal&os=linux&page=0&page_size=1&project=jdk&release_type=ga<s=true&semver=true' + local type=${1:-jre} + + # https://github.com/adoptium/api.adoptium.net/issues/468 + arch=$(uname -m) + curl --retry 3 -sSLf -H 'accept: application/json' "${base_url}?architecture=${arch}&image_type=${type}&${api_args}" \ + | jq --raw-output '.versions[0].semver' +} + +# https://github.com/adoptium/api.adoptium.net/issues/492 +function patch_java_version () { + local version=${1} + if [[ "${version}" =~ ^((0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*))\+([1-9])([0-9][0-9])$ ]]; then + local build=${BASH_REMATCH[5]} + local meta=${BASH_REMATCH[6]} + version="${BASH_REMATCH[1]}.$build+$((meta + 0))" + fi + echo "${version}" +} From f414323f3764909b979b21dee741986133dd4916 Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Tue, 4 Jun 2024 09:59:07 +0200 Subject: [PATCH 4/5] feat: revert to use adoptium api --- src/cli/tools/java/index.ts | 70 +++++++++++++++++++----------------- src/cli/tools/java/schema.ts | 36 ++++++++++++++----- src/cli/tools/java/utils.ts | 44 +++++++++++++++++++++++ 3 files changed, 109 insertions(+), 41 deletions(-) create mode 100644 src/cli/tools/java/utils.ts diff --git a/src/cli/tools/java/index.ts b/src/cli/tools/java/index.ts index 654265ec4..cb35429a6 100644 --- a/src/cli/tools/java/index.ts +++ b/src/cli/tools/java/index.ts @@ -12,11 +12,7 @@ import { PathService, } from '../../services'; import { logger } from '../../utils'; -import type { JavaReleases } from './schema'; - -const baseUrl = - 'https://github.com/joschi/java-metadata/raw/main/docs/metadata/ga/linux'; -const fileUrl = 'hotspot/temurin.json'; +import { resolveJavaDownloadUrl, resolveLatestJavaLtsVersion } from './utils'; @injectable() export class PrepareJavaService extends PrepareToolBaseService { @@ -40,25 +36,33 @@ export class PrepareJavaService extends PrepareToolBaseService { return; } - const url = `${baseUrl}/${this.envSvc.arch === 'amd64' ? 'x86_64' : 'aarch64'}/jre/${fileUrl}`; + const version = await resolveLatestJavaLtsVersion( + this.httpSvc, + 'jre', + this.envSvc.arch, + ); + if (!version) { + throw new Error('Could not resolve latest java version'); + } - const releases = await this.httpSvc.getJson(url); + const pkg = await resolveJavaDownloadUrl( + this.httpSvc, + 'jre', + this.envSvc.arch, + version, + ); - // sadly no lts info in metadata, so using latest version - // also ignore alpine builds - const latest = releases - .filter((c) => !c.filename.includes('_alpine-linux_')) - .reduce((prev, curr) => - prev && semver.compare(prev.version, curr.version) > 0 ? prev : curr, - ); + if (!pkg) { + throw new Error(`Could not resolve download url for java ${version}`); + } - logger.debug(`downloading cacerts from ${latest.version}`); + logger.debug(`downloading cacerts from ${version}`); const jre = await this.httpSvc.download({ - url: latest.url, - checksumType: 'sha512', - expectedChecksum: latest.sha512, - fileName: latest.filename, + url: pkg.link, + checksumType: 'sha256', + expectedChecksum: pkg.checksum, + fileName: pkg.name, }); const tmp = path.join(this.pathSvc.tmpDir, 'java'); @@ -106,26 +110,26 @@ export class InstallJavaService extends InstallToolBaseService { } override async install(version: string): Promise { - const arch = this.envSvc.arch === 'amd64' ? 'x86_64' : 'aarch64'; const type = this.name === 'java-jre' ? 'jre' : 'jdk'; - const url = `${baseUrl}/${arch}/${type}/${fileUrl}`; - - const releases = await this.http.getJson(url); - // Ignore alpine builds - const release = releases - .filter((c) => !c.filename.includes('_alpine-linux_')) - .find((r) => r.version === version); + const pkg = await resolveJavaDownloadUrl( + this.http, + type, + this.envSvc.arch, + version, + ); - if (!release) { - throw new Error(`Version ${version} not found`); + if (!pkg) { + throw new Error( + `Could not resolve download url for java ${version} and type ${type}`, + ); } const file = await this.http.download({ - url: release.url, - checksumType: 'sha512', - expectedChecksum: release.sha512, - fileName: release.filename, + url: pkg.link, + checksumType: 'sha256', + expectedChecksum: pkg.checksum, + fileName: pkg.name, }); // TODO: create recursive diff --git a/src/cli/tools/java/schema.ts b/src/cli/tools/java/schema.ts index eb94baae6..c88d17333 100644 --- a/src/cli/tools/java/schema.ts +++ b/src/cli/tools/java/schema.ts @@ -1,12 +1,32 @@ import { z } from 'zod'; -const JavaRelease = z.object({ - filename: z.string(), - sha512: z.string(), - url: z.string(), - version: z.string(), +// https://api.adoptium.net/q/swagger-ui + +const AdoptiumVersionData = z.object({ + semver: z.string(), +}); + +export const AdoptiumReleaseVersions = z.object({ + versions: z.array(AdoptiumVersionData), +}); + +const AdoptiumPackage = z.object({ + /** + * sha256 checksum + */ + checksum: z.string(), + link: z.string(), + name: z.string(), +}); + +export type AdoptiumPackage = z.infer; + +const AdoptiumBinary = z.object({ + package: AdoptiumPackage, +}); + +const AdoptiumRelease = z.object({ + binaries: z.array(AdoptiumBinary), }); -export type JavaRelease = z.infer; -export const JavaReleases = z.array(JavaRelease); -export type JavaReleases = z.infer; +export const AdoptiumReleases = z.array(AdoptiumRelease); diff --git a/src/cli/tools/java/utils.ts b/src/cli/tools/java/utils.ts new file mode 100644 index 000000000..d6e29959e --- /dev/null +++ b/src/cli/tools/java/utils.ts @@ -0,0 +1,44 @@ +import type { HttpService } from '../../services'; +import type { Arch } from '../../utils'; +import { + type AdoptiumPackage, + AdoptiumReleaseVersions, + AdoptiumReleases, +} from './schema'; + +export async function resolveLatestJavaLtsVersion( + http: HttpService, + type: 'jre' | 'jdk', + arch: Arch, +): Promise { + const base_url = 'https://api.adoptium.net/v3/info/release_versions'; + const api_args = + 'heap_size=normal&os=linux&page=0&page_size=1&project=jdk&release_type=ga<s=true&semver=true'; + + const res = AdoptiumReleaseVersions.parse( + await http.getJson( + `${base_url}?architecture=${arch === 'amd64' ? 'x64' : 'aarch64'}&image_type=${type}&${api_args}`, + ), + ); + + return res.versions[0]!.semver; +} + +export async function resolveJavaDownloadUrl( + http: HttpService, + type: 'jre' | 'jdk', + arch: Arch, + version: string, +): Promise { + const base_url = 'https://api.adoptium.net/v3/assets/version'; + const api_args = + 'heap_size=normal&os=linux&page=0&page_size=1&project=jdk&semver=true'; + + const res = AdoptiumReleases.parse( + await http.getJson( + `${base_url}/${version}?architecture=${arch === 'amd64' ? 'x64' : 'aarch64'}&image_type=${type}&${api_args}`, + ), + ); + + return res?.[0]?.binaries?.[0]?.package; +} From c3fcdf0ffa8f359281d2def668954ed3dbe0f90e Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Tue, 4 Jun 2024 10:17:57 +0200 Subject: [PATCH 5/5] fix: revert --- src/cli/install-tool/install-tool-base.service.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/cli/install-tool/install-tool-base.service.ts b/src/cli/install-tool/install-tool-base.service.ts index 22a47a40e..fce916d51 100644 --- a/src/cli/install-tool/install-tool-base.service.ts +++ b/src/cli/install-tool/install-tool-base.service.ts @@ -28,9 +28,6 @@ export abstract class InstallToolBaseService { async isInstalled(version: string): Promise { return !!(await this.pathSvc.findVersionedToolPath(this.name, version)); } - async isPrepared(): Promise { - return null !== (await this.pathSvc.findToolPath(this.name)); - } async isPrepared(): Promise { return null !== (await this.pathSvc.findToolPath(this.name));