From a713e3fc687081cb3197e843529ae094916e0823 Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Tue, 18 Jul 2023 10:15:50 +0200 Subject: [PATCH 1/8] feat: support url rewrite --- src/cli/command/download-file.ts | 53 ++++++++++++++++++++++ src/cli/command/index.ts | 2 + src/cli/command/utils.ts | 10 +++- src/cli/services/env.service.ts | 13 ++++++ src/cli/services/http.service.ts | 15 +----- src/cli/utils/index.ts | 2 +- src/usr/local/containerbase/util.sh | 3 ++ src/usr/local/containerbase/utils/cache.sh | 5 +- 8 files changed, 84 insertions(+), 19 deletions(-) create mode 100644 src/cli/command/download-file.ts diff --git a/src/cli/command/download-file.ts b/src/cli/command/download-file.ts new file mode 100644 index 0000000000..ba4de7ca9c --- /dev/null +++ b/src/cli/command/download-file.ts @@ -0,0 +1,53 @@ +import { createWriteStream } from 'node:fs'; +import { mkdir } from 'node:fs/promises'; +import { dirname } from 'node:path'; +import { pipeline } from 'node:stream/promises'; +import { Command, Option } from 'clipanion'; +import { got } from 'got'; +import prettyMilliseconds from 'pretty-ms'; +import { EnvService, rootContainer } from '../services'; +import { logger } from '../utils'; + +export class DownloadFileCommand extends Command { + static override paths = [['download', 'file'], ['df']]; + + static override usage = Command.Usage({ + description: 'Downloads a file and optionally validates the checksum.', + }); + + url = Option.String({ required: true }); + output = Option.String({ required: true }); + + // checksum = Option.String('-c,--checksum'); + // algo = Option.String('-a,--algo'); + + // dryRun = Option.Boolean('-d,--dry-run', false); + + async execute(): Promise { + const start = Date.now(); + let error = false; + logger.info({ url: this.url }, `Downloading file ...`); + try { + const container = rootContainer.createChild(); + + const env = container.get(EnvService); + const path = dirname(this.output); + await mkdir(path, { recursive: true }); + + const nUrl = env.replaceUrl(this.url); + await pipeline(got.stream(nUrl), createWriteStream(this.output)); + + return 0; + } catch (err) { + logger.fatal(err); + error = true; + return 1; + } finally { + logger.info( + `Download completed ${ + error ? 'with errors ' : '' + } in ${prettyMilliseconds(Date.now() - start)}.` + ); + } + } +} diff --git a/src/cli/command/index.ts b/src/cli/command/index.ts index 10c2ab117c..cec7525e92 100644 --- a/src/cli/command/index.ts +++ b/src/cli/command/index.ts @@ -2,6 +2,7 @@ import { argv0 } from 'node:process'; import type { Cli } from 'clipanion'; import type { CliMode } from '../utils'; import { logger } from '../utils/logger'; +import { DownloadFileCommand } from './download-file'; import { InstallToolCommand, InstallToolShortCommand } from './install-tool'; import { PrepareToolCommand, PrepareToolShortCommand } from './prepare-tool'; import { prepareToolVersion } from './utils'; @@ -28,6 +29,7 @@ export function prepareCommands( cli.register(InstallToolCommand); cli.register(PrepareToolCommand); + cli.register(DownloadFileCommand); return prepareToolVersion(mode, args); } diff --git a/src/cli/command/utils.ts b/src/cli/command/utils.ts index c0c71bbffa..c56ec526bc 100644 --- a/src/cli/command/utils.ts +++ b/src/cli/command/utils.ts @@ -5,6 +5,8 @@ export function prepareToolVersion( args: string[] ): string[] { switch (mode) { + case 'prepare-tool': + break; case 'install-tool': { if (args.length === 1) { // install-tool node @@ -14,10 +16,14 @@ export function prepareToolVersion( } case 'containerbase-cli': default: { - if (args.length === 2) { + if (args.length === 2 && args[0] === 'it') { // containerbase-cli it node appendVersion(args, 1); - } else if (args.length === 3) { + } else if ( + args.length === 3 && + args[0] === 'install' && + args[1] === 'tool' + ) { // containerbase-cli install tool node appendVersion(args, 2); } diff --git a/src/cli/services/env.service.ts b/src/cli/services/env.service.ts index b90bd560e9..e92e55e356 100644 --- a/src/cli/services/env.service.ts +++ b/src/cli/services/env.service.ts @@ -90,4 +90,17 @@ export class EnvService { return (this.replacements = replacements); } + + public replaceUrl(src: string): string { + let tgt = src; + const replacements = this.urlReplacements; + + for (const [from, to] of replacements) { + tgt = tgt.replace(from, to); + } + if (tgt !== src) { + logger.debug({ src, tgt }, 'url replaced'); + } + return tgt; + } } diff --git a/src/cli/services/http.service.ts b/src/cli/services/http.service.ts index ddc2401a1f..151c11ab24 100644 --- a/src/cli/services/http.service.ts +++ b/src/cli/services/http.service.ts @@ -65,7 +65,7 @@ export class HttpService { await mkdir(cachePath, { recursive: true }); - const nUrl = this.replaceUrl(url); + const nUrl = this.envSvc.replaceUrl(url); for (const run of [1, 2, 3]) { try { @@ -95,17 +95,4 @@ export class HttpService { await rm(cachePath, { recursive: true }); throw new Error('download failed'); } - - private replaceUrl(src: string): string { - let tgt = src; - const replacements = this.envSvc.urlReplacements; - - for (const [from, to] of replacements) { - tgt = tgt.replace(from, to); - } - if (tgt !== src) { - logger.debug({ src, tgt }, 'url replaced'); - } - return tgt; - } } diff --git a/src/cli/utils/index.ts b/src/cli/utils/index.ts index a7c94863f1..f8549cdb7b 100644 --- a/src/cli/utils/index.ts +++ b/src/cli/utils/index.ts @@ -13,7 +13,7 @@ export function cliMode(): CliMode | null { if (argv0.endsWith('/prepare-tool') || argv0 === 'prepare-tool') { return 'prepare-tool'; } - if (argv0.endsWith('/install-tool') || argv0 === 'install-tool') { + if (argv0.endsWith('/containerbase-cli') || argv0 === 'containerbase-cli') { return 'containerbase-cli'; } diff --git a/src/usr/local/containerbase/util.sh b/src/usr/local/containerbase/util.sh index 39527e59d1..50fb45eb62 100644 --- a/src/usr/local/containerbase/util.sh +++ b/src/usr/local/containerbase/util.sh @@ -1,5 +1,8 @@ #!/bin/bash +# https://github.com/vercel/pkg/issues/1861 +unset PKG_EXECPATH + # get path location DIR="${BASH_SOURCE%/*}" if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi diff --git a/src/usr/local/containerbase/utils/cache.sh b/src/usr/local/containerbase/utils/cache.sh index 1aa21de3c8..f85cbe3d02 100644 --- a/src/usr/local/containerbase/utils/cache.sh +++ b/src/usr/local/containerbase/utils/cache.sh @@ -90,8 +90,9 @@ function download_file () { local temp_folder=${CONTAINERBASE_CACHE_DIR:-${TEMP_DIR}} while [ "${retry}" -gt 0 ]; do retry=$((retry-1)) - if ! curl --retry 3 --create-dirs -sSfLo "${temp_folder}/${name}" "${url}" ; then + if ! containerbase-cli df "${url}" "${temp_folder}/${name}" >&2; then echo "Download failed: ${url}" >&2 + env >&2 exit 1 fi; @@ -119,7 +120,7 @@ function download_file () { # First argument is the path to the file # Second argument is the checksum # Third argument is the type, currently supported: -# * sha1 +# * sha1sum # * sha224sum # * sha256sum # * sha384sum From d213f9a40c1d9f64299804f3bbbd0b9727f78264 Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Tue, 18 Jul 2023 10:25:16 +0200 Subject: [PATCH 2/8] Update src/usr/local/containerbase/utils/cache.sh --- src/usr/local/containerbase/utils/cache.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/src/usr/local/containerbase/utils/cache.sh b/src/usr/local/containerbase/utils/cache.sh index f85cbe3d02..ade6737059 100644 --- a/src/usr/local/containerbase/utils/cache.sh +++ b/src/usr/local/containerbase/utils/cache.sh @@ -92,7 +92,6 @@ function download_file () { retry=$((retry-1)) if ! containerbase-cli df "${url}" "${temp_folder}/${name}" >&2; then echo "Download failed: ${url}" >&2 - env >&2 exit 1 fi; From 055f6e9157560fb01908607319319d517f68770f Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Tue, 18 Jul 2023 11:19:24 +0200 Subject: [PATCH 3/8] tes: fix bats tests --- .github/workflows/build.yml | 3 +++ src/cli/command/download-file.ts | 6 ++++-- test/bash/cache.bats | 17 +++++++++-------- test/bash/util.sh | 12 ++++++++++++ 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 08f151c22a..acb6eb22b7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,6 +86,9 @@ jobs: with: node-version: ${{ env.NODE_VERSION }} + - name: build + run: yarn build + - name: bats run: yarn test:bats diff --git a/src/cli/command/download-file.ts b/src/cli/command/download-file.ts index ba4de7ca9c..24aaca0fbf 100644 --- a/src/cli/command/download-file.ts +++ b/src/cli/command/download-file.ts @@ -1,5 +1,5 @@ import { createWriteStream } from 'node:fs'; -import { mkdir } from 'node:fs/promises'; +import { mkdir, rm } from 'node:fs/promises'; import { dirname } from 'node:path'; import { pipeline } from 'node:stream/promises'; import { Command, Option } from 'clipanion'; @@ -26,12 +26,14 @@ export class DownloadFileCommand extends Command { async execute(): Promise { const start = Date.now(); let error = false; - logger.info({ url: this.url }, `Downloading file ...`); + logger.info({ url: this.url, output: this.output }, `Downloading file ...`); try { const container = rootContainer.createChild(); const env = container.get(EnvService); const path = dirname(this.output); + + await rm(this.output, { force: true }); await mkdir(path, { recursive: true }); const nUrl = env.replaceUrl(this.url); diff --git a/test/bash/cache.bats b/test/bash/cache.bats index 61a0abfe36..9c98aaafe2 100644 --- a/test/bash/cache.bats +++ b/test/bash/cache.bats @@ -156,11 +156,11 @@ teardown() { run download_file "${file}" assert_success - assert_output "${CONTAINERBASE_CACHE_DIR}/containerbase.tar.xz" + assert_line "${CONTAINERBASE_CACHE_DIR}/containerbase.tar.xz" run download_file "${file}" "foobar" assert_success - assert_output "${CONTAINERBASE_CACHE_DIR}/foobar" + assert_line "${CONTAINERBASE_CACHE_DIR}/foobar" CONTAINERBASE_CACHE_DIR= \ tmp_file=$(download_file "${file}") @@ -178,11 +178,11 @@ teardown() { run get_from_url "${file}" assert_success - assert_output --regexp "^${CONTAINERBASE_CACHE_DIR}/[0-9a-f]{64}/containerbase\.tar\.xz" + assert_line --regexp "^${CONTAINERBASE_CACHE_DIR}/[0-9a-f]{64}/containerbase\.tar\.xz" run get_from_url "${file}" test assert_success - assert_output --regexp "${CONTAINERBASE_CACHE_DIR}/[0-9a-f]{64}/test" + assert_line --regexp "${CONTAINERBASE_CACHE_DIR}/[0-9a-f]{64}/test" # overwrite donwload function to fail function download_file () { @@ -209,13 +209,13 @@ teardown() { run get_from_url "${file}" $(basename "${file}") "${checksum}" "sha512sum" assert_success - assert_output --regexp "^${CONTAINERBASE_CACHE_DIR}/[0-9a-f]{64}/containerbase\.tar\.xz" + assert_line --regexp "^${CONTAINERBASE_CACHE_DIR}/[0-9a-f]{64}/containerbase\.tar\.xz" rm -rf "${CONTAINERBASE_CACHE_DIR}" run get_from_url "${file}" test "${checksum}" "sha512sum" assert_success - assert_output --regexp "${CONTAINERBASE_CACHE_DIR}/[0-9a-f]{64}/test" + assert_line --regexp "${CONTAINERBASE_CACHE_DIR}/[0-9a-f]{64}/test" rm -rf "${CONTAINERBASE_CACHE_DIR}" @@ -236,6 +236,7 @@ teardown() { } @test "get_from_url_with_cache_and_checksum" { + bats_require_minimum_version 1.5.0 # create cache dir CONTAINERBASE_CACHE_DIR="${TEST_ROOT_DIR}/cache" mkdir -p "${CONTAINERBASE_CACHE_DIR}" @@ -244,7 +245,7 @@ teardown() { local checksum="233c335a7f10e9f0dfd7e9d0cda802a38c15a7f13b6678c55980814f22799a70590d56888a819b6591881ec1939240d9dbe68e7e495021b4d6c6a49cdee24d80" local file="https://github.com/containerbase/base/releases/download/7.10.0/containerbase.tar.xz" - run get_from_url "${file}" $(basename "${file}") "${checksum}" "sha512sum" + run --separate-stderr get_from_url "${file}" $(basename "${file}") "${checksum}" "sha512sum" assert_success assert_output --regexp "^${CONTAINERBASE_CACHE_DIR}/[0-9a-f]{64}/containerbase\.tar\.xz" @@ -252,7 +253,7 @@ teardown() { run get_from_url "${file}" test "${checksum}" "sha512sum" assert_success - assert_output --regexp "${CONTAINERBASE_CACHE_DIR}/[0-9a-f]{64}/test" + assert_line --regexp "${CONTAINERBASE_CACHE_DIR}/[0-9a-f]{64}/test" # change checksum of cached file echo "a" >> "${file_path}" diff --git a/test/bash/util.sh b/test/bash/util.sh index b8110d0f95..20e596ab7f 100644 --- a/test/bash/util.sh +++ b/test/bash/util.sh @@ -19,3 +19,15 @@ function is_root () { echo 0 fi } + +function link_cli_tool () { + local arch=x64 + + if [[ "${ARCHITECTURE}" = "aarch64" ]];then + arch=arm64 + fi + mkdir -p "${BIN_DIR}" + export PATH="${BIN_DIR}:${PATH}" + ln -sf "${CONTAINERBASE_DIR}/bin/containerbase-cli-${arch}" "${BIN_DIR}/containerbase-cli" +} +link_cli_tool From 34dc4a3318188c1716864515aee2ce565e565669 Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Tue, 18 Jul 2023 11:27:04 +0200 Subject: [PATCH 4/8] fix: remove file removal --- src/cli/command/download-file.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cli/command/download-file.ts b/src/cli/command/download-file.ts index 24aaca0fbf..9d90345727 100644 --- a/src/cli/command/download-file.ts +++ b/src/cli/command/download-file.ts @@ -1,5 +1,5 @@ import { createWriteStream } from 'node:fs'; -import { mkdir, rm } from 'node:fs/promises'; +import { mkdir } from 'node:fs/promises'; import { dirname } from 'node:path'; import { pipeline } from 'node:stream/promises'; import { Command, Option } from 'clipanion'; @@ -33,7 +33,6 @@ export class DownloadFileCommand extends Command { const env = container.get(EnvService); const path = dirname(this.output); - await rm(this.output, { force: true }); await mkdir(path, { recursive: true }); const nUrl = env.replaceUrl(this.url); From f3ed01d8b179a0135612f6f0620b26c1b325807e Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Tue, 18 Jul 2023 12:09:31 +0200 Subject: [PATCH 5/8] docs: update url replacement --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4c9ff9e6c1..d06696955c 100644 --- a/README.md +++ b/README.md @@ -97,10 +97,9 @@ You can replace the default urls used to download the tools. The number of `URL_REPLACE_*_FROM` and `URL_REPLACE_*_TO` environment variables must match. Theses variables are case sensitive. The numbers will be processed in numerical order and can have gaps. -This is currently only supported by these tool installers: - -- `docker` -- `dart` +Almost all tools are noew supported. +Only tooles which are installed by `gem`, `npm` or `pip` are not supported. +They can be configured via thier own environment variables. Checkout [#1067](https://github.com/containerbase/base/issues/1067) for additional support. From 0d93b53f4bb3fc07383b25dc423272036e584b83 Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Tue, 18 Jul 2023 13:39:21 +0200 Subject: [PATCH 6/8] Update src/cli/services/env.service.ts --- src/cli/services/env.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cli/services/env.service.ts b/src/cli/services/env.service.ts index 3325ac37b1..cb4d70265a 100644 --- a/src/cli/services/env.service.ts +++ b/src/cli/services/env.service.ts @@ -91,7 +91,6 @@ export class EnvService { return (this.replacements = replacements); } - public isToolIgnored(tool: string): boolean { if (!this.ignoredTools) { this.ignoredTools = new Set( From 17fa2a44e93c92fa9d7c8668a15f5d6a65ba9f56 Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Tue, 18 Jul 2023 13:40:07 +0200 Subject: [PATCH 7/8] fix: formatting --- src/cli/services/env.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cli/services/env.service.ts b/src/cli/services/env.service.ts index cb4d70265a..d5e059c7d0 100644 --- a/src/cli/services/env.service.ts +++ b/src/cli/services/env.service.ts @@ -91,6 +91,7 @@ export class EnvService { return (this.replacements = replacements); } + public isToolIgnored(tool: string): boolean { if (!this.ignoredTools) { this.ignoredTools = new Set( From acd30da2aba030fde308c71b66687daf054220eb Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Tue, 18 Jul 2023 13:46:23 +0200 Subject: [PATCH 8/8] Apply suggestions from code review --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5d83837777..11a9469ace 100644 --- a/README.md +++ b/README.md @@ -98,8 +98,8 @@ You can replace the default urls used to download the tools. The number of `URL_REPLACE_*_FROM` and `URL_REPLACE_*_TO` environment variables must match. Theses variables are case sensitive. The numbers will be processed in numerical order and can have gaps. -Almost all tools are noew supported. -Only tooles which are installed by `gem`, `npm` or `pip` are not supported. +Almost all tools are now supported. +Only tools which are installed by `gem`, `npm` or `pip` are not supported. They can be configured via thier own environment variables. Checkout [#1067](https://github.com/containerbase/base/issues/1067) for additional support.