diff --git a/src/cli/services/path.service.spec.ts b/src/cli/services/path.service.spec.ts index 46047a84ef..3517944e47 100644 --- a/src/cli/services/path.service.spec.ts +++ b/src/cli/services/path.service.spec.ts @@ -1,5 +1,6 @@ import fs, { mkdir, readFile, stat, writeFile } from 'node:fs/promises'; import { platform } from 'node:os'; +import { join } from 'node:path'; import { env } from 'node:process'; import { deleteAsync } from 'del'; import { Container } from 'inversify'; @@ -145,6 +146,27 @@ describe('cli/services/path.service', () => { ); }); + test('createVersionedToolPath with sub folders', async () => { + await ensurePaths('opt/containerbase/tools'); + + const path = await pathSvc.createVersionedToolPath( + 'jb', + '0.6.0', + 'lib', + 'bin', + ); + + expect(path).toBe(rootPath('opt/containerbase/tools/jb/0.6.0/lib/bin')); + // tests don't run as root, so the umask is group writable + const mode = platform() === 'win32' ? 0 : 0o775; + expect((await stat(path)).mode & fileRights).toBe(mode); + expect((await stat(join(path, '..'))).mode & fileRights).toBe(mode); + // an existing folder is fine + await expect( + pathSvc.createVersionedToolPath('jb', '0.6.0', 'lib', 'bin'), + ).resolves.toBe(path); + }); + test('exportEnv', async () => { await mkdir(rootPath('usr/local/etc'), { recursive: true }); await pathSvc.exportEnv({ NODE_VERSION: 'v14.17.1' }); diff --git a/src/cli/services/path.service.ts b/src/cli/services/path.service.ts index 8909843c10..140f1ebbbe 100644 --- a/src/cli/services/path.service.ts +++ b/src/cli/services/path.service.ts @@ -133,14 +133,23 @@ export class PathService { return toolPath; } - /** Creates the versioned tool path with the configured umask and returns it. */ + /** + * Creates the versioned tool path and the optional sub folders below it, + * eg. `bin`, all owned by the configured user with the configured umask. + * Returns the innermost path. + */ async createVersionedToolPath( tool: string, version: string, + ...subPaths: string[] ): Promise { - const toolPath = this.versionedToolPath(tool, version); - await this.createDir(toolPath, this.envSvc.umask); - return toolPath; + let path = this.versionedToolPath(tool, version); + await this.createDir(path, this.envSvc.umask); + for (const sub of subPaths) { + path = join(path, sub); + await this.createDir(path, this.envSvc.umask); + } + return path; } /** diff --git a/src/cli/tools/apko.ts b/src/cli/tools/apko.ts index 506d0083a3..1c861848c3 100644 --- a/src/cli/tools/apko.ts +++ b/src/cli/tools/apko.ts @@ -1,4 +1,3 @@ -import fs from 'node:fs/promises'; import { join } from 'node:path'; import { injectFromHierarchy, injectable } from 'inversify'; import { BaseInstallService } from '../install-tool/base-install.service.ts'; @@ -44,11 +43,11 @@ export class ApkoInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); await this.compress.extract({ file, cwd: path, diff --git a/src/cli/tools/bazelisk.ts b/src/cli/tools/bazelisk.ts index f4185424d9..1d1dba7376 100644 --- a/src/cli/tools/bazelisk.ts +++ b/src/cli/tools/bazelisk.ts @@ -22,11 +22,11 @@ export class BazeliskInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); const binarypath = join(path, 'bazelisk'); await fs.copyFile(file, binarypath); diff --git a/src/cli/tools/bun.ts b/src/cli/tools/bun.ts index 88b03f72c4..f17c37b6b4 100644 --- a/src/cli/tools/bun.ts +++ b/src/cli/tools/bun.ts @@ -53,11 +53,11 @@ export class BunInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); await this.compress.extract({ file, cwd: path, diff --git a/src/cli/tools/deno.ts b/src/cli/tools/deno.ts index e4122afc93..342a6503e6 100644 --- a/src/cli/tools/deno.ts +++ b/src/cli/tools/deno.ts @@ -1,4 +1,3 @@ -import fs from 'node:fs/promises'; import { join } from 'node:path'; import { injectFromHierarchy, injectable } from 'inversify'; import { BaseInstallService } from '../install-tool/base-install.service.ts'; @@ -42,11 +41,11 @@ export class DenoInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); await this.compress.extract({ file, diff --git a/src/cli/tools/devbox.ts b/src/cli/tools/devbox.ts index 4396934380..f1b5709c99 100644 --- a/src/cli/tools/devbox.ts +++ b/src/cli/tools/devbox.ts @@ -1,4 +1,3 @@ -import fs from 'node:fs/promises'; import { join } from 'node:path'; import { injectFromHierarchy, injectable } from 'inversify'; import { BaseInstallService } from '../install-tool/base-install.service.ts'; @@ -29,11 +28,11 @@ export class DevboxInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); await this.compress.extract({ file, cwd: path, diff --git a/src/cli/tools/docker/buildx.ts b/src/cli/tools/docker/buildx.ts index 97e00e6036..33cfd60f0f 100644 --- a/src/cli/tools/docker/buildx.ts +++ b/src/cli/tools/docker/buildx.ts @@ -35,11 +35,11 @@ export class BuildxInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); const bin = join(path, this.name); await fs.copyFile(file, bin); await fs.chmod(bin, this.envSvc.umask); diff --git a/src/cli/tools/docker/compose.ts b/src/cli/tools/docker/compose.ts index 84af63b1a7..104ade25bc 100644 --- a/src/cli/tools/docker/compose.ts +++ b/src/cli/tools/docker/compose.ts @@ -44,11 +44,11 @@ export class DockerComposeInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); const bin = join(path, this.name); await fs.copyFile(file, bin); await fs.chmod(bin, this.envSvc.umask); diff --git a/src/cli/tools/docker/index.ts b/src/cli/tools/docker/index.ts index 9f8f9aa17f..9dce097254 100644 --- a/src/cli/tools/docker/index.ts +++ b/src/cli/tools/docker/index.ts @@ -59,11 +59,11 @@ export class DockerInstallService extends BaseInstallService { const url = `https://download.docker.com/linux/static/stable/${this.arch}/docker-${version}.tgz`; const file = await this.http.download({ url }); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); await this.compress.extract({ file, cwd: path, diff --git a/src/cli/tools/dotnet/nuget.ts b/src/cli/tools/dotnet/nuget.ts index c94b44be8d..38cd243678 100644 --- a/src/cli/tools/dotnet/nuget.ts +++ b/src/cli/tools/dotnet/nuget.ts @@ -27,11 +27,11 @@ export class NugetInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); const binary = join(path, filename); await fs.copyFile(file, binary); // create shell wrapper to be able to execute it with mono diff --git a/src/cli/tools/flux.ts b/src/cli/tools/flux.ts index 4979169645..46074ce087 100644 --- a/src/cli/tools/flux.ts +++ b/src/cli/tools/flux.ts @@ -1,4 +1,3 @@ -import fs from 'node:fs/promises'; import { join } from 'node:path'; import { injectFromHierarchy, injectable } from 'inversify'; import { BaseInstallService } from '../install-tool/base-install.service.ts'; @@ -34,11 +33,11 @@ export class FluxInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); await this.compress.extract({ file, cwd: path, diff --git a/src/cli/tools/haskell/cabal.ts b/src/cli/tools/haskell/cabal.ts index 736511632f..f9fdc1e9ac 100644 --- a/src/cli/tools/haskell/cabal.ts +++ b/src/cli/tools/haskell/cabal.ts @@ -1,4 +1,3 @@ -import fs from 'node:fs/promises'; import { join } from 'node:path'; import { injectFromHierarchy, injectable } from 'inversify'; import { BaseInstallService } from '../../install-tool/base-install.service.ts'; @@ -49,11 +48,11 @@ export class CabalInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); await this.compress.extract({ file, cwd: path, diff --git a/src/cli/tools/helm.ts b/src/cli/tools/helm.ts index aab710ad07..15c4e8912b 100644 --- a/src/cli/tools/helm.ts +++ b/src/cli/tools/helm.ts @@ -1,4 +1,3 @@ -import fs from 'node:fs/promises'; import path from 'node:path'; import { injectFromHierarchy, injectable } from 'inversify'; import { BaseInstallService } from '../install-tool/base-install.service.ts'; @@ -24,11 +23,11 @@ export class HelmInstallService extends BaseInstallService { expectedChecksum, }); await this.pathSvc.ensureToolPath(this.name); - const cwd = path.join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const cwd = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(cwd); await this.compress.extract({ file, cwd, strip: 1 }); } diff --git a/src/cli/tools/helmfile.ts b/src/cli/tools/helmfile.ts index 5b1474159f..eac51a16a8 100644 --- a/src/cli/tools/helmfile.ts +++ b/src/cli/tools/helmfile.ts @@ -1,4 +1,3 @@ -import fs from 'node:fs/promises'; import path from 'node:path'; import { injectFromHierarchy, injectable } from 'inversify'; import { BaseInstallService } from '../install-tool/base-install.service.ts'; @@ -27,11 +26,11 @@ export class HelmfileInstallService extends BaseInstallService { expectedChecksum, }); await this.pathSvc.ensureToolPath(this.name); - const cwd = path.join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const cwd = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(cwd); await this.compress.extract({ file, cwd }); } diff --git a/src/cli/tools/jb.ts b/src/cli/tools/jb.ts index e69b8bc253..087713ca10 100644 --- a/src/cli/tools/jb.ts +++ b/src/cli/tools/jb.ts @@ -19,11 +19,11 @@ export class JsonnetBundlerInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); const target = join(path, this.name); await fs.copyFile(file, target); await fs.chmod(target, this.envSvc.umask); diff --git a/src/cli/tools/kubectl.ts b/src/cli/tools/kubectl.ts index b25c29fa0a..f31dec923c 100644 --- a/src/cli/tools/kubectl.ts +++ b/src/cli/tools/kubectl.ts @@ -29,11 +29,11 @@ export class KubectlInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); await fs.copyFile(file, join(path, filename)); await fs.chmod(join(path, filename), this.envSvc.umask); } diff --git a/src/cli/tools/kustomize.ts b/src/cli/tools/kustomize.ts index 20a4142d9a..a9ad3ffb4b 100644 --- a/src/cli/tools/kustomize.ts +++ b/src/cli/tools/kustomize.ts @@ -1,4 +1,3 @@ -import fs from 'node:fs/promises'; import path from 'node:path'; import { injectFromHierarchy, injectable } from 'inversify'; import { BaseInstallService } from '../install-tool/base-install.service.ts'; @@ -29,11 +28,11 @@ export class KustomizeInstallService extends BaseInstallService { expectedChecksum, }); await this.pathSvc.ensureToolPath(this.name); - const cwd = path.join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const cwd = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(cwd); await this.compress.extract({ file, cwd }); } diff --git a/src/cli/tools/pixi.ts b/src/cli/tools/pixi.ts index 6ddc3aa6eb..eb0c2f91c0 100644 --- a/src/cli/tools/pixi.ts +++ b/src/cli/tools/pixi.ts @@ -1,4 +1,3 @@ -import fs from 'node:fs/promises'; import { join } from 'node:path'; import { injectFromHierarchy, injectable } from 'inversify'; import { BaseInstallService } from '../install-tool/base-install.service.ts'; @@ -36,11 +35,11 @@ export class PixiInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); await this.compress.extract({ file, cwd: path, diff --git a/src/cli/tools/python/utils.ts b/src/cli/tools/python/utils.ts index ddd369685f..0af252859a 100644 --- a/src/cli/tools/python/utils.ts +++ b/src/cli/tools/python/utils.ts @@ -56,7 +56,7 @@ export abstract class PipBaseInstallService extends PythonBaseInstallService { } prefix = path.join(prefix, pythonVersion); - await fs.mkdir(prefix); + await this.pathSvc.createDir(prefix); await this.createVirtualenv(prefix, env); await this.installPackage(version, pythonVersion, env, prefix); } diff --git a/src/cli/tools/ruby/utils.ts b/src/cli/tools/ruby/utils.ts index 5c4d76669f..4c38d0b5c9 100644 --- a/src/cli/tools/ruby/utils.ts +++ b/src/cli/tools/ruby/utils.ts @@ -1,4 +1,4 @@ -import { chmod, mkdir, readFile, rm } from 'node:fs/promises'; +import { chmod, readFile, rm } from 'node:fs/promises'; import { join } from 'node:path'; import { isNonEmptyStringAndNotWhitespace } from '@sindresorhus/is'; import { execa } from 'execa'; @@ -49,7 +49,7 @@ export abstract class RubyBaseInstallService extends BaseInstallService { } prefix = join(prefix, ruby); - await mkdir(prefix); + await this.pathSvc.createDir(prefix); const res = await execa( gem, diff --git a/src/cli/tools/sops.ts b/src/cli/tools/sops.ts index 86ed113d6b..808e348585 100644 --- a/src/cli/tools/sops.ts +++ b/src/cli/tools/sops.ts @@ -29,11 +29,11 @@ export class SopsInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); await fs.copyFile(file, join(path, this.name)); await fs.chmod(join(path, this.name), this.envSvc.umask); } diff --git a/src/cli/tools/terraform.ts b/src/cli/tools/terraform.ts index 5230a6f34a..e32a364a53 100644 --- a/src/cli/tools/terraform.ts +++ b/src/cli/tools/terraform.ts @@ -1,4 +1,3 @@ -import fs from 'node:fs/promises'; import { join } from 'node:path'; import { injectFromHierarchy, injectable } from 'inversify'; import { BaseInstallService } from '../install-tool/base-install.service.ts'; @@ -29,11 +28,11 @@ export class TerraformInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); await this.compress.extract({ file, cwd: path }); } diff --git a/src/cli/tools/tofu.ts b/src/cli/tools/tofu.ts index 4e3060166b..ef1221631e 100644 --- a/src/cli/tools/tofu.ts +++ b/src/cli/tools/tofu.ts @@ -1,4 +1,3 @@ -import fs from 'node:fs/promises'; import { join } from 'node:path'; import { injectFromHierarchy, injectable } from 'inversify'; import { BaseInstallService } from '../install-tool/base-install.service.ts'; @@ -40,11 +39,11 @@ export class TofuInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); await this.compress.extract({ file, diff --git a/src/cli/tools/vendir.ts b/src/cli/tools/vendir.ts index c103815f7f..9e2c4712c3 100644 --- a/src/cli/tools/vendir.ts +++ b/src/cli/tools/vendir.ts @@ -36,11 +36,11 @@ export class VendirInstallService extends BaseInstallService { await this.pathSvc.ensureToolPath(this.name); - const path = join( - await this.pathSvc.createVersionedToolPath(this.name, version), + const path = await this.pathSvc.createVersionedToolPath( + this.name, + version, 'bin', ); - await fs.mkdir(path); const target = join(path, this.name); await fs.copyFile(file, target); await fs.chmod(target, this.envSvc.umask);