Skip to content

Commit

Permalink
feat(cli): convert java installer
Browse files Browse the repository at this point in the history
  • Loading branch information
viceice committed Jun 4, 2024
1 parent 621ddb2 commit b3ee3b4
Show file tree
Hide file tree
Showing 17 changed files with 240 additions and 221 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions src/cli/install-tool/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Check warning on line 15 in src/cli/install-tool/index.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/install-tool/index.ts#L11-L15

Added lines #L11 - L15 were not covered by tests
import { InstallMavenService } from '../tools/java/maven';
import { InstallNodeService } from '../tools/node';
import {
Expand Down Expand Up @@ -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);

Check warning on line 59 in src/cli/install-tool/index.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/install-tool/index.ts#L57-L59

Added lines #L57 - L59 were not covered by tests
container.bind(INSTALL_TOOL_TOKEN).to(InstallMavenService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallNodeService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallRenovateService);
Expand Down
3 changes: 3 additions & 0 deletions src/cli/install-tool/install-tool-base.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export abstract class InstallToolBaseService {
async isInstalled(version: string): Promise<boolean> {
return !!(await this.pathSvc.findVersionedToolPath(this.name, version));
}
async isPrepared(): Promise<boolean> {
return null !== (await this.pathSvc.findToolPath(this.name));
}

Check warning on line 33 in src/cli/install-tool/install-tool-base.service.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/install-tool/install-tool-base.service.ts#L31-L33

Added lines #L31 - L33 were not covered by tests

abstract link(version: string): Promise<void>;

Expand Down
5 changes: 1 addition & 4 deletions src/cli/install-tool/install-tool.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,7 @@ export class InstallToolService {
return;
}

if (
toolSvc.needsPrepare() &&
!(await this.pathSvc.findToolPath(tool))
) {
if (toolSvc.needsPrepare() && !(await toolSvc.isPrepared())) {

Check warning on line 47 in src/cli/install-tool/install-tool.service.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/install-tool/install-tool.service.ts#L47

Added line #L47 was not covered by tests
logger.debug({ tool }, 'tool not prepared');
const res = await prepareTools([tool], dryRun);
if (res) {
Expand Down
8 changes: 8 additions & 0 deletions src/cli/prepare-tool/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Check warning on line 11 in src/cli/prepare-tool/index.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/prepare-tool/index.ts#L7-L11

Added lines #L7 - L11 were not covered by tests
import { logger } from '../utils';
import { PrepareLegacyToolsService } from './prepare-legacy-tools.service';
import { PREPARE_TOOL_TOKEN, PrepareToolService } from './prepare-tool.service';
Expand All @@ -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);

Check warning on line 32 in src/cli/prepare-tool/index.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/prepare-tool/index.ts#L30-L32

Added lines #L30 - L32 were not covered by tests

logger.trace('preparing container done');
return container;
Expand Down
4 changes: 4 additions & 0 deletions src/cli/prepare-tool/prepare-tool-base.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export abstract class PrepareToolBaseService {

abstract execute(): Promise<void> | void;

async isPrepared(): Promise<boolean> {
return null !== (await this.pathSvc.findToolPath(this.name));
}

Check warning on line 18 in src/cli/prepare-tool/prepare-tool-base.service.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/prepare-tool/prepare-tool-base.service.ts#L15-L18

Added lines #L15 - L18 were not covered by tests
toString(): string {
return this.name;
}
Expand Down
4 changes: 2 additions & 2 deletions src/cli/prepare-tool/prepare-tool.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class PrepareToolService {
logger.info({ tool }, 'tool ignored');
continue;
}
if (await this.pathSvc.findToolPath(tool.name)) {
if (await tool.isPrepared()) {

Check warning on line 42 in src/cli/prepare-tool/prepare-tool.service.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/prepare-tool/prepare-tool.service.ts#L42

Added line #L42 was not covered by tests
logger.debug({ tool: tool.name }, 'tool already prepared');
continue;
}
Expand All @@ -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()) {

Check warning on line 63 in src/cli/prepare-tool/prepare-tool.service.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/prepare-tool/prepare-tool.service.ts#L63

Added line #L63 was not covered by tests
logger.debug({ tool }, 'tool already prepared');
continue;
}
Expand Down
6 changes: 6 additions & 0 deletions src/cli/services/path.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
4 changes: 4 additions & 0 deletions src/cli/services/path.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
Expand Down
182 changes: 182 additions & 0 deletions src/cli/tools/java/index.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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<JavaReleases>(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<boolean> {
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<void> {
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<JavaReleases>(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<boolean> {
return await this.pathSvc.fileExists(
path.join(this.pathSvc.sslPath, 'cacerts'),
);
}

override async link(version: string): Promise<void> {
const src = path.join(
this.pathSvc.versionedToolPath(this.name, version),
'bin',
);
await this.shellwrapper({ srcDir: src, name: 'java' });
}

override async test(_version: string): Promise<void> {
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';
}

Check warning on line 182 in src/cli/tools/java/index.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/tools/java/index.ts#L1-L182

Added lines #L1 - L182 were not covered by tests
12 changes: 12 additions & 0 deletions src/cli/tools/java/schema.ts
Original file line number Diff line number Diff line change
@@ -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<typeof JavaRelease>;

export const JavaReleases = z.array(JavaRelease);
export type JavaReleases = z.infer<typeof JavaReleases>;

Check warning on line 12 in src/cli/tools/java/schema.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/tools/java/schema.ts#L1-L12

Added lines #L1 - L12 were not covered by tests
22 changes: 0 additions & 22 deletions src/usr/local/containerbase/tools/v2/java-jdk.sh

This file was deleted.

22 changes: 0 additions & 22 deletions src/usr/local/containerbase/tools/v2/java-jre.sh

This file was deleted.

Loading

0 comments on commit b3ee3b4

Please sign in to comment.