From 6dc53a24ed20fd02053e63ae4c02697bf4992f32 Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Fri, 27 Sep 2024 16:40:21 +0200 Subject: [PATCH] feat: partial readonly fs support --- src/cli/command/index.ts | 2 + src/cli/command/init-tool.spec.ts | 26 ++++ src/cli/command/init-tool.ts | 47 ++++++ src/cli/install-tool/base-install.service.ts | 17 ++- src/cli/install-tool/install-tool.service.ts | 12 +- src/cli/prepare-tool/base-prepare.service.ts | 7 +- src/cli/prepare-tool/index.spec.ts | 19 ++- src/cli/prepare-tool/index.ts | 10 +- .../prepare-legacy-tools.service.ts | 13 +- src/cli/prepare-tool/prepare-tool.service.ts | 136 +++++++++++++----- src/cli/services/path.service.spec.ts | 12 +- src/cli/services/path.service.ts | 62 +++++++- src/cli/tools/dart/index.ts | 15 +- src/cli/tools/dart/utils.ts | 23 ++- src/cli/tools/docker.ts | 2 +- src/cli/tools/dotnet.ts | 31 ++-- src/cli/tools/flutter.ts | 40 ++++-- src/cli/tools/index.ts | 2 + src/cli/tools/java/index.ts | 28 ++-- src/cli/tools/java/utils.ts | 12 +- src/cli/tools/node/index.ts | 20 ++- src/cli/tools/node/utils.ts | 4 +- src/cli/tools/php/index.ts | 2 +- src/cli/tools/python/conan.ts | 17 ++- src/cli/utils/common.ts | 2 +- src/usr/local/containerbase/bin/init-tool.sh | 28 ++-- .../local/containerbase/bin/install-tool.sh | 4 - .../local/containerbase/bin/prepare-tool.sh | 20 +-- .../local/containerbase/tools/v2/golang.sh | 17 ++- src/usr/local/containerbase/tools/v2/ruby.sh | 52 ++++--- src/usr/local/containerbase/tools/v2/rust.sh | 18 ++- .../local/containerbase/utils/filesystem.sh | 5 +- src/usr/local/containerbase/utils/init.sh | 30 +--- src/usr/local/containerbase/utils/install.sh | 2 +- src/usr/local/containerbase/utils/linking.sh | 7 + src/usr/local/containerbase/utils/prepare.sh | 2 +- test/node/Dockerfile | 51 ++++--- 37 files changed, 567 insertions(+), 230 deletions(-) create mode 100644 src/cli/command/init-tool.spec.ts create mode 100644 src/cli/command/init-tool.ts diff --git a/src/cli/command/index.ts b/src/cli/command/index.ts index 246d510bf5..1b391159c6 100644 --- a/src/cli/command/index.ts +++ b/src/cli/command/index.ts @@ -2,6 +2,7 @@ import type { Cli } from 'clipanion'; import type { CliMode } from '../utils'; import { logger } from '../utils/logger'; import { DownloadFileCommand } from './download-file'; +import { InitToolCommand } from './init-tool'; import { InstallGemCommand, InstallGemShortCommand } from './install-gem'; import { InstallNpmCommand, InstallNpmShortCommand } from './install-npm'; import { InstallPipCommand, InstallPipShortCommand } from './install-pip'; @@ -40,4 +41,5 @@ export function prepareCommands(cli: Cli, mode: CliMode | null): void { cli.register(InstallPipCommand); cli.register(InstallToolCommand); cli.register(PrepareToolCommand); + cli.register(InitToolCommand); } diff --git a/src/cli/command/init-tool.spec.ts b/src/cli/command/init-tool.spec.ts new file mode 100644 index 0000000000..c33ddb0e89 --- /dev/null +++ b/src/cli/command/init-tool.spec.ts @@ -0,0 +1,26 @@ +import { Cli } from 'clipanion'; +import { describe, expect, test, vi } from 'vitest'; +import { prepareCommands } from '.'; + +const mocks = vi.hoisted(() => ({ + installTool: vi.fn(), + prepareTools: vi.fn(), + initializeTools: vi.fn(), +})); + +vi.mock('../install-tool', () => mocks); +vi.mock('../prepare-tool', () => mocks); + +describe('index', () => { + test('init-tool', async () => { + const cli = new Cli({ binaryName: 'cli' }); + prepareCommands(cli, null); + + expect(await cli.run(['init', 'tool', 'node'])).toBe(0); + expect(mocks.initializeTools).toHaveBeenCalledOnce(); + expect(mocks.initializeTools).toHaveBeenCalledWith(['node'], false); + + mocks.initializeTools.mockRejectedValueOnce(new Error('test')); + expect(await cli.run(['init', 'tool', 'node'])).toBe(1); + }); +}); diff --git a/src/cli/command/init-tool.ts b/src/cli/command/init-tool.ts new file mode 100644 index 0000000000..cca00c8152 --- /dev/null +++ b/src/cli/command/init-tool.ts @@ -0,0 +1,47 @@ +import { Command, Option } from 'clipanion'; +import prettyMilliseconds from 'pretty-ms'; +import { initializeTools } from '../prepare-tool'; +import { logger } from '../utils'; + +export class InitToolCommand extends Command { + static override paths = [['init', 'tool']]; + + static override usage = Command.Usage({ + description: + 'Initialize a tool into the container. This creates missing files and directories.', + examples: [ + ['Initialize node', '$0 init tool node'], + ['Initialize all tools', '$0 init tool all'], + ], + }); + + tools = Option.Rest({ required: 1 }); + + dryRun = Option.Boolean('-d,--dry-run', false); + + async execute(): Promise { + const start = Date.now(); + let error = false; + logger.info(`Initializing tools ${this.tools.join(', ')}...`); + try { + return await initializeTools(this.tools, this.dryRun); + } catch (err) { + error = true; + logger.debug(err); + if (err instanceof Error) { + logger.fatal(err.message); + } + return 1; + } finally { + if (error) { + logger.fatal( + `Initialize tools ${this.tools.join(', ')} failed in ${prettyMilliseconds(Date.now() - start)}.`, + ); + } else { + logger.info( + `Initialize tools ${this.tools.join(', ')} succeded in ${prettyMilliseconds(Date.now() - start)}.`, + ); + } + } + } +} diff --git a/src/cli/install-tool/base-install.service.ts b/src/cli/install-tool/base-install.service.ts index f1dfe90d8d..40e8940c89 100644 --- a/src/cli/install-tool/base-install.service.ts +++ b/src/cli/install-tool/base-install.service.ts @@ -3,7 +3,7 @@ import { join } from 'node:path'; import { codeBlock } from 'common-tags'; import { injectable } from 'inversify'; import type { EnvService, PathService } from '../services'; -import { NoPrepareTools } from '../tools'; +import { NoInitTools, NoPrepareTools } from '../tools'; import { isValid } from '../utils'; export interface ShellWrapperConfig { @@ -35,12 +35,20 @@ export abstract class BaseInstallService { return !!(await this.pathSvc.findVersionedToolPath(this.name, version)); } + async isInitialized(): Promise { + return await this.pathSvc.isInitialized(this.name); + } + async isPrepared(): Promise { - return null !== (await this.pathSvc.findToolPath(this.name)); + return await this.pathSvc.isPrepared(this.name); } abstract link(version: string): Promise; + needsInitialize(): boolean { + return !NoInitTools.includes(this.name); + } + needsPrepare(): boolean { return !NoPrepareTools.includes(this.name); } @@ -78,6 +86,11 @@ export abstract class BaseInstallService { . ${this.pathSvc.envFile} fi + if [[ ! -f "${this.pathSvc.toolInitPath(this.name)}" ]]; then + # set logging to only warn and above to not interfere with tool output + CONTAINERBASE_LOG_LEVEL=warn containerbase-cli init tool "${this.name}" + fi + # load tool envs for n in ${envs.join(' ')}; do if [[ -f "${this.pathSvc.toolsPath}/\${n}/env.sh" ]]; then diff --git a/src/cli/install-tool/install-tool.service.ts b/src/cli/install-tool/install-tool.service.ts index 6edc5e1ce8..4df478e103 100644 --- a/src/cli/install-tool/install-tool.service.ts +++ b/src/cli/install-tool/install-tool.service.ts @@ -1,6 +1,6 @@ import { deleteAsync } from 'del'; import { inject, injectable, multiInject, optional } from 'inversify'; -import { prepareTools } from '../prepare-tool'; +import { initializeTools, prepareTools } from '../prepare-tool'; import { EnvService, PathService, VersionService } from '../services'; import { cleanAptFiles, cleanTmpFiles, isDockerBuild, logger } from '../utils'; import type { BaseInstallService } from './base-install.service'; @@ -31,6 +31,8 @@ export class InstallToolService { 'supported tools', ); + await this.pathSvc.ensureBasePaths(); + try { const toolSvc = this.toolSvcs.find((t) => t.name === tool); if (toolSvc) { @@ -48,6 +50,14 @@ export class InstallToolService { } } + if (toolSvc.needsInitialize() && !(await toolSvc.isInitialized())) { + logger.debug({ tool }, 'tool not initialized'); + const res = await initializeTools([tool], dryRun); + if (res) { + return res; + } + } + logger.debug({ tool }, 'validate tool'); if (!(await toolSvc.validate(version))) { logger.fatal({ tool, version }, 'tool version not supported'); diff --git a/src/cli/prepare-tool/base-prepare.service.ts b/src/cli/prepare-tool/base-prepare.service.ts index 795a943a71..b691eb5f3a 100644 --- a/src/cli/prepare-tool/base-prepare.service.ts +++ b/src/cli/prepare-tool/base-prepare.service.ts @@ -10,7 +10,12 @@ export abstract class BasePrepareService { @inject(EnvService) protected readonly envSvc: EnvService, ) {} - abstract execute(): Promise | void; + prepare(): Promise | void { + // noting to do; + } + initialize(): Promise | void { + // noting to do; + } toString(): string { return this.name; diff --git a/src/cli/prepare-tool/index.spec.ts b/src/cli/prepare-tool/index.spec.ts index b22aff6713..d06d353473 100644 --- a/src/cli/prepare-tool/index.spec.ts +++ b/src/cli/prepare-tool/index.spec.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises'; import { beforeAll, describe, expect, test, vi } from 'vitest'; import { rootPath } from '../../../test/path'; -import { prepareTools } from '.'; +import { initializeTools, prepareTools } from '.'; vi.mock('del'); vi.mock('execa'); @@ -15,13 +15,22 @@ vi.mock('node:process', async (importOriginal) => ({ describe('index', () => { beforeAll(async () => { - const prepDir = rootPath('var/lib/containerbase/tool.prep.d'); - await fs.mkdir(prepDir, { - recursive: true, - }); + for (const p of [ + 'var/lib/containerbase/tool.prep.d', + 'tmp/containerbase/tool.init.d', + ]) { + const prepDir = rootPath(p); + await fs.mkdir(prepDir, { + recursive: true, + }); + } }); test('prepareTools', async () => { expect(await prepareTools(['bun', 'dummy'])).toBeUndefined(); }); + + test('initializeTools', async () => { + expect(await initializeTools(['bun', 'dummy'])).toBeUndefined(); + }); }); diff --git a/src/cli/prepare-tool/index.ts b/src/cli/prepare-tool/index.ts index 3d9bdcdbcb..34025d1c03 100644 --- a/src/cli/prepare-tool/index.ts +++ b/src/cli/prepare-tool/index.ts @@ -46,5 +46,13 @@ export function prepareTools( dryRun = false, ): Promise { const container = prepareContainer(); - return container.get(PrepareToolService).execute(tools, dryRun); + return container.get(PrepareToolService).prepare(tools, dryRun); +} + +export function initializeTools( + tools: string[], + dryRun = false, +): Promise { + const container = prepareContainer(); + return container.get(PrepareToolService).initialize(tools, dryRun); } diff --git a/src/cli/prepare-tool/prepare-legacy-tools.service.ts b/src/cli/prepare-tool/prepare-legacy-tools.service.ts index 6691a62c82..3f17630a72 100644 --- a/src/cli/prepare-tool/prepare-legacy-tools.service.ts +++ b/src/cli/prepare-tool/prepare-legacy-tools.service.ts @@ -4,9 +4,16 @@ import { logger } from '../utils'; @injectable() export class PrepareLegacyToolsService { - async execute(tools: string[]): Promise { - logger.debug(`Preparing legacy tools ${tools.join(', ')} ...`); - await execa('/usr/local/containerbase/bin/prepare-tool.sh', tools, { + async prepare(tool: string): Promise { + logger.debug(`Preparing legacy tool ${tool} ...`); + await execa('/usr/local/containerbase/bin/prepare-tool.sh', [tool], { + stdio: ['inherit', 'inherit', 1], + }); + } + + async initialize(tool: string): Promise { + logger.debug(`Initializing legacy tool ${tool} ...`); + await execa('/usr/local/containerbase/bin/init-tool.sh', [tool], { stdio: ['inherit', 'inherit', 1], }); } diff --git a/src/cli/prepare-tool/prepare-tool.service.ts b/src/cli/prepare-tool/prepare-tool.service.ts index 1d19fd41f2..2202a43ce6 100644 --- a/src/cli/prepare-tool/prepare-tool.service.ts +++ b/src/cli/prepare-tool/prepare-tool.service.ts @@ -1,7 +1,7 @@ import { deleteAsync } from 'del'; import { inject, injectable, multiInject, optional } from 'inversify'; import { EnvService, PathService } from '../services'; -import { NoPrepareTools } from '../tools'; +import { NoInitTools, NoPrepareTools } from '../tools'; import { cleanAptFiles, cleanTmpFiles, logger } from '../utils'; import type { BasePrepareService } from './base-prepare.service'; import { PrepareLegacyToolsService } from './prepare-legacy-tools.service'; @@ -20,7 +20,7 @@ export class PrepareToolService { @inject(EnvService) private envSvc: EnvService, ) {} - async execute(tools: string[], dryRun = false): Promise { + async prepare(tools: string[], dryRun = false): Promise { logger.trace( { tools: this.toolSvcs.map((t) => t.name) }, 'supported tools', @@ -35,42 +35,21 @@ export class PrepareToolService { } try { if (tools.length === 1 && tools[0] === 'all') { - for (const tool of this.toolSvcs) { - if (this.envSvc.isToolIgnored(tool.name)) { - logger.info({ tool }, 'tool ignored'); - continue; + for (const tool of [ + ...this.toolSvcs.map((t) => t.name), + ...(await this.pathSvc.findLegacyTools()), + ]) { + const res = await this._prepareTool(tool, dryRun); + if (res) { + return res; } - if (await this.pathSvc.isPrepared(tool.name)) { - logger.debug({ tool: tool.name }, 'tool already prepared'); - continue; - } - logger.debug({ tool: tool.name }, 'preparing tool'); - await tool.execute(); - await this.pathSvc.ensureToolPath(tool.name); - await this.pathSvc.setPrepared(tool.name); + await this.pathSvc.setPrepared(tool); } - await this.legacySvc.execute(tools); } else { for (const tool of tools) { - if (this.envSvc.isToolIgnored(tool)) { - logger.info({ tool }, 'tool ignored'); - continue; - } - if (NoPrepareTools.includes(tool)) { - logger.info({ tool }, 'tool does not need to be prepared'); - continue; - } - if (await this.pathSvc.isPrepared(tool)) { - logger.debug({ tool }, 'tool already prepared'); - continue; - } - const toolSvc = this.toolSvcs.find((t) => t.name === tool); - if (toolSvc) { - logger.debug({ tool }, 'preparing tool'); - await toolSvc.execute(); - await this.pathSvc.ensureToolPath(tool); - } else { - await this.legacySvc.execute([tool]); + const res = await this._prepareTool(tool, dryRun); + if (res) { + return res; } await this.pathSvc.setPrepared(tool); } @@ -85,4 +64,93 @@ export class PrepareToolService { }); } } + + async initialize(tools: string[], dryRun = false): Promise { + logger.trace( + { tools: this.toolSvcs.map((t) => t.name) }, + 'supported tools', + ); + if (dryRun) { + logger.info(`Dry run: initializing tools ${tools.join(', ')} ...`); + return; + } + + await this.pathSvc.ensureBasePaths(); + + if (tools.length === 1 && tools[0] === 'all') { + for (const tool of [ + ...this.toolSvcs.map((t) => t.name), + ...(await this.pathSvc.findLegacyTools()), + ]) { + const res = await this._initTool(tool, dryRun); + if (res) { + return res; + } + await this.pathSvc.setInitialized(tool); + } + } else { + for (const tool of tools) { + const res = await this._initTool(tool, dryRun); + if (res) { + return res; + } + await this.pathSvc.setInitialized(tool); + } + } + } + + private async _initTool( + tool: string, + _dryRun: boolean, + ): Promise { + if (this.envSvc.isToolIgnored(tool)) { + logger.info({ tool }, 'tool ignored'); + return; + } + if (NoInitTools.includes(tool)) { + logger.info({ tool }, 'tool does not need to be initialized'); + return; + } + if (await this.pathSvc.isInitialized(tool)) { + logger.debug({ tool }, 'tool already initialized'); + return; + } + const toolSvc = this.toolSvcs.find((t) => t.name === tool); + if (toolSvc) { + logger.debug({ tool }, 'initialize tool'); + await toolSvc.initialize(); + await this.pathSvc.ensureToolPath(tool); + } else if (await this.pathSvc.isLegacyTool(tool)) { + await this.legacySvc.initialize(tool); + } // ignore else + } + + private async _prepareTool( + tool: string, + _dryRun: boolean, + ): Promise { + if (this.envSvc.isToolIgnored(tool)) { + logger.info({ tool }, 'tool ignored'); + return; + } + if (NoPrepareTools.includes(tool)) { + logger.info({ tool }, 'tool does not need to be prepared'); + return; + } + if (await this.pathSvc.isPrepared(tool)) { + logger.debug({ tool }, 'tool already prepared'); + return; + } + const toolSvc = this.toolSvcs.find((t) => t.name === tool); + if (toolSvc) { + logger.debug({ tool }, 'preparing tool'); + await toolSvc.prepare(); + await this.pathSvc.ensureToolPath(tool); + } else if (await this.pathSvc.isLegacyTool(tool)) { + await this.legacySvc.prepare(tool); + } else { + logger.error({ tool }, 'tool not found'); + return 1; + } + } } diff --git a/src/cli/services/path.service.spec.ts b/src/cli/services/path.service.spec.ts index 6bd4b31a24..719977e899 100644 --- a/src/cli/services/path.service.spec.ts +++ b/src/cli/services/path.service.spec.ts @@ -65,6 +65,15 @@ describe('path.service', () => { expect(await child.get(PathService).toolEnvExists('node')).toBe(false); }); + test('ensureBasePaths', async () => { + await child.get(PathService).ensureBasePaths(); + expect(await pathExists(rootPath('opt/containerbase'), true)).toBe(true); + expect(await pathExists(rootPath('var/lib/containerbase'), true)).toBe( + true, + ); + expect(await pathExists(rootPath('tmp/containerbase'), true)).toBe(true); + }); + test('exportToolEnvContent', async () => { const pathSvc = child.get(PathService); @@ -180,11 +189,12 @@ describe('path.service', () => { }); test('createDir', async () => { - const dir = rootPath('env123'); + const dir = rootPath('env123/sub'); expect(await child.get(PathService).createDir(dir)).toBeUndefined(); const s = await stat(dir); expect(s.mode & fileRights).toBe(platform() === 'win32' ? 0 : 0o775); + expect(await child.get(PathService).createDir(dir)).toBeUndefined(); }); test('toolInit', async () => { diff --git a/src/cli/services/path.service.ts b/src/cli/services/path.service.ts index a02004078f..de074bc413 100644 --- a/src/cli/services/path.service.ts +++ b/src/cli/services/path.service.ts @@ -15,6 +15,19 @@ export interface FileOwnerConfig { @injectable() export class PathService { + /** + * Path to `/tmp/containerbase/tool.init.d`. + */ + private get _toolInitPath(): string { + return join(this.tmpDir, 'tool.init.d'); + } + /** + * Path to `/var/lib/containerbase/tool.prep.d`. + */ + private get _toolPrepPath(): string { + return join(this.varPath, 'tool.prep.d'); + } + get binDir(): string { return join(this.installDir, 'bin'); } @@ -49,6 +62,13 @@ export class PathService { return join(this.installDir, 'tools'); } + /** + * Path to `/usr/local/containerbase`. + */ + get usrPath(): string { + return join(this.envSvc.rootDir, 'usr/local/containerbase'); + } + /** * Path to `/var/lib/containerbase`. */ @@ -70,6 +90,7 @@ export class PathService { if (!(await pathExists(parent, true))) { await this.createDir(parent, 0o775); } + logger.debug({ path }, 'creating dir'); await fs.mkdir(path); await this.setOwner({ path, mode }); } @@ -79,8 +100,8 @@ export class PathService { return toolPath; } - async ensureToolPath(tool: string): Promise { - return (await this.findToolPath(tool)) ?? (await this.createToolPath(tool)); + async dirExists(filePath: string): Promise { + return await pathExists(filePath, true); } async createVersionedToolPath( @@ -92,6 +113,21 @@ export class PathService { return toolPath; } + async ensureBasePaths(): Promise { + if (!(await pathExists(this.varPath, true))) { + throw new Error('System not initialized for containerbase'); + } + await this.createDir(this._toolPrepPath); + await this.createDir(this.toolsPath); + await this.createDir(this.versionPath); + await this.createDir(this.sslPath); + await this.createDir(this._toolInitPath); + } + + async ensureToolPath(tool: string): Promise { + return (await this.findToolPath(tool)) ?? (await this.createToolPath(tool)); + } + async findToolPath(tool: string): Promise { const toolPath = this.toolPath(tool); @@ -113,6 +149,13 @@ export class PathService { return null; } + async findLegacyTools(): Promise { + const tools = await fs.readdir(join(this.usrPath, 'tools/v2')); + return tools + .filter((t) => t.endsWith('.sh')) + .map((t) => t.substring(0, t.length - 3)); + } + async fileExists(filePath: string): Promise { return await pathExists(filePath); } @@ -125,6 +168,14 @@ export class PathService { return await this.fileExists(this.toolPreparePath(tool)); } + async isLegacyTool(tool: string, v1 = false): Promise { + let exists = await pathExists(join(this.usrPath, 'tools/v2', `${tool}.sh`)); + if (!exists && v1) { + exists = await pathExists(join(this.usrPath, 'tools', `${tool}.sh`)); + } + return exists; + } + async setInitialized(tool: string): Promise { await fs.writeFile(this.toolInitPath(tool), ''); } @@ -134,7 +185,7 @@ export class PathService { } toolInitPath(tool: string): string { - return join(this.tmpDir, 'tool.init.d', tool); + return join(this._toolInitPath, tool); } toolPath(tool: string): string { @@ -142,7 +193,7 @@ export class PathService { } toolPreparePath(tool: string): string { - return join(this.varPath, 'tool.prep.d', tool); + return join(this._toolPrepPath, tool); } versionedToolPath(tool: string, version: string): string { @@ -223,8 +274,7 @@ export class PathService { content += 'fi\n'; } - await fs.appendFile(file, content); - await this.setOwner({ path: file, mode: 0o664 }); + await this.writeFile(file, content); } async exportToolPath( diff --git a/src/cli/tools/dart/index.ts b/src/cli/tools/dart/index.ts index c0f84f342f..35f8afaf3a 100644 --- a/src/cli/tools/dart/index.ts +++ b/src/cli/tools/dart/index.ts @@ -11,7 +11,12 @@ import { PathService, } from '../../services'; import { parse } from '../../utils'; -import { prepareDartHome, preparePubCache } from './utils'; +import { + initDartHome, + initPubCache, + prepareDartHome, + preparePubCache, +} from './utils'; // Dart SDK sample urls // https://storage.googleapis.com/dart-archive/channels/stable/release/1.11.0/sdk/dartsdk-linux-x64-release.zip @@ -24,10 +29,16 @@ import { prepareDartHome, preparePubCache } from './utils'; export class DartPrepareService extends BasePrepareService { readonly name = 'dart'; - async execute(): Promise { + override async prepare(): Promise { + await this.initialize(); await prepareDartHome(this.envSvc, this.pathSvc); await preparePubCache(this.envSvc, this.pathSvc); } + + override async initialize(): Promise { + await initDartHome(this.pathSvc); + await initPubCache(this.pathSvc); + } } @injectable() diff --git a/src/cli/tools/dart/utils.ts b/src/cli/tools/dart/utils.ts index 8ff080eaf5..21170bf9c9 100644 --- a/src/cli/tools/dart/utils.ts +++ b/src/cli/tools/dart/utils.ts @@ -7,6 +7,15 @@ export async function prepareDartHome( envSvc: EnvService, pathSvc: PathService, ): Promise { + const dart = join(envSvc.userHome, '.dart'); + if (!(await pathExists(dart, true))) { + await fs.symlink(join(pathSvc.cachePath, '.dart'), dart); + await fs.symlink( + join(pathSvc.cachePath, '.dart-tool'), + join(envSvc.userHome, '.dart-tool'), + ); + } + // for root const rootDart = join(envSvc.rootDir, 'root', '.dart'); if (!(await pathExists(rootDart, true))) { @@ -16,7 +25,9 @@ export async function prepareDartHome( '{ "firstRun": false, "enabled": false }', ); } +} +export async function initDartHome(pathSvc: PathService): Promise { // for user const dart = join(pathSvc.cachePath, '.dart'); if (await pathExists(dart, true)) { @@ -30,9 +41,6 @@ export async function prepareDartHome( const dartToolTelemetry = join(dartTool, 'dart-flutter-telemetry.config'); await pathSvc.writeFile(dartToolTelemetry, 'reporting=0\n'); - - await fs.symlink(dart, join(envSvc.userHome, '.dart')); - await fs.symlink(dartTool, join(envSvc.userHome, '.dart-tool')); } } @@ -40,9 +48,12 @@ export async function preparePubCache( envSvc: EnvService, pathSvc: PathService, ): Promise { - const pubCache = join(pathSvc.cachePath, '.pub-cache'); + const pubCache = join(envSvc.userHome, '.pub-cache'); if (!(await pathExists(pubCache, true))) { - await pathSvc.createDir(pubCache); - await fs.symlink(pubCache, join(envSvc.userHome, '.pub-cache')); + await fs.symlink(join(pathSvc.cachePath, '.pub-cache'), pubCache); } } + +export async function initPubCache(pathSvc: PathService): Promise { + await pathSvc.createDir(join(pathSvc.cachePath, '.pub-cache')); +} diff --git a/src/cli/tools/docker.ts b/src/cli/tools/docker.ts index 2721a7ad84..041e612f88 100644 --- a/src/cli/tools/docker.ts +++ b/src/cli/tools/docker.ts @@ -15,7 +15,7 @@ import { export class DockerPrepareService extends BasePrepareService { readonly name = 'docker'; - async execute(): Promise { + override async prepare(): Promise { await execa('groupadd', ['-g', '999', 'docker']); await execa('usermod', ['-aG', 'docker', this.envSvc.userName]); } diff --git a/src/cli/tools/dotnet.ts b/src/cli/tools/dotnet.ts index 8a61bf996e..15bc42162c 100644 --- a/src/cli/tools/dotnet.ts +++ b/src/cli/tools/dotnet.ts @@ -25,7 +25,7 @@ export class DotnetPrepareService extends BasePrepareService { super(pathSvc, envSvc); } - async execute(): Promise { + override async prepare(): Promise { const distro = await getDistro(); switch (distro.versionCode) { @@ -64,17 +64,26 @@ export class DotnetPrepareService extends BasePrepareService { break; } - await this.pathSvc.exportToolEnv(this.name, { - DOTNET_ROOT: this.pathSvc.toolPath(this.name), - DOTNET_CLI_TELEMETRY_OPTOUT: '1', - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: '1', - }); + await this.initialize(); + await fs.symlink( + join(this.pathSvc.cachePath, '.nuget'), + join(this.envSvc.userHome, '.nuget'), + ); + } + + override async initialize(): Promise { + if (!(await this.pathSvc.toolEnvExists(this.name))) { + await this.pathSvc.exportToolEnv(this.name, { + DOTNET_ROOT: this.pathSvc.toolPath(this.name), + DOTNET_CLI_TELEMETRY_OPTOUT: '1', + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: '1', + }); + } - const nuget = join(this.envSvc.userHome, '.nuget'); - await fs.mkdir(join(nuget, 'NuGet'), { recursive: true }); - // fs isn't recursive, so we use system binaries - await execa('chown', ['-R', this.envSvc.userName, nuget]); - await execa('chmod', ['-R', 'g+w', nuget]); + const nuget = join(this.pathSvc.cachePath, '.nuget'); + if (!(await this.pathSvc.dirExists(nuget))) { + await this.pathSvc.createDir(join(nuget, 'NuGet')); + } } } diff --git a/src/cli/tools/flutter.ts b/src/cli/tools/flutter.ts index 891b8ea30d..cc6a404b55 100644 --- a/src/cli/tools/flutter.ts +++ b/src/cli/tools/flutter.ts @@ -12,14 +12,21 @@ import { PathService, } from '../services'; import { logger } from '../utils'; -import { prepareDartHome, preparePubCache } from './dart/utils'; +import { + initDartHome, + initPubCache, + prepareDartHome, + preparePubCache, +} from './dart/utils'; @injectable() export class FlutterPrepareService extends BasePrepareService { readonly name = 'flutter'; - override async execute(): Promise { + override async prepare(): Promise { + await this.initialize(); await prepareDartHome(this.envSvc, this.pathSvc); + await preparePubCache(this.envSvc, this.pathSvc); // for root await fs.writeFile( @@ -28,24 +35,31 @@ export class FlutterPrepareService extends BasePrepareService { ); // for user - const flutter = join(this.pathSvc.cachePath, '.flutter'); + await fs.symlink( + join(this.pathSvc.cachePath, '.flutter'), + join(this.envSvc.userHome, '.flutter'), + ); + + await fs.symlink( + join(this.pathSvc.cachePath, '.flutter_tool_state'), + join(this.envSvc.userHome, '.flutter_tool_state'), + ); + } + + override async initialize(): Promise { + await initDartHome(this.pathSvc); + await initPubCache(this.pathSvc); + + // for user await this.pathSvc.writeFile( - flutter, + join(this.pathSvc.cachePath, '.flutter'), '{ "firstRun": false, "enabled": false }\n', ); - await fs.symlink(flutter, join(this.envSvc.userHome, '.flutter')); - const futterToolState = join(this.pathSvc.cachePath, '.flutter_tool_state'); await this.pathSvc.writeFile( - futterToolState, + join(this.pathSvc.cachePath, '.flutter_tool_state'), '{ "is-bot": false, "redisplay-welcome-message": false }\n', ); - await fs.symlink( - futterToolState, - join(this.envSvc.userHome, '.flutter_tool_state'), - ); - - await preparePubCache(this.envSvc, this.pathSvc); } } diff --git a/src/cli/tools/index.ts b/src/cli/tools/index.ts index c7f5770edb..a9d72b333c 100644 --- a/src/cli/tools/index.ts +++ b/src/cli/tools/index.ts @@ -34,6 +34,8 @@ export const NoPrepareTools = [ 'yarn-slim', ]; +export const NoInitTools = [...NoPrepareTools]; + /** * Tools in this map are implicit mapped from `install-tool` to `install-`. * So no need for an extra install service. diff --git a/src/cli/tools/java/index.ts b/src/cli/tools/java/index.ts index e010bbf7f4..32684fcf54 100644 --- a/src/cli/tools/java/index.ts +++ b/src/cli/tools/java/index.ts @@ -32,7 +32,9 @@ export class JavaPrepareService extends BasePrepareService { super(pathSvc, envSvc); } - override async execute(): Promise { + override async prepare(): Promise { + await this.initialize(); + const cacerts = path.join(this.pathSvc.sslPath, 'cacerts'); if (await this.pathSvc.fileExists(cacerts)) { @@ -40,9 +42,6 @@ export class JavaPrepareService extends BasePrepareService { return; } - await createMavenSettings(this.pathSvc); - await createGradleSettings(this.pathSvc); - // compatibility with gradle and maven await fs.symlink( path.join(this.pathSvc.cachePath, '.m2'), @@ -53,13 +52,6 @@ export class JavaPrepareService extends BasePrepareService { path.join(this.envSvc.userHome, '.gradle'), ); - // fix: Failed to load native library 'libnative-platform.so' for Linux amd64. - await this.pathSvc.exportToolEnv( - 'gradle', - { GRADLE_USER_HOME: path.join(this.pathSvc.cachePath, '.gradle') }, - true, - ); - const version = await resolveLatestJavaLtsVersion( this.httpSvc, 'jre', @@ -103,6 +95,20 @@ export class JavaPrepareService extends BasePrepareService { // cleanup will be done by caller } + + override async initialize(): Promise { + await createMavenSettings(this.pathSvc); + await createGradleSettings(this.pathSvc); + + if (!(await this.pathSvc.toolEnvExists(this.name))) { + // fix: Failed to load native library 'libnative-platform.so' for Linux amd64. + await this.pathSvc.exportToolEnv( + 'gradle', + { GRADLE_USER_HOME: path.join(this.pathSvc.cachePath, '.gradle') }, + true, + ); + } + } } @injectable() diff --git a/src/cli/tools/java/utils.ts b/src/cli/tools/java/utils.ts index 04adefcbce..2c97700705 100644 --- a/src/cli/tools/java/utils.ts +++ b/src/cli/tools/java/utils.ts @@ -47,15 +47,15 @@ export async function resolveJavaDownloadUrl( export async function createMavenSettings(pathSvc: PathService): Promise { const dir = path.join(pathSvc.cachePath, '.m2'); + await pathSvc.createDir(dir); + const file = path.join(dir, 'settings.xml'); if (await pathExists(file)) { logger.debug('Maven settings already found'); return; } - logger.debug('Creating Maven settings'); - - await pathSvc.createDir(dir); + logger.debug('Creating Maven settings'); await pathSvc.writeFile( file, codeBlock` @@ -73,15 +73,15 @@ export async function createGradleSettings( pathSvc: PathService, ): Promise { const dir = path.join(pathSvc.cachePath, '.gradle'); + await pathSvc.createDir(dir); + const file = path.join(dir, 'gradle.properties'); if (await pathExists(file)) { logger.debug('Gradle settings already found'); return; } - logger.debug('Creating Gradle settings'); - - await pathSvc.createDir(dir); + logger.debug('Creating Gradle settings'); await pathSvc.writeFile( file, codeBlock` diff --git a/src/cli/tools/node/index.ts b/src/cli/tools/node/index.ts index 102566fb06..92c5f2da7a 100644 --- a/src/cli/tools/node/index.ts +++ b/src/cli/tools/node/index.ts @@ -22,16 +22,22 @@ import { @injectable() export class NodePrepareService extends BasePrepareService { override name = 'node'; - override async execute(): Promise { + override async prepare(): Promise { + await this.initialize(); + await prepareSymlinks(this.envSvc, this.pathSvc); + } + + override async initialize(): Promise { await prepareNpmCache(this.pathSvc); await prepareNpmrc(this.pathSvc); - await prepareSymlinks(this.envSvc, this.pathSvc); - await this.pathSvc.exportToolEnv(this.name, { - NO_UPDATE_NOTIFIER: '1', - npm_config_update_notifier: 'false', - npm_config_fund: 'false', - }); + if (!(await this.pathSvc.toolEnvExists(this.name))) { + await this.pathSvc.exportToolEnv(this.name, { + NO_UPDATE_NOTIFIER: '1', + npm_config_update_notifier: 'false', + npm_config_fund: 'false', + }); + } } } diff --git a/src/cli/tools/node/utils.ts b/src/cli/tools/node/utils.ts index 2ad6a5d0f1..2e50445f1c 100644 --- a/src/cli/tools/node/utils.ts +++ b/src/cli/tools/node/utils.ts @@ -281,9 +281,7 @@ async function readPackageJson(path: string): Promise { export async function prepareNpmCache(pathSvc: PathService): Promise { const path = join(pathSvc.cachePath, '.npm'); - if (!(await pathExists(path, true))) { - await pathSvc.createDir(path); - } + await pathSvc.createDir(path); } export async function prepareNpmrc(pathSvc: PathService): Promise { diff --git a/src/cli/tools/php/index.ts b/src/cli/tools/php/index.ts index cd69794e25..3d085f1a7f 100644 --- a/src/cli/tools/php/index.ts +++ b/src/cli/tools/php/index.ts @@ -27,7 +27,7 @@ export class PhpPrepareService extends BasePrepareService { super(pathSvc, envSvc); } - override async execute(): Promise { + override async prepare(): Promise { const distro = await getDistro(); switch (distro.versionCode) { diff --git a/src/cli/tools/python/conan.ts b/src/cli/tools/python/conan.ts index 39e436f502..ec3cb4a1f8 100644 --- a/src/cli/tools/python/conan.ts +++ b/src/cli/tools/python/conan.ts @@ -1,3 +1,4 @@ +import fs from 'node:fs/promises'; import { join } from 'node:path'; import { codeBlock } from 'common-tags'; import { inject, injectable } from 'inversify'; @@ -19,7 +20,18 @@ export class ConanPrepareService extends BasePrepareService { super(pathSvc, envSvc); } - override async execute(): Promise { + override async prepare(): Promise { + await this.aptSvc.install('cmake', 'gcc', 'g++', 'make', 'perl'); + + await this.initialize(); + + await fs.symlink( + join(this.pathSvc.cachePath, '.conan2'), + join(this.envSvc.userHome, '.conan2'), + ); + } + + override async initialize(): Promise { const distro = await getDistro(); const profile = codeBlock` [settings] @@ -32,10 +44,9 @@ export class ConanPrepareService extends BasePrepareService { os=Linux `; - const profilesPath = join(this.envSvc.userHome, '.conan2', 'profiles'); + const profilesPath = join(this.pathSvc.cachePath, '.conan2', 'profiles'); await this.pathSvc.createDir(profilesPath); await this.pathSvc.writeFile(join(profilesPath, 'default'), profile); - await this.aptSvc.install('cmake', 'gcc', 'g++', 'make', 'perl'); } } diff --git a/src/cli/utils/common.ts b/src/cli/utils/common.ts index a343ace9d6..15d633b12d 100644 --- a/src/cli/utils/common.ts +++ b/src/cli/utils/common.ts @@ -106,7 +106,7 @@ export async function cleanTmpFiles( tmp: string, dryRun = false, ): Promise { - await deleteAsync([`**`, `!containerbase`], { + await deleteAsync([`**`, `!containerbase/**`], { dot: true, dryRun, force: true, diff --git a/src/usr/local/containerbase/bin/init-tool.sh b/src/usr/local/containerbase/bin/init-tool.sh index 08ed0667a9..186abe3f79 100755 --- a/src/usr/local/containerbase/bin/init-tool.sh +++ b/src/usr/local/containerbase/bin/init-tool.sh @@ -2,26 +2,22 @@ set -e - # shellcheck source=/dev/null . /usr/local/containerbase/util.sh -TOOLS=( "$@" ) -TOOL_PATH="${CONTAINERBASE_DIR}/tools/v2" -# special case if only 'all' is given -if [ "$#" -eq 1 ] && [ "${TOOLS[0]}" == "all" ]; then - TOOLS=() - for i in "${TOOL_PATH}"/*.sh; do - TOOLS+=( "$(basename "${i%%.*}")") - done -fi +V2_TOOL="${CONTAINERBASE_DIR}/tools/v2/${TOOL_NAME}.sh" -for tool in "${TOOLS[@]}" -do - # TODO: find better way to reset env +if [[ -f "$V2_TOOL" ]]; then + # init v2 tool + # load overrides needed for v2 tools # shellcheck source=/dev/null - . "${CONTAINERBASE_DIR}/util.sh" - init_tools "${tool}" -done + . "${CONTAINERBASE_DIR}/utils/v2/overrides.sh" + # shellcheck source=/dev/null + . "${V2_TOOL}" + init_v2_tool +else + echo "No tool defined - skipping: ${TOOL_NAME}" >&2 + exit 1; +fi diff --git a/src/usr/local/containerbase/bin/install-tool.sh b/src/usr/local/containerbase/bin/install-tool.sh index 16d103d0e2..3753706b99 100755 --- a/src/usr/local/containerbase/bin/install-tool.sh +++ b/src/usr/local/containerbase/bin/install-tool.sh @@ -10,10 +10,6 @@ require_distro require_user require_tool "$@" -if [[ $(ignore_tool) -eq 1 ]]; then - echo "Tool ignored - skipping: ${TOOL_NAME}" - exit 0; -fi TOOL="${CONTAINERBASE_DIR}/tools/${TOOL_NAME}.sh" V2_TOOL="${CONTAINERBASE_DIR}/tools/v2/${TOOL_NAME}.sh" diff --git a/src/usr/local/containerbase/bin/prepare-tool.sh b/src/usr/local/containerbase/bin/prepare-tool.sh index 4b9d37279b..27c728220f 100755 --- a/src/usr/local/containerbase/bin/prepare-tool.sh +++ b/src/usr/local/containerbase/bin/prepare-tool.sh @@ -7,22 +7,4 @@ set -e . /usr/local/containerbase/util.sh require_root - -TOOLS=( "$@" ) -TOOL_PATH="${CONTAINERBASE_DIR}/tools/v2" - -# special case if only 'all' is given -if [ "$#" -eq 1 ] && [ "${TOOLS[0]}" == "all" ]; then - TOOLS=() - for i in "${TOOL_PATH}"/*.sh; do - TOOLS+=( "$(basename "${i%%.*}")") - done -fi - -for tool in "${TOOLS[@]}" -do - # TODO: find better way to reset env - # shellcheck source=/dev/null - . "${CONTAINERBASE_DIR}/util.sh" - prepare_tools "${tool}" -done +prepare_tools "${@}" diff --git a/src/usr/local/containerbase/tools/v2/golang.sh b/src/usr/local/containerbase/tools/v2/golang.sh index d0ee84693b..bb3efbdac7 100644 --- a/src/usr/local/containerbase/tools/v2/golang.sh +++ b/src/usr/local/containerbase/tools/v2/golang.sh @@ -3,17 +3,30 @@ export NEEDS_PREPARE=1 function prepare_tool() { + local go_path # go suggests: git svn bzr mercurial apt_install bzr mercurial + init_tool + go_path=$(get_cache_path)/go ln -sf "${go_path}" "${USER_HOME}/go" +} + +function init_tool() { + local go_path - mkdir -p "${go_path}/src" "${go_path}/bin" "${go_path}/pkg" + go_path=$(get_cache_path)/go + + if [ -d "${go_path}" ]; then + return + fi + create_folder "${go_path}/src" 775 + create_folder "${go_path}/bin" 775 + create_folder "${go_path}/pkg" 775 chown -R "${USER_ID}" "${go_path}" - chmod -R g+w "${go_path}" } function install_tool () { diff --git a/src/usr/local/containerbase/tools/v2/ruby.sh b/src/usr/local/containerbase/tools/v2/ruby.sh index 9f4c077364..30d5730653 100644 --- a/src/usr/local/containerbase/tools/v2/ruby.sh +++ b/src/usr/local/containerbase/tools/v2/ruby.sh @@ -4,7 +4,6 @@ export NEEDS_PREPARE=1 function prepare_tool() { local version_codename - local tool_path local path version_codename="$(get_distro)" @@ -23,42 +22,61 @@ function prepare_tool() { libyaml-0-2 \ make \ ; - tool_path=$(find_tool_path) + + init_tool # Redirect gemrc path="$(get_cache_path)/.gemrc" + ln -sf "${path}" "${USER_HOME}/.gemrc" + + # Redirect gem home + path="$(get_cache_path)/.gem" + ln -sf "${path}" "${USER_HOME}/.gem" + + # Redirect cocoapods home + path="$(get_cache_path)/.cocoapods" + ln -sf "${path}" "${USER_HOME}/.cocoapods" + + # Redirect Library home + path="$(get_cache_path)/Library" + ln -sf "${path}" "${USER_HOME}/Library" + + # Workaround for compatibillity for Ruby hardcoded paths + path=$(find_tool_path) + if [ "${path}" != "${ROOT_DIR_LEGACY}/ruby" ]; then + ln -sf "${path}" /usr/local/ruby + fi +} + +function init_tool () { + local path + path="$(get_cache_path)/.gemrc" + + if [ -f "${path}" ]; then + return + fi + + # Init gemrc { printf -- "gem: --no-document\n" } > "${path}" chown "${USER_ID}" "${path}" chmod g+w "${path}" - ln -sf "${path}" "${USER_HOME}/.gemrc" - # Redirect gem home + # Init gem home path="$(get_cache_path)/.gem" create_folder "${path}" 775 chown "${USER_ID}" "${path}" - chmod g+w "${path}" - ln -sf "${path}" "${USER_HOME}/.gem" - # Redirect cocoapods home + # Init cocoapods home path="$(get_cache_path)/.cocoapods" create_folder "${path}" 775 chown "${USER_ID}" "${path}" - chmod g+w "${path}" - ln -sf "${path}" "${USER_HOME}/.cocoapods" - # Redirect Library home + # Init Library home path="$(get_cache_path)/Library" create_folder "${path}" 775 chown "${USER_ID}" "${path}" - chmod g+w "${path}" - ln -sf "${path}" "${USER_HOME}/Library" - - # Workaround for compatibillity for Ruby hardcoded paths - if [ "${tool_path}" != "${ROOT_DIR_LEGACY}/ruby" ]; then - ln -sf "${tool_path}" /usr/local/ruby - fi } function install_tool () { diff --git a/src/usr/local/containerbase/tools/v2/rust.sh b/src/usr/local/containerbase/tools/v2/rust.sh index 8fc0a7b460..75e3cd7c7a 100644 --- a/src/usr/local/containerbase/tools/v2/rust.sh +++ b/src/usr/local/containerbase/tools/v2/rust.sh @@ -5,12 +5,24 @@ export NEEDS_PREPARE=1 function prepare_tool() { local cargo_home - cargo_home=$(get_cache_path)/.cargo + init_tool + cargo_home=$(get_cache_path)/.cargo ln -sf "${cargo_home}" "${USER_HOME}/.cargo" - mkdir "${cargo_home}" +} + +function init_tool () { + local cargo_home + + cargo_home=$(get_cache_path)/.cargo + + if [ -d "${cargo_home}" ]; then + # already initialized + return + fi + + create_folder "${cargo_home}" 775 chown -R "${USER_ID}" "${cargo_home}" - chmod -R g+w "${cargo_home}" } function install_tool () { diff --git a/src/usr/local/containerbase/utils/filesystem.sh b/src/usr/local/containerbase/utils/filesystem.sh index cb038cfd63..f552033838 100644 --- a/src/usr/local/containerbase/utils/filesystem.sh +++ b/src/usr/local/containerbase/utils/filesystem.sh @@ -214,8 +214,9 @@ function set_tool_init () { } function get_tool_init () { - if [[ -f "$(get_tool_init_path)/${TOOL_NAME}" ]]; then - echo "$(get_tool_init_path)/${TOOL_NAME}" + local tool=${1:-${TOOL_NAME}} + if [[ -f "$(get_tool_init_path)/${tool}" ]]; then + echo "$(get_tool_init_path)/${tool}" fi } diff --git a/src/usr/local/containerbase/utils/init.sh b/src/usr/local/containerbase/utils/init.sh index f5bfe4a727..30b49f1b98 100644 --- a/src/usr/local/containerbase/utils/init.sh +++ b/src/usr/local/containerbase/utils/init.sh @@ -1,34 +1,6 @@ #!/bin/bash -# Will install the tool in the given path according to the v2 tool spec -function init_tools () { - local TOOL_NAME - TOOL_NAME=${1} - check TOOL_NAME true - - if [[ $(ignore_tool) -eq 1 ]]; then - echo "Tool ignored - skipping: ${TOOL_NAME}" - return - fi - - TOOL="${CONTAINERBASE_DIR}/tools/v2/${TOOL_NAME}.sh" - - # load overrides needed for v2 tools - # shellcheck source=/dev/null - . "${CONTAINERBASE_DIR}/utils/v2/overrides.sh" - - if [[ -f "$TOOL" ]]; then - # shellcheck source=/dev/null - . "${TOOL}" - else - echo "tool ${TOOL_NAME} does not exist" - exit 1 - fi - - init_tool_wrapper -} - -function init_tool_wrapper () { +function init_v2_tool () { if [[ -f "$(get_tool_init)" ]]; then # tool already initialized diff --git a/src/usr/local/containerbase/utils/install.sh b/src/usr/local/containerbase/utils/install.sh index 8c35c83002..74b18dbd4f 100644 --- a/src/usr/local/containerbase/utils/install.sh +++ b/src/usr/local/containerbase/utils/install.sh @@ -21,7 +21,7 @@ function install_v2_tool () { fi # init tool if required - init_tool_wrapper + init_v2_tool check_tool_requirements diff --git a/src/usr/local/containerbase/utils/linking.sh b/src/usr/local/containerbase/utils/linking.sh index b480b0e1ef..3e43d6ba84 100644 --- a/src/usr/local/containerbase/utils/linking.sh +++ b/src/usr/local/containerbase/utils/linking.sh @@ -7,6 +7,7 @@ function shell_wrapper () { local EXPORTS=$3 local args=$4 local content=$5 + local tool_init TARGET="$(get_bin_path)/${1}" if [[ -z "$SOURCE" ]]; then SOURCE=$(command -v "${1}") @@ -17,12 +18,18 @@ function shell_wrapper () { check SOURCE true check_command "$SOURCE" + tool_init=$(get_tool_init "${TOOL_NAME}") + cat > "$TARGET" <<- EOM #!/bin/bash if [[ -z "\${CONTAINERBASE_ENV+x}" ]]; then . $ENV_FILE fi +if [[ ! -f "${tool_init}" ]]; then + # set logging to only warn and above to not interfere with tool output + CONTAINERBASE_LOG_LEVEL=warn containerbase-cli init tool "${TOOL_NAME}" +fi EOM if [ -n "$EXPORTS" ]; then diff --git a/src/usr/local/containerbase/utils/prepare.sh b/src/usr/local/containerbase/utils/prepare.sh index 17e495d0bd..3f437ae589 100644 --- a/src/usr/local/containerbase/utils/prepare.sh +++ b/src/usr/local/containerbase/utils/prepare.sh @@ -49,5 +49,5 @@ function prepare_tool_wrapper () { set_tool_prep # init tool - init_tool_wrapper + init_v2_tool } diff --git a/test/node/Dockerfile b/test/node/Dockerfile index 3e7777b1df..00f66775eb 100644 --- a/test/node/Dockerfile +++ b/test/node/Dockerfile @@ -466,12 +466,29 @@ RUN set -ex; \ yarn -v | grep ${YARN_SLIM_VERSION}; #-------------------------------------- -# test: renovate +# test: renovate (rootless,readonly) #-------------------------------------- FROM base AS testq +RUN set -ex; \ + ls -la /opt/containerbase; \ + ls -la /tmp/containerbase; \ + ls -la /tmp/containerbase/cache; \ + true;true + RUN prepare-tool node +# emulate emtpy containerbase folders +RUN set -ex; \ + ls -la /tmp/containerbase/cache; \ + rm -rf /opt/containerbase/*; \ + ls -la /opt/containerbase/; \ + rm -rf /tmp/*; \ + ls -la /tmp/; \ + true + +USER 1000 + # install latest version RUN install-tool node RUN install-tool yarn @@ -500,20 +517,20 @@ RUN set -ex; \ #-------------------------------------- FROM base -COPY --from=testa /.dummy /.dummy -COPY --from=testb /.dummy /.dummy -COPY --from=testc /.dummy /.dummy -COPY --from=testd /.dummy /.dummy -COPY --from=teste /.dummy /.dummy -COPY --from=testf /.dummy /.dummy -COPY --from=testg /.dummy /.dummy -COPY --from=testh /.dummy /.dummy -COPY --from=testi /.dummy /.dummy -COPY --from=testj /.dummy /.dummy -COPY --from=testk /.dummy /.dummy -COPY --from=testl /.dummy /.dummy -COPY --from=testm /.dummy /.dummy -COPY --from=testn /.dummy /.dummy -COPY --from=testo /.dummy /.dummy -COPY --from=testp /.dummy /.dummy +# COPY --from=testa /.dummy /.dummy +# COPY --from=testb /.dummy /.dummy +# COPY --from=testc /.dummy /.dummy +# COPY --from=testd /.dummy /.dummy +# COPY --from=teste /.dummy /.dummy +# COPY --from=testf /.dummy /.dummy +# COPY --from=testg /.dummy /.dummy +# COPY --from=testh /.dummy /.dummy +# COPY --from=testi /.dummy /.dummy +# COPY --from=testj /.dummy /.dummy +# COPY --from=testk /.dummy /.dummy +# COPY --from=testl /.dummy /.dummy +# COPY --from=testm /.dummy /.dummy +# COPY --from=testn /.dummy /.dummy +# COPY --from=testo /.dummy /.dummy +# COPY --from=testp /.dummy /.dummy COPY --from=testq /.dummy /.dummy