├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .dockerignore ├── .editorconfig ├── .github ├── actions │ ├── check │ │ └── action.yml │ ├── prepare-proxy │ │ └── action.yml │ └── setup-node │ │ └── action.yml └── workflows │ ├── build-pr.yml │ ├── build-push.yml │ ├── build.yml │ ├── devcontainer.yml │ └── trivy.yml ├── .gitignore ├── .husky └── pre-commit ├── .lintstagedrc.json ├── .node-version ├── .npmrc ├── .prettierignore ├── .prettierrc.json ├── .releaserc.json ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── __mocks__ └── pino.ts ├── codecov.yml ├── docker-bake.hcl ├── docs ├── cdn.md ├── custom-base-image.md ├── custom-registries.md └── custom-root-ca.md ├── eslint.config.js ├── package.json ├── patches ├── @yao-pkg__pkg.patch ├── clipanion@3.2.1.patch └── global-agent.patch ├── pnpm-lock.yaml ├── renovate.json ├── src ├── cli │ ├── command │ │ ├── cleanup-path.spec.ts │ │ ├── cleanup-path.ts │ │ ├── download-file.spec.ts │ │ ├── download-file.ts │ │ ├── index.ts │ │ ├── init-tool.spec.ts │ │ ├── init-tool.ts │ │ ├── install-gem.spec.ts │ │ ├── install-gem.ts │ │ ├── install-npm.spec.ts │ │ ├── install-npm.ts │ │ ├── install-pip.spec.ts │ │ ├── install-pip.ts │ │ ├── install-tool.spec.ts │ │ ├── install-tool.ts │ │ ├── prepare-tool.spec.ts │ │ ├── prepare-tool.ts │ │ ├── utils.spec.ts │ │ └── utils.ts │ ├── index.spec.ts │ ├── index.ts │ ├── install-tool │ │ ├── base-install.service.ts │ │ ├── index.spec.ts │ │ ├── index.ts │ │ ├── install-legacy-tool.service.ts │ │ ├── install-tool.service.ts │ │ ├── tool-version-resolver.service.ts │ │ └── tool-version-resolver.ts │ ├── main.spec.ts │ ├── main.ts │ ├── prepare-tool │ │ ├── base-prepare.service.ts │ │ ├── index.spec.ts │ │ ├── index.ts │ │ ├── prepare-legacy-tools.service.ts │ │ └── prepare-tool.service.ts │ ├── proxy.spec.ts │ ├── proxy.ts │ ├── services │ │ ├── apt.service.spec.ts │ │ ├── apt.service.ts │ │ ├── compression.service.spec.ts │ │ ├── compression.service.ts │ │ ├── env.service.spec.ts │ │ ├── env.service.ts │ │ ├── http.service.spec.ts │ │ ├── http.service.ts │ │ ├── index.ts │ │ ├── path.service.spec.ts │ │ ├── path.service.ts │ │ ├── version.service.spec.ts │ │ └── version.service.ts │ ├── tools │ │ ├── __mocks__ │ │ │ └── bun.ts │ │ ├── bazelisk.ts │ │ ├── bun.ts │ │ ├── dart │ │ │ ├── index.ts │ │ │ └── utils.ts │ │ ├── devbox.ts │ │ ├── docker.ts │ │ ├── dotnet.ts │ │ ├── flutter.ts │ │ ├── flux.ts │ │ ├── gleam.ts │ │ ├── helm.ts │ │ ├── helmfile.ts │ │ ├── index.ts │ │ ├── java │ │ │ ├── gradle.ts │ │ │ ├── index.ts │ │ │ ├── maven.ts │ │ │ ├── resolver.ts │ │ │ ├── schema.ts │ │ │ └── utils.ts │ │ ├── kubectl.ts │ │ ├── kustomize.ts │ │ ├── node │ │ │ ├── index.ts │ │ │ ├── npm.ts │ │ │ ├── resolver.ts │ │ │ ├── schema.ts │ │ │ └── utils.ts │ │ ├── php │ │ │ ├── __mocks__ │ │ │ │ └── composer.ts │ │ │ ├── composer.ts │ │ │ └── index.ts │ │ ├── pixi.ts │ │ ├── protoc.ts │ │ ├── python │ │ │ ├── conan.ts │ │ │ ├── pip.ts │ │ │ ├── poetry.ts │ │ │ ├── schema.ts │ │ │ └── utils.ts │ │ ├── ruby │ │ │ ├── cocoapods.ts │ │ │ ├── schema.ts │ │ │ └── utils.ts │ │ ├── skopeo.ts │ │ ├── sops.ts │ │ └── wally.ts │ ├── types.d.ts │ └── utils │ │ ├── codes.ts │ │ ├── common.spec.ts │ │ ├── common.ts │ │ ├── hash.spec.ts │ │ ├── hash.ts │ │ ├── index.spec.ts │ │ ├── index.ts │ │ ├── logger.spec.ts │ │ ├── logger.ts │ │ ├── types.ts │ │ ├── versions.spec.ts │ │ └── versions.ts └── usr │ └── local │ ├── containerbase │ ├── bin │ │ ├── docker-entrypoint.sh │ │ ├── init-tool.sh │ │ ├── install-apt.sh │ │ ├── install-containerbase.sh │ │ ├── install-tool.sh │ │ └── prepare-tool.sh │ ├── tools │ │ ├── git.sh │ │ └── v2 │ │ │ ├── elixir.sh │ │ │ ├── erlang.sh │ │ │ ├── git-lfs.sh │ │ │ ├── golang.sh │ │ │ ├── jb.sh │ │ │ ├── nix.sh │ │ │ ├── powershell.sh │ │ │ ├── python.sh │ │ │ ├── ruby.sh │ │ │ ├── rust.sh │ │ │ ├── sbt.sh │ │ │ ├── scala.sh │ │ │ ├── swift.sh │ │ │ ├── terraform.sh │ │ │ └── vendir.sh │ ├── util.sh │ └── utils │ │ ├── cache.sh │ │ ├── constants.sh │ │ ├── environment.sh │ │ ├── filesystem.sh │ │ ├── init.sh │ │ ├── install.sh │ │ ├── linking.sh │ │ ├── prepare.sh │ │ ├── ruby.sh │ │ ├── user.sh │ │ ├── v2 │ │ ├── defaults.sh │ │ ├── filesystem.sh │ │ └── overrides.sh │ │ └── version.sh │ └── sbin │ └── install-containerbase ├── test ├── Dockerfile.distro ├── bash │ ├── cache.bats │ ├── cache.sh │ ├── environment.bats │ ├── filesystem.bats │ ├── linking.bats │ ├── prepare.bats │ ├── util.bats │ ├── util.sh │ ├── v2 │ │ ├── defaults.bats │ │ └── filesystem.bats │ └── version.bats ├── dart │ ├── Dockerfile │ ├── Dockerfile.arm64 │ └── test │ │ ├── .gitignore │ │ └── a │ │ ├── pubspec.lock │ │ └── pubspec.yaml ├── dotnet │ ├── Dockerfile │ ├── Dockerfile.arm64 │ └── test │ │ ├── .gitignore │ │ ├── Class1.cs │ │ ├── packages.lock.json │ │ └── test.csproj ├── erlang │ ├── Dockerfile │ ├── Dockerfile.arm64 │ └── test │ │ └── a │ │ ├── mix.exs │ │ └── mix.lock ├── flutter │ ├── Dockerfile │ ├── Dockerfile.arm64 │ └── test │ │ ├── .gitignore │ │ ├── a │ │ ├── pubspec.lock │ │ └── pubspec.yaml │ │ ├── b │ │ ├── pubspec.lock │ │ └── pubspec.yaml │ │ ├── c │ │ ├── pubspec.lock │ │ └── pubspec.yaml │ │ └── d │ │ ├── pubspec.lock │ │ └── pubspec.yaml ├── flux │ ├── Dockerfile │ └── Dockerfile.arm64 ├── global-setup.ts ├── golang │ ├── Dockerfile │ ├── Dockerfile.arm64 │ └── test │ │ ├── a │ │ ├── go.mod │ │ └── go.sum │ │ ├── b │ │ ├── go.mod │ │ └── go.sum │ │ ├── c │ │ └── go.mod │ │ └── d │ │ ├── go.mod │ │ ├── go.sum │ │ └── vendor │ │ ├── github.com │ │ └── pkg │ │ │ └── errors │ │ │ ├── .gitignore │ │ │ ├── .travis.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── appveyor.yml │ │ │ ├── errors.go │ │ │ └── stack.go │ │ └── modules.txt ├── helm │ ├── Dockerfile │ └── Dockerfile.arm64 ├── http-mock.ts ├── java │ ├── Dockerfile │ ├── Dockerfile.arm64 │ └── test │ │ └── sbt │ │ ├── build.sbt │ │ └── src │ │ └── main │ │ └── scala │ │ └── example │ │ └── Hello.scala ├── jb │ ├── Dockerfile │ ├── Dockerfile.arm64 │ └── test │ │ ├── jsonnetfile.json │ │ └── jsonnetfile.lock.json ├── latest │ ├── Dockerfile │ ├── Dockerfile.arm64 │ └── src │ │ ├── etc │ │ └── nginx │ │ │ └── sites-enabled │ │ │ └── default │ │ └── test │ │ ├── ca.conf │ │ ├── ca.srl │ │ ├── cert.conf │ │ ├── request.mjs │ │ ├── request.php │ │ ├── request.py │ │ └── request.rb ├── nix │ ├── Dockerfile │ ├── Dockerfile.arm64 │ └── test │ │ ├── flake.lock │ │ └── flake.nix ├── node │ ├── Dockerfile │ ├── Dockerfile.arm64 │ └── test │ │ ├── a │ │ ├── .yarnrc.yml │ │ └── package.json │ │ └── b │ │ ├── package.json │ │ └── yarn.lock ├── path.ts ├── php │ ├── Dockerfile │ ├── Dockerfile.arm64 │ └── test │ │ └── a │ │ ├── composer.json │ │ └── composer.lock ├── powershell │ ├── Dockerfile │ └── Dockerfile.arm64 ├── python │ ├── Dockerfile │ ├── Dockerfile.arm64 │ └── test │ │ ├── a │ │ ├── Pipfile │ │ └── Pipfile.lock │ │ ├── b-conan │ │ └── conanfile.txt │ │ ├── c-poetry │ │ ├── poetry.lock │ │ └── pyproject.toml │ │ ├── d-poetry │ │ ├── poetry.lock │ │ └── pyproject.toml │ │ ├── f │ │ └── requirements.txt │ │ └── pipenv-b │ │ ├── Pipfile │ │ └── Pipfile.lock ├── ruby │ ├── Dockerfile │ ├── Dockerfile.arm64 │ └── test │ │ ├── a │ │ ├── Gemfile │ │ └── Gemfile.lock │ │ └── b │ │ ├── CPDAcknowledgements.podspec │ │ ├── CPDAcknowledgements │ │ └── private │ │ │ └── .gitkeep │ │ ├── Project │ │ ├── Demo Project.xcodeproj │ │ │ └── project.pbxproj │ │ ├── Podfile │ │ └── Podfile.lock │ │ └── Rakefile ├── rust │ ├── Dockerfile │ ├── Dockerfile.arm64 │ └── test │ │ └── a │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── crate1 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ │ ├── crate2 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ │ ├── crate5 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ │ ├── src │ │ └── main.rs │ │ └── subdir │ │ ├── crate3 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ │ └── crate4 │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── swift │ ├── Dockerfile │ ├── Dockerfile.arm64 │ └── test │ │ ├── a │ │ ├── Package.resolved │ │ └── Package.swift │ │ ├── b │ │ ├── Package.resolved │ │ └── Package.swift │ │ └── c │ │ ├── Package.resolved │ │ └── Package.swift └── types.d.ts ├── tools ├── bats.js ├── containerbase.acl ├── esbuild.js ├── prepare-proxy.js ├── prepare-release.js ├── publish-release.js ├── test.js └── utils.js ├── tsconfig.dist.json ├── tsconfig.json ├── tsconfig.lint.json └── vite.config.ts /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/containerbase/devcontainer:13.8.36 2 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "containerbase", 3 | "build": { 4 | "dockerfile": "Dockerfile" 5 | }, 6 | "customizations": { 7 | "vscode": { 8 | "settings": { 9 | "terminal.integrated.profiles.linux": { 10 | "bash": { 11 | "path": "bash", 12 | "icon": "terminal-bash" 13 | } 14 | }, 15 | "terminal.integrated.defaultProfile.linux": "bash" 16 | }, 17 | "extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] 18 | } 19 | }, 20 | "postCreateCommand": "pnpm install" 21 | } 22 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !test 3 | !dist/docker 4 | !dist/cli 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.github/actions/check/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Check system' 2 | description: 'checks system status' 3 | 4 | runs: 5 | using: 'composite' 6 | 7 | steps: 8 | - name: ⚙️ Check docker service 9 | shell: bash 10 | run: | 11 | systemctl status docker 12 | - name: ⚙️ Check docker info 13 | shell: bash 14 | run: | 15 | docker info 16 | -------------------------------------------------------------------------------- /.github/actions/prepare-proxy/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Prepare propxy' 2 | description: 'Prepares the apt proxy for the build' 3 | 4 | runs: 5 | using: 'composite' 6 | 7 | steps: 8 | - name: ⚙️ Prepare proxy 9 | shell: bash 10 | run: | 11 | sudo $(command -v node) tools/prepare-proxy.js 12 | -------------------------------------------------------------------------------- /.github/actions/setup-node/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Setup Node.js' 2 | description: 'Setup Node and install dependencies using cache' 3 | inputs: 4 | save-cache: 5 | description: 'Save cache when needed' 6 | required: false 7 | default: 'false' 8 | 9 | runs: 10 | using: 'composite' 11 | 12 | steps: 13 | - name: ⚙️ Calculate `CACHE_KEY` 14 | shell: bash 15 | run: | 16 | echo 'CACHE_KEY=node_modules-${{ 17 | hashFiles('.node-version', 'pnpm-lock.yaml', 'package.json') 18 | }}' >> "$GITHUB_ENV" 19 | 20 | - name: ♻️ Restore `node_modules` 21 | uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 22 | id: node-modules-restore 23 | with: 24 | path: node_modules 25 | key: ${{ env.CACHE_KEY }} 26 | enableCrossOsArchive: true 27 | 28 | - name: Calculate `CACHE_HIT` 29 | shell: bash 30 | run: | 31 | echo 'CACHE_HIT=${{ 32 | (steps.node-modules-restore.outputs.cache-hit == 'true') && 'true' || '' 33 | }}' >> "$GITHUB_ENV" 34 | 35 | - name: ⚙️ Setup pnpm 36 | uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 37 | with: 38 | standalone: false 39 | 40 | - name: ⚙️ Setup Node.js 41 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 42 | with: 43 | node-version-file: .node-version 44 | cache: ${{ env.CACHE_HIT != 'true' && 'pnpm' || '' }} 45 | 46 | - name: 📥 Install dependencies 47 | if: env.CACHE_HIT != 'true' 48 | shell: bash 49 | run: pnpm install --frozen-lockfile 50 | env: 51 | # Other environment variables 52 | HUSKY: '0' # By default do not run HUSKY install 53 | 54 | - name: ♻️ Write `node_modules` cache 55 | if: inputs.save-cache == 'true' && env.CACHE_HIT != 'true' 56 | uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 57 | with: 58 | path: node_modules 59 | key: ${{ env.CACHE_KEY }} 60 | enableCrossOsArchive: true 61 | -------------------------------------------------------------------------------- /.github/workflows/build-pr.yml: -------------------------------------------------------------------------------- 1 | name: build-pr 2 | 3 | on: 4 | pull_request: 5 | 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.event.number || github.ref }} 8 | cancel-in-progress: true 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | build: 15 | if: ${{ github.event_name != 'pull_request' || github.repository_owner != github.event.pull_request.head.repo.owner.login }} 16 | uses: ./.github/workflows/build.yml 17 | permissions: 18 | contents: read 19 | checks: write 20 | -------------------------------------------------------------------------------- /.github/workflows/devcontainer.yml: -------------------------------------------------------------------------------- 1 | name: devcontainer 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | types: 7 | - opened 8 | - synchronize 9 | - reopened 10 | - ready_for_review 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | devcontainer-test: 17 | runs-on: ubuntu-22.04 18 | if: github.event.pull_request.draft != true 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 22 | 23 | - name: Build and run dev container task 24 | uses: devcontainers/ci@8bf61b26e9c3a98f69cb6ce2f88d24ff59b785c6 # v0.3.1900000417 25 | with: 26 | runCmd: pnpm build 27 | -------------------------------------------------------------------------------- /.github/workflows/trivy.yml: -------------------------------------------------------------------------------- 1 | name: trivy 2 | 3 | on: 4 | schedule: 5 | - cron: '59 11 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: {} 9 | 10 | jobs: 11 | trivy: 12 | runs-on: ubuntu-22.04 13 | permissions: 14 | contents: read 15 | security-events: write 16 | steps: 17 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 18 | with: 19 | show-progress: false 20 | 21 | - uses: aquasecurity/trivy-action@e5f43133f6e8736992c9f3c1b3296e24b37e17f2 # 0.10.0 22 | with: 23 | image-ref: 'ghcr.io/containerbase/base:latest' 24 | format: 'sarif' 25 | output: 'trivy-results.sarif' 26 | 27 | - uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 28 | with: 29 | sarif_file: trivy-results.sarif 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | node_modules/ 3 | /.pnpm-store 4 | /bin/ 5 | /tmp/ 6 | /dist/ 7 | /coverage 8 | /html/ 9 | 10 | .eslintcache 11 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | lint-staged 4 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.sh": "shellcheck", 3 | "*.bats": "shellcheck", 4 | ".husky/*": "shellcheck", 5 | "src/usr/local/bin/*": "shellcheck", 6 | "src/usr/local/containerbase/bin/*": "shellcheck", 7 | "*.{js,ts,cjs,mjs}": ["eslint --fix", "prettier --write"], 8 | "!*.{js,ts,cjs,mjs}": "prettier --ignore-unknown --write" 9 | } 10 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 22.16.0 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact = true 2 | save-prefix = 3 | 4 | # pnpm run settings 5 | # https://pnpm.io/cli/run 6 | shell-emulator = true 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /coverage/ 3 | /dist/ 4 | /bin/ 5 | /tmp/ 6 | /html/ 7 | 8 | # pnpm 9 | pnpm-lock.yaml 10 | /.pnpm-store 11 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "plugins": ["prettier-plugin-packagejson"] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": ["/**"], 12 | "program": "${workspaceFolder}\\src\\cli\\index.ts", 13 | "outFiles": ["${workspaceFolder}/**/*.js"] 14 | }, 15 | { 16 | "type": "node", 17 | "request": "launch", 18 | "name": "Debug Current Test File", 19 | "autoAttachChildProcesses": true, 20 | "skipFiles": ["/**", "**/node_modules/**"], 21 | "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs", 22 | "args": [ 23 | "run", 24 | "--pool=forks", 25 | "--poolOptions.forks.singleFork", 26 | "${relativeFile}" 27 | ], 28 | "smartStep": true, 29 | "console": "integratedTerminal" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "omnisharp.autoStart": false, 3 | "typescript.preferences.importModuleSpecifier": "project-relative", 4 | "search.exclude": { 5 | "**/.yarn": true, 6 | "**/.pnp.*": true 7 | }, 8 | "files.associations": { 9 | "*.bats": "shellscript" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | #-------------------------------------- 2 | # Image: base 3 | #-------------------------------------- 4 | FROM ghcr.io/containerbase/ubuntu:24.04@sha256:6015f66923d7afbc53558d7ccffd325d43b4e249f41a6e93eef074c9505d2233 5 | 6 | ARG APT_HTTP_PROXY 7 | 8 | # Weekly cache buster 9 | ARG CACHE_WEEK 10 | 11 | ARG CONTAINERBASE_CDN 12 | ARG CONTAINERBASE_VERSION 13 | 14 | LABEL maintainer="Rhys Arkins " \ 15 | org.opencontainers.image.source="https://github.com/containerbase/base" 16 | 17 | # autoloading containerbase env 18 | ENV BASH_ENV=/usr/local/etc/env ENV=/usr/local/etc/env PATH=/home/ubuntu/bin:$PATH 19 | SHELL ["/bin/bash" , "-c"] 20 | 21 | ENTRYPOINT ["docker-entrypoint.sh"] 22 | CMD ["bash"] 23 | 24 | ARG TARGETARCH 25 | 26 | COPY dist/docker/ / 27 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 28 | 29 | RUN install-containerbase 30 | 31 | # renovate: datasource=github-tags packageName=git/git 32 | RUN install-tool git v2.49.0 33 | 34 | 35 | LABEL org.opencontainers.image.version="${CONTAINERBASE_VERSION}" 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 WhiteSource Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /__mocks__/pino.ts: -------------------------------------------------------------------------------- 1 | import { vi } from 'vitest'; 2 | 3 | export const levels = { values: { info: 30 } }; 4 | export const pino = vi.fn().mockReturnValue({ 5 | trace: vi.fn(), 6 | debug: vi.fn(), 7 | info: vi.fn(), 8 | warn: vi.fn(), 9 | error: vi.fn(), 10 | fatal: vi.fn(), 11 | }); 12 | export const transport = vi.fn((a) => a); 13 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | codecov: 3 | require_ci_to_pass: false 4 | notify: 5 | wait_for_ci: false 6 | -------------------------------------------------------------------------------- /docs/cdn.md: -------------------------------------------------------------------------------- 1 | # Containerbase CDN 2 | 3 | We provide a CDN for all tools. 4 | This is an experimental feature and might be changed at any time. 5 | It can be combined with [url replacements](./custom-registries.md). 6 | The replacements are applied after the CDN is resolved. 7 | 8 | ## Usage 9 | 10 | Set the `CONTAINERBASE_CDN` environment variable to the CDN URL before calling any containerbase tools. 11 | 12 | All urls will be resolved to the CDN URL. 13 | 14 | ## Additional configuration 15 | 16 | The package managers `gem`, `npm`, `pip` need to be configured to use the CDN explicitly via the following environment variables: 17 | 18 | - `CONTAINERBASE_CDN_GEM=true` 19 | - `CONTAINERBASE_CDN_NPM=true` 20 | - `CONTAINERBASE_CDN_PIP=true` 21 | 22 | ## Sample 23 | 24 | With the following sample the `java` tool will be installed from the CDN. 25 | 26 | ```bash 27 | export CONTAINERBASE_CDN=https://cdn.example.test 28 | install-tool java 29 | ``` 30 | 31 | The following urls will be called: 32 | 33 | - `https://cdn.example.test/api.adoptium.net/v3/info/release_versions?...` (fetch latest Java LTS) 34 | - `https://cdn.example.test/api.adoptium.net/v3/assets/version/{version}?...` (resolve download url) 35 | - `https://cdn.example.test/github.com/adoptium/temurin{major}-binaries/releases/...` (download the binary) 36 | -------------------------------------------------------------------------------- /docs/custom-root-ca.md: -------------------------------------------------------------------------------- 1 | # Custom Root CA Certificates 2 | 3 | Most tools support two ways to extend the default Root CA certificates list. 4 | 5 | If you are using a custom base image, checkout [Custom base image](./custom-base-image.md) docs. 6 | 7 | ## Notes 8 | 9 | 1. `Bun` doesn't support custom root ca certificates[^1]. 10 | 11 | [^1]: 12 | 13 | ## Buildtime install 14 | 15 | This is the easiest method. 16 | 17 | ```Dockerfile 18 | FROM containerbase/base 19 | 20 | COPY my-root-ca.crt /usr/local/share/ca-certificates/my-root-ca.crt 21 | RUN update-ca-certificates 22 | ``` 23 | 24 | ### Buildtime Java install 25 | 26 | Containerbase will create a central certificate store at `/opt/containerbase/ssl/cacerts` when preparing Java (`prepare-tool java`). 27 | This will be used by all Java versions installed by our `install-tool`. 28 | So you can copy your own store like this: 29 | 30 | ```Dockerfile 31 | FROM containerbase/base 32 | 33 | COPY my-root-cert-store.jks /opt/containerbase/ssl/cacerts 34 | 35 | RUN install-tool java 36 | ``` 37 | 38 | ## Runtime install 39 | 40 | Most OpenSSL base tools (and maybe BoringSSL) support `SSL_CERT_FILE` environment for additional custom root ca files. 41 | 42 | ```bash 43 | docker run --rm -it \ 44 | -v my-root-ca.crt:/my-root-ca.crt \ 45 | -e SSL_CERT_FILE=/my-root-ca.crt \ 46 | containerbase/base bash 47 | ``` 48 | 49 | ### Runtime Java install 50 | 51 | For Java you need to mount your own certificate store to `/opt/containerbase/ssl/cacerts`. 52 | 53 | ```bash 54 | docker run --rm -it \ 55 | -v my-root-ca.crt:/my-root-ca.crt \ 56 | -v my-root-cert-store.jks:/opt/containerbase/ssl/cacerts \ 57 | -e SSL_CERT_FILE=/my-root-ca.crt \ 58 | containerbase/base bash 59 | ``` 60 | -------------------------------------------------------------------------------- /patches/clipanion@3.2.1.patch: -------------------------------------------------------------------------------- 1 | diff --git a/lib/platform/package.json b/lib/platform/package.json 2 | index 5ea9d43740d1bdb509612376c0e9ce91d20f8b20..4164c61b7a30b2d74536f1f34e6cca6e6ed8c2f9 100644 3 | --- a/lib/platform/package.json 4 | +++ b/lib/platform/package.json 5 | @@ -1,4 +1,5 @@ 6 | { 7 | - "main": "./node", 8 | - "browser": "./browser" 9 | + "main": "./node.js", 10 | + "browser": "./browser.js", 11 | + "module": "./node.mjs" 12 | } 13 | diff --git a/package.json b/package.json 14 | index cbf943bcc31a864f2771e457d327e5106eba9afe..ae653069c2fd2dadcc4d1df45cdb8b25cf664e1a 100644 15 | --- a/package.json 16 | +++ b/package.json 17 | @@ -13,7 +13,8 @@ 18 | "command" 19 | ], 20 | "version": "3.2.1", 21 | - "main": "lib/advanced/index", 22 | + "main": "lib/advanced/index.js", 23 | + "module": "lib/advanced/index.mjs", 24 | "license": "MIT", 25 | "sideEffects": false, 26 | "repository": { 27 | -------------------------------------------------------------------------------- /patches/global-agent.patch: -------------------------------------------------------------------------------- 1 | diff --git a/dist/factories/createGlobalProxyAgent.js b/dist/factories/createGlobalProxyAgent.js 2 | index c87b9ed04f1cf8314374d9e169383e3b81904c22..a17fcde1b68706694ecdce5ae97e650065d5da87 100644 3 | --- a/dist/factories/createGlobalProxyAgent.js 4 | +++ b/dist/factories/createGlobalProxyAgent.js 5 | @@ -11,7 +11,7 @@ var _https = _interopRequireDefault(require("https")); 6 | 7 | var _boolean = require("boolean"); 8 | 9 | -var _semver = _interopRequireDefault(require("semver")); 10 | +var _semverGte = require("semver/functions/gte"); 11 | 12 | var _Logger = _interopRequireDefault(require("../Logger")); 13 | 14 | @@ -138,7 +138,7 @@ const createGlobalProxyAgent = (configurationInput = defaultConfigurationInput) 15 | const httpsAgent = new BoundHttpsProxyAgent(); // Overriding globalAgent was added in v11.7. 16 | // @see https://nodejs.org/uk/blog/release/v11.7.0/ 17 | 18 | - if (_semver.default.gte(process.version, 'v11.7.0')) { 19 | + if (_semverGte(process.version, 'v11.7.0')) { 20 | // @see https://github.com/facebook/flow/issues/7670 21 | // $FlowFixMe 22 | _http.default.globalAgent = httpAgent; // $FlowFixMe 23 | @@ -154,7 +154,7 @@ const createGlobalProxyAgent = (configurationInput = defaultConfigurationInput) 24 | // in `bindHttpMethod`. 25 | 26 | 27 | - if (_semver.default.gte(process.version, 'v10.0.0')) { 28 | + if (_semverGte(process.version, 'v10.0.0')) { 29 | // $FlowFixMe 30 | _http.default.get = (0, _utilities.bindHttpMethod)(httpGet, httpAgent, configuration.forceGlobalAgent); // $FlowFixMe 31 | 32 | -------------------------------------------------------------------------------- /src/cli/command/cleanup-path.spec.ts: -------------------------------------------------------------------------------- 1 | import { Cli } from 'clipanion'; 2 | import { describe, expect, test, vi } from 'vitest'; 3 | import { prepareCommands } from '.'; 4 | 5 | const mocks = vi.hoisted(() => ({ 6 | deleteAsync: vi.fn(), 7 | })); 8 | 9 | vi.mock('del', () => mocks); 10 | 11 | describe('cli/command/cleanup-path', () => { 12 | test('download-file', async () => { 13 | const cli = new Cli({ binaryName: 'containerbase-cli' }); 14 | prepareCommands(cli, null); 15 | 16 | expect( 17 | await cli.run(['cleanup', 'path', '/tmp/**:/var/tmp', '/some/path/**']), 18 | ).toBe(0); 19 | 20 | expect(mocks.deleteAsync).toHaveBeenCalledOnce(); 21 | expect(mocks.deleteAsync).toHaveBeenCalledWith( 22 | ['/tmp/**', '/var/tmp', '/some/path/**'], 23 | { dot: true }, 24 | ); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/cli/command/cleanup-path.ts: -------------------------------------------------------------------------------- 1 | import { Command, Option } from 'clipanion'; 2 | import { deleteAsync } from 'del'; 3 | import prettyMilliseconds from 'pretty-ms'; 4 | import { logger } from '../utils'; 5 | 6 | export class CleanupPathCommand extends Command { 7 | static override paths = [['cleanup', 'path']]; 8 | 9 | static override usage = Command.Usage({ 10 | description: 'Cleanup passed paths.', 11 | examples: [ 12 | [ 13 | 'Cleanup multiple paths', 14 | '$0 cleanup path "/tmp/**:/var/tmp" "/some/paths/**"', 15 | ], 16 | ], 17 | }); 18 | 19 | cleanupPaths = Option.Rest({ required: 1 }); 20 | 21 | async execute(): Promise { 22 | const start = Date.now(); 23 | let error = false; 24 | const paths = this.cleanupPaths.flatMap((p) => p.split(':')); 25 | logger.info({ paths }, `Cleanup paths ...`); 26 | try { 27 | const deleted = await deleteAsync(paths, { dot: true }); 28 | logger.debug({ deleted }, 'Deleted paths'); 29 | return 0; 30 | } catch (err) { 31 | error = true; 32 | logger.debug(err); 33 | if (err instanceof Error) { 34 | logger.error(err.message); 35 | } 36 | return 1; 37 | } finally { 38 | if (error) { 39 | logger.fatal( 40 | `Cleanup failed in ${prettyMilliseconds(Date.now() - start)}.`, 41 | ); 42 | } else { 43 | logger.info( 44 | `Cleanup succeded in ${prettyMilliseconds(Date.now() - start)}.`, 45 | ); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/cli/command/download-file.spec.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'node:process'; 2 | import { Cli } from 'clipanion'; 3 | import { beforeEach, describe, expect, test, vi } from 'vitest'; 4 | import { prepareCommands } from '.'; 5 | import { scope } from '~test/http-mock'; 6 | import { cachePath } from '~test/path'; 7 | 8 | const mocks = vi.hoisted(() => ({ 9 | installTool: vi.fn(), 10 | prepareTools: vi.fn(), 11 | })); 12 | 13 | vi.mock('../install-tool', () => mocks); 14 | vi.mock('../prepare-tool', () => mocks); 15 | 16 | describe('cli/command/download-file', () => { 17 | beforeEach(() => { 18 | for (const key of Object.keys(env)) { 19 | if (key.startsWith('URL_REPLACE_')) { 20 | delete env[key]; 21 | } 22 | } 23 | }); 24 | 25 | test('download-file', async () => { 26 | const cli = new Cli({ binaryName: 'containerbase-cli' }); 27 | prepareCommands(cli, null); 28 | 29 | const baseUrl = 'https://example.com'; 30 | scope(baseUrl) 31 | .get('/file.txt') 32 | .reply(200, 'ok') 33 | .get('/fail.txt') 34 | .reply(404); 35 | 36 | env.URL_REPLACE_0_FROM = 'https://example.test'; 37 | env.URL_REPLACE_0_TO = baseUrl; 38 | 39 | expect( 40 | await cli.run([ 41 | 'download', 42 | 'file', 43 | 'https://example.test/file.txt', 44 | cachePath('file.txt'), 45 | ]), 46 | ).toBe(0); 47 | 48 | expect( 49 | await cli.run([ 50 | 'download', 51 | 'file', 52 | 'https://example.test/fail.txt', 53 | cachePath('file.txt'), 54 | ]), 55 | ).toBe(1); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /src/cli/command/index.ts: -------------------------------------------------------------------------------- 1 | import type { Cli } from 'clipanion'; 2 | import type { CliMode } from '../utils'; 3 | import { logger } from '../utils/logger'; 4 | import { CleanupPathCommand } from './cleanup-path'; 5 | import { DownloadFileCommand } from './download-file'; 6 | import { InitToolCommand } from './init-tool'; 7 | import { InstallGemCommand, InstallGemShortCommand } from './install-gem'; 8 | import { InstallNpmCommand, InstallNpmShortCommand } from './install-npm'; 9 | import { InstallPipCommand, InstallPipShortCommand } from './install-pip'; 10 | import { InstallToolCommand, InstallToolShortCommand } from './install-tool'; 11 | import { PrepareToolCommand, PrepareToolShortCommand } from './prepare-tool'; 12 | 13 | export function prepareCommands(cli: Cli, mode: CliMode | null): void { 14 | logger.debug('prepare commands'); 15 | /* 16 | * Workaround for linking the cli tool as different executables. 17 | * So it can be called as 18 | * - `install-tool node 1.2.3` 19 | * - `install-npm corepack 1.2.3` 20 | * - `prepare-tool node` 21 | */ 22 | if (mode === 'install-tool') { 23 | cli.register(InstallToolShortCommand); 24 | return; 25 | } else if (mode === 'prepare-tool') { 26 | cli.register(PrepareToolShortCommand); 27 | return; 28 | } else if (mode === 'install-npm') { 29 | cli.register(InstallNpmShortCommand); 30 | return; 31 | } else if (mode === 'install-gem') { 32 | cli.register(InstallGemShortCommand); 33 | return; 34 | } else if (mode === 'install-pip') { 35 | cli.register(InstallPipShortCommand); 36 | return; 37 | } 38 | 39 | cli.register(DownloadFileCommand); 40 | cli.register(InstallGemCommand); 41 | cli.register(InstallNpmCommand); 42 | cli.register(InstallPipCommand); 43 | cli.register(InstallToolCommand); 44 | cli.register(PrepareToolCommand); 45 | cli.register(InitToolCommand); 46 | cli.register(CleanupPathCommand); 47 | } 48 | -------------------------------------------------------------------------------- /src/cli/command/init-tool.spec.ts: -------------------------------------------------------------------------------- 1 | import { Cli } from 'clipanion'; 2 | import { describe, expect, test, vi } from 'vitest'; 3 | import { prepareCommands } from '.'; 4 | 5 | const mocks = vi.hoisted(() => ({ 6 | installTool: vi.fn(), 7 | prepareTools: vi.fn(), 8 | initializeTools: vi.fn(), 9 | })); 10 | 11 | vi.mock('../install-tool', () => mocks); 12 | vi.mock('../prepare-tool', () => mocks); 13 | 14 | describe('cli/command/init-tool', () => { 15 | test('init-tool', async () => { 16 | const cli = new Cli({ binaryName: 'cli' }); 17 | prepareCommands(cli, null); 18 | 19 | expect(await cli.run(['init', 'tool', 'node'])).toBe(0); 20 | expect(mocks.initializeTools).toHaveBeenCalledOnce(); 21 | expect(mocks.initializeTools).toHaveBeenCalledWith(['node'], false); 22 | 23 | mocks.initializeTools.mockRejectedValueOnce(new Error('test')); 24 | expect(await cli.run(['init', 'tool', 'node'])).toBe(1); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/cli/command/init-tool.ts: -------------------------------------------------------------------------------- 1 | import { Command, Option } from 'clipanion'; 2 | import prettyMilliseconds from 'pretty-ms'; 3 | import { initializeTools } from '../prepare-tool'; 4 | import { logger } from '../utils'; 5 | 6 | export class InitToolCommand extends Command { 7 | static override paths = [['init', 'tool']]; 8 | 9 | static override usage = Command.Usage({ 10 | description: 11 | 'Initialize a tool into the container. This creates missing files and directories.', 12 | examples: [ 13 | ['Initialize node', '$0 init tool node'], 14 | ['Initialize all prepared tools', '$0 init tool all'], 15 | ], 16 | }); 17 | 18 | tools = Option.Rest({ required: 1 }); 19 | 20 | dryRun = Option.Boolean('-d,--dry-run', false); 21 | 22 | async execute(): Promise { 23 | const start = Date.now(); 24 | let error = false; 25 | logger.info(`Initializing tools ${this.tools.join(', ')}...`); 26 | try { 27 | return await initializeTools(this.tools, this.dryRun); 28 | } catch (err) { 29 | error = true; 30 | logger.debug(err); 31 | if (err instanceof Error) { 32 | logger.fatal(err.message); 33 | } 34 | return 1; 35 | } finally { 36 | if (error) { 37 | logger.fatal( 38 | `Initialize tools ${this.tools.join(', ')} failed in ${prettyMilliseconds(Date.now() - start)}.`, 39 | ); 40 | } else { 41 | logger.info( 42 | `Initialize tools ${this.tools.join(', ')} succeded in ${prettyMilliseconds(Date.now() - start)}.`, 43 | ); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/cli/command/install-gem.spec.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'node:process'; 2 | import { Cli } from 'clipanion'; 3 | import { beforeEach, describe, expect, test, vi } from 'vitest'; 4 | import { MissingVersion } from '../utils/codes'; 5 | import { prepareCommands } from '.'; 6 | 7 | const mocks = vi.hoisted(() => ({ 8 | installTool: vi.fn(), 9 | resolveVersion: vi.fn((_, v) => v), 10 | prepareTools: vi.fn(), 11 | })); 12 | 13 | vi.mock('../install-tool', () => mocks); 14 | vi.mock('../prepare-tool', () => mocks); 15 | 16 | describe('cli/command/install-gem', () => { 17 | beforeEach(() => { 18 | delete env.RAKE_VERSION; 19 | }); 20 | 21 | test('works', async () => { 22 | const cli = new Cli({ binaryName: 'install-gem' }); 23 | prepareCommands(cli, 'install-gem'); 24 | 25 | expect(await cli.run(['rake'])).toBe(MissingVersion); 26 | 27 | env.RAKE_VERSION = '13.0.6'; 28 | expect(await cli.run(['rake'])).toBe(0); 29 | expect(mocks.installTool).toHaveBeenCalledTimes(1); 30 | expect(mocks.installTool).toHaveBeenCalledWith( 31 | 'rake', 32 | '13.0.6', 33 | false, 34 | 'gem', 35 | ); 36 | expect(await cli.run(['rake', '-d'])).toBe(0); 37 | 38 | mocks.installTool.mockRejectedValueOnce(new Error('test')); 39 | expect(await cli.run(['rake'])).toBe(1); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/cli/command/install-gem.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'clipanion'; 2 | import { InstallToolCommand } from './install-tool'; 3 | 4 | export class InstallGemCommand extends InstallToolCommand { 5 | static override usage = Command.Usage({ 6 | description: 'Installs a gem package into the container.', 7 | examples: [ 8 | ['Installs rake 13.0.6', '$0 install gem rake 13.0.6'], 9 | [ 10 | 'Installs rake with version via environment variable', 11 | 'RAKE_VERSION=13.0.6 $0 install gem rake', 12 | ], 13 | // ['Installs latest rake version', '$0 install gem rake'], // not yet supported 14 | ], 15 | }); 16 | 17 | protected override type = 'gem' as const; 18 | } 19 | 20 | export class InstallGemShortCommand extends InstallGemCommand { 21 | static override paths = [Command.Default]; 22 | 23 | static override usage = Command.Usage({ 24 | description: 'Installs a gem package into the container.', 25 | examples: [ 26 | ['Installs rake v13.0.6', '$0 rake 13.0.6'], 27 | [ 28 | 'Installs rake with version via environment variable', 29 | 'RAKE_VERSION=13.0.6 $0 rake', 30 | ], 31 | // ['Installs latest rake version', '$0 rake'], // not yet supported 32 | ], 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /src/cli/command/install-npm.spec.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'node:process'; 2 | import { Cli } from 'clipanion'; 3 | import { beforeEach, describe, expect, test, vi } from 'vitest'; 4 | import { MissingVersion } from '../utils/codes'; 5 | import { prepareCommands } from '.'; 6 | 7 | const mocks = vi.hoisted(() => ({ 8 | installTool: vi.fn(), 9 | resolveVersion: vi.fn((_, v) => v), 10 | prepareTools: vi.fn(), 11 | })); 12 | 13 | vi.mock('../install-tool', () => mocks); 14 | vi.mock('../prepare-tool', () => mocks); 15 | 16 | describe('cli/command/install-npm', () => { 17 | beforeEach(() => { 18 | delete env.DEL_CLI_VERSION; 19 | }); 20 | 21 | test('works', async () => { 22 | const cli = new Cli({ binaryName: 'install-npm' }); 23 | prepareCommands(cli, 'install-npm'); 24 | 25 | expect(await cli.run(['del-cli'])).toBe(MissingVersion); 26 | 27 | mocks.resolveVersion.mockResolvedValueOnce('4.0.0'); 28 | expect(await cli.run(['del-cli'])).toBe(0); 29 | 30 | env.DEL_CLI_VERSION = '5.0.0'; 31 | expect(await cli.run(['del-cli'])).toBe(0); 32 | expect(mocks.installTool).toHaveBeenCalledTimes(2); 33 | expect(mocks.installTool).toHaveBeenCalledWith( 34 | 'del-cli', 35 | '4.0.0', 36 | false, 37 | 'npm', 38 | ); 39 | expect(mocks.installTool).toHaveBeenCalledWith( 40 | 'del-cli', 41 | '5.0.0', 42 | false, 43 | 'npm', 44 | ); 45 | expect(await cli.run(['del-cli', '-d'])).toBe(0); 46 | 47 | mocks.installTool.mockRejectedValueOnce(new Error('test')); 48 | expect(await cli.run(['del-cli'])).toBe(1); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/cli/command/install-npm.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'clipanion'; 2 | import { InstallToolCommand } from './install-tool'; 3 | 4 | export class InstallNpmCommand extends InstallToolCommand { 5 | static override paths = [['install', 'npm']]; 6 | static override usage = Command.Usage({ 7 | description: 'Installs a npm package into the container.', 8 | examples: [ 9 | ['Installs del-cli 5.0.0', '$0 install npm del-cli 5.0.0'], 10 | [ 11 | 'Installs del-cli with version via environment variable', 12 | 'DEL_CLI_VERSION=5.0.0 $0 install npm del-cli', 13 | ], 14 | ['Installs latest del-cli version', '$0 install npm del-cli'], 15 | ], 16 | }); 17 | 18 | protected override type = 'npm' as const; 19 | } 20 | 21 | export class InstallNpmShortCommand extends InstallNpmCommand { 22 | static override paths = [Command.Default]; 23 | 24 | static override usage = Command.Usage({ 25 | description: 'Installs a npm package into the container.', 26 | examples: [ 27 | ['Installs del-cli v5.0.0', '$0 del-cli 5.0.0'], 28 | [ 29 | 'Installs del-cli with version via environment variable', 30 | 'DEL_CLI_VERSION=5.0.0 $0 del-cli', 31 | ], 32 | ['Installs latest del-cli version', '$0 del-cli'], 33 | ], 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /src/cli/command/install-pip.spec.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'node:process'; 2 | import { Cli } from 'clipanion'; 3 | import { beforeEach, describe, expect, test, vi } from 'vitest'; 4 | import { MissingVersion } from '../utils/codes'; 5 | import { prepareCommands } from '.'; 6 | 7 | const mocks = vi.hoisted(() => ({ 8 | installTool: vi.fn(), 9 | resolveVersion: vi.fn((_, v) => v), 10 | prepareTools: vi.fn(), 11 | })); 12 | 13 | vi.mock('../install-tool', () => mocks); 14 | vi.mock('../prepare-tool', () => mocks); 15 | 16 | describe('cli/command/install-pip', () => { 17 | beforeEach(() => { 18 | delete env.POETRY_VERSION; 19 | }); 20 | 21 | test('works', async () => { 22 | const cli = new Cli({ binaryName: 'install-pip' }); 23 | prepareCommands(cli, 'install-pip'); 24 | 25 | expect(await cli.run(['poetry'])).toBe(MissingVersion); 26 | 27 | mocks.resolveVersion.mockResolvedValueOnce('4.0.0'); 28 | expect(await cli.run(['poetry'])).toBe(0); 29 | 30 | env.POETRY_VERSION = '5.0.0'; 31 | expect(await cli.run(['poetry'])).toBe(0); 32 | expect(mocks.installTool).toHaveBeenCalledTimes(2); 33 | expect(mocks.installTool).toHaveBeenCalledWith( 34 | 'poetry', 35 | '4.0.0', 36 | false, 37 | 'pip', 38 | ); 39 | expect(mocks.installTool).toHaveBeenCalledWith( 40 | 'poetry', 41 | '5.0.0', 42 | false, 43 | 'pip', 44 | ); 45 | expect(await cli.run(['poetry', '-d'])).toBe(0); 46 | 47 | mocks.installTool.mockRejectedValueOnce(new Error('test')); 48 | expect(await cli.run(['poetry'])).toBe(1); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/cli/command/install-pip.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'clipanion'; 2 | import { InstallToolCommand } from './install-tool'; 3 | 4 | export class InstallPipCommand extends InstallToolCommand { 5 | static override paths = [['install', 'pip']]; 6 | static override usage = Command.Usage({ 7 | description: 'Installs a pip package into the container.', 8 | examples: [ 9 | ['Installs checkov 2.4.7', '$0 install pip checkov 2.4.7'], 10 | [ 11 | 'Installs checkov with version via environment variable', 12 | 'DEL_CLI_VERSION=2.4.7 $0 install pip checkov', 13 | ], 14 | // TODO: add version resolver 15 | // ['Installs latest checkov version', '$0 install pip checkov'], 16 | ], 17 | }); 18 | 19 | protected override type = 'pip' as const; 20 | } 21 | 22 | export class InstallPipShortCommand extends InstallPipCommand { 23 | static override paths = [Command.Default]; 24 | 25 | static override usage = Command.Usage({ 26 | description: 'Installs a pip package into the container.', 27 | examples: [ 28 | ['Installs checkov v5.0.0', '$0 checkov 2.4.7'], 29 | [ 30 | 'Installs checkov with version via environment variable', 31 | 'DEL_CLI_VERSION=2.4.7 $0 checkov', 32 | ], 33 | // TODO: add version resolver 34 | // ['Installs latest checkov version', '$0 checkov'], 35 | ], 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /src/cli/command/install-tool.spec.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'node:process'; 2 | import { Cli } from 'clipanion'; 3 | import { beforeEach, describe, expect, test, vi } from 'vitest'; 4 | import { MissingVersion } from '../utils/codes'; 5 | import { prepareCommands } from '.'; 6 | 7 | const mocks = vi.hoisted(() => ({ 8 | installTool: vi.fn(), 9 | resolveVersion: vi.fn((_, v) => v), 10 | prepareTools: vi.fn(), 11 | })); 12 | 13 | vi.mock('../install-tool', () => mocks); 14 | vi.mock('../prepare-tool', () => mocks); 15 | 16 | describe('cli/command/install-tool', () => { 17 | beforeEach(() => { 18 | delete env.NODE_VERSION; 19 | }); 20 | 21 | test('works', async () => { 22 | const cli = new Cli({ binaryName: 'install-tool' }); 23 | prepareCommands(cli, 'install-tool'); 24 | 25 | expect(await cli.run(['node'])).toBe(MissingVersion); 26 | env.NODE_VERSION = '16.13.0'; 27 | expect(await cli.run(['node'])).toBe(0); 28 | expect(mocks.installTool).toHaveBeenCalledTimes(1); 29 | expect(mocks.installTool).toHaveBeenCalledWith( 30 | 'node', 31 | '16.13.0', 32 | false, 33 | undefined, 34 | ); 35 | expect(await cli.run(['node', '-d'])).toBe(0); 36 | 37 | mocks.installTool.mockRejectedValueOnce(new Error('test')); 38 | expect(await cli.run(['node'])).toBe(1); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/cli/command/prepare-tool.spec.ts: -------------------------------------------------------------------------------- 1 | import { Cli } from 'clipanion'; 2 | import { describe, expect, test, vi } from 'vitest'; 3 | import { prepareCommands } from '.'; 4 | 5 | const mocks = vi.hoisted(() => ({ 6 | installTool: vi.fn(), 7 | prepareTools: vi.fn(), 8 | })); 9 | 10 | vi.mock('../install-tool', () => mocks); 11 | vi.mock('../prepare-tool', () => mocks); 12 | 13 | describe('cli/command/prepare-tool', () => { 14 | test('prepare-tool', async () => { 15 | const cli = new Cli({ binaryName: 'prepare-tool' }); 16 | prepareCommands(cli, 'prepare-tool'); 17 | 18 | expect(await cli.run(['node'])).toBe(0); 19 | expect(mocks.prepareTools).toHaveBeenCalledOnce(); 20 | expect(mocks.prepareTools).toHaveBeenCalledWith(['node'], false); 21 | 22 | mocks.prepareTools.mockRejectedValueOnce(new Error('test')); 23 | expect(await cli.run(['node'])).toBe(1); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/cli/command/prepare-tool.ts: -------------------------------------------------------------------------------- 1 | import { Command, Option } from 'clipanion'; 2 | import prettyMilliseconds from 'pretty-ms'; 3 | import { prepareTools } from '../prepare-tool'; 4 | import { logger } from '../utils'; 5 | 6 | export class PrepareToolCommand extends Command { 7 | static override paths = [['prepare', 'tool'], ['pt']]; 8 | 9 | static override usage = Command.Usage({ 10 | description: 'Prepares a tool into the container.', 11 | examples: [ 12 | ['Prepares node', '$0 prepare tool node'], 13 | ['Prepares all tools', '$0 prepare tool all'], 14 | ], 15 | }); 16 | 17 | tools = Option.Rest({ required: 1 }); 18 | 19 | dryRun = Option.Boolean('-d,--dry-run', false); 20 | 21 | async execute(): Promise { 22 | const start = Date.now(); 23 | let error = false; 24 | logger.info(`Preparing tools ${this.tools.join(', ')}...`); 25 | try { 26 | return await prepareTools(this.tools, this.dryRun); 27 | } catch (err) { 28 | error = true; 29 | logger.debug(err); 30 | if (err instanceof Error) { 31 | logger.fatal(err.message); 32 | } 33 | return 1; 34 | } finally { 35 | if (error) { 36 | logger.fatal( 37 | `Prepare tools ${this.tools.join(', ')} failed in ${prettyMilliseconds(Date.now() - start)}.`, 38 | ); 39 | } else { 40 | logger.info( 41 | `Prepare tools ${this.tools.join(', ')} succeded in ${prettyMilliseconds(Date.now() - start)}.`, 42 | ); 43 | } 44 | } 45 | } 46 | } 47 | 48 | export class PrepareToolShortCommand extends PrepareToolCommand { 49 | static override paths = []; 50 | 51 | static override usage = Command.Usage({ 52 | description: 'Prepares a tool into the container.', 53 | examples: [ 54 | ['Prepares node', '$0 node'], 55 | ['Prepares all tools', '$0'], 56 | ], 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /src/cli/command/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'node:process'; 2 | import { beforeEach, describe, expect, test } from 'vitest'; 3 | import { getVersion, isToolIgnored } from './utils'; 4 | 5 | describe('cli/command/utils', () => { 6 | beforeEach(() => { 7 | delete env.NODE_VERSION; 8 | delete env.DEL_CLI_VERSION; 9 | delete env.IGNORED_TOOLS; 10 | }); 11 | 12 | test('getVersion', () => { 13 | expect(getVersion('node')).toBeUndefined(); 14 | env.NODE_VERSION = '1.0.0'; 15 | expect(getVersion('node')).toBe('1.0.0'); 16 | env.DEL_CLI_VERSION = '1.0.1'; 17 | expect(getVersion('del-cli')).toBe('1.0.1'); 18 | }); 19 | 20 | test('isToolIgnored', () => { 21 | expect(isToolIgnored('node')).toBe(false); 22 | env.IGNORED_TOOLS = 'node,pnpm'; 23 | expect(isToolIgnored('node')).toBe(true); 24 | expect(isToolIgnored('php')).toBe(false); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/cli/command/utils.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'node:process'; 2 | import { EnvService, rootContainer } from '../services'; 3 | 4 | export function getVersion(tool: string): string | undefined { 5 | return env[tool.replace('-', '_').toUpperCase() + '_VERSION']; 6 | } 7 | 8 | export function isToolIgnored(tool: string): boolean { 9 | const container = rootContainer.createChild(); 10 | return container.get(EnvService).isToolIgnored(tool); 11 | } 12 | -------------------------------------------------------------------------------- /src/cli/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test, vi } from 'vitest'; 2 | 3 | const mocks = vi.hoisted(() => ({ 4 | main: vi.fn(), 5 | })); 6 | 7 | vi.mock('./main', () => mocks); 8 | 9 | describe('cli/index', () => { 10 | test('works', async () => { 11 | await import('./index'); 12 | expect(mocks.main).toHaveBeenCalledTimes(1); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/cli/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import 'reflect-metadata'; 4 | import { main } from './main'; 5 | 6 | void main(); 7 | -------------------------------------------------------------------------------- /src/cli/install-tool/index.spec.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import { beforeAll, describe, expect, test, vi } from 'vitest'; 3 | import { rootPath } from '../../../test/path'; 4 | import { installTool, resolveVersion } from '.'; 5 | 6 | vi.mock('del'); 7 | vi.mock('execa'); 8 | vi.mock('../tools/bun'); 9 | vi.mock('../tools/php/composer'); 10 | 11 | describe('cli/install-tool/index', () => { 12 | beforeAll(async () => { 13 | for (const p of [ 14 | 'var/lib/containerbase/tool.prep.d', 15 | 'tmp/containerbase/tool.init.d', 16 | ]) { 17 | const prepDir = rootPath(p); 18 | await fs.mkdir(prepDir, { 19 | recursive: true, 20 | }); 21 | } 22 | }); 23 | 24 | test('installTool', async () => { 25 | expect(await installTool('bun', '1.0.0')).toBeUndefined(); 26 | expect(await installTool('dummy', '1.0.0')).toBeUndefined(); 27 | }); 28 | 29 | test('resolveVersion', async () => { 30 | expect(await resolveVersion('composer', '1.0.0')).toBe('1.0.0'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/cli/install-tool/install-legacy-tool.service.ts: -------------------------------------------------------------------------------- 1 | import { isNonEmptyStringAndNotWhitespace } from '@sindresorhus/is'; 2 | import { execa } from 'execa'; 3 | import { inject, injectable } from 'inversify'; 4 | import { EnvService } from '../services'; 5 | import { logger } from '../utils'; 6 | 7 | const defaultPipRegistry = 'https://pypi.org/simple/'; 8 | 9 | @injectable() 10 | export class LegacyToolInstallService { 11 | constructor(@inject(EnvService) private readonly envSvc: EnvService) {} 12 | 13 | async execute(tool: string, version: string): Promise { 14 | logger.debug(`Installing legacy tool ${tool} v${version} ...`); 15 | const env: NodeJS.ProcessEnv = {}; 16 | 17 | const pipIndex = this.envSvc.replaceUrl( 18 | defaultPipRegistry, 19 | isNonEmptyStringAndNotWhitespace(env.CONTAINERBASE_CDN_PIP), 20 | ); 21 | if (pipIndex !== defaultPipRegistry) { 22 | env.PIP_INDEX_URL = pipIndex; 23 | } 24 | 25 | await execa( 26 | '/usr/local/containerbase/bin/install-tool.sh', 27 | [tool, version], 28 | { 29 | stdio: ['inherit', 'inherit', 1], 30 | env, 31 | }, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/cli/install-tool/tool-version-resolver.service.ts: -------------------------------------------------------------------------------- 1 | import { injectable, multiInject } from 'inversify'; 2 | import { 3 | TOOL_VERSION_RESOLVER, 4 | type ToolVersionResolver, 5 | } from './tool-version-resolver'; 6 | 7 | @injectable() 8 | export class ToolVersionResolverService { 9 | constructor( 10 | @multiInject(TOOL_VERSION_RESOLVER) private resolver: ToolVersionResolver[], 11 | ) {} 12 | 13 | async resolve( 14 | tool: string, 15 | version: string | undefined, 16 | ): Promise { 17 | const resolver = this.resolver.find((r) => r.tool === tool); 18 | return (await resolver?.resolve(version)) ?? version; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/cli/install-tool/tool-version-resolver.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from 'inversify'; 2 | import { EnvService, HttpService } from '../services'; 3 | 4 | export const TOOL_VERSION_RESOLVER = Symbol('TOOL_VERSION_RESOLVER'); 5 | 6 | @injectable() 7 | export abstract class ToolVersionResolver { 8 | abstract readonly tool: string; 9 | 10 | constructor( 11 | @inject(HttpService) protected readonly http: HttpService, 12 | @inject(EnvService) protected readonly env: EnvService, 13 | ) {} 14 | 15 | abstract resolve(version: string | undefined): Promise; 16 | } 17 | -------------------------------------------------------------------------------- /src/cli/main.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test, vi } from 'vitest'; 2 | import { main } from './main'; 3 | 4 | const mocks = vi.hoisted(() => ({ 5 | argv0: 'containerbase-cli', 6 | argv: ['node', 'containerbase-cli', 'help'], 7 | })); 8 | 9 | vi.mock('node:process', async (importOriginal) => ({ 10 | ...(await importOriginal()), 11 | ...mocks, 12 | })); 13 | 14 | vi.mock('./utils/common', async (importActual) => ({ 15 | ...(await importActual()), 16 | validateSystem: vi.fn(), 17 | })); 18 | 19 | describe('cli/main', () => { 20 | test('works', async () => { 21 | expect(await main()).toBeUndefined(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/cli/main.ts: -------------------------------------------------------------------------------- 1 | import { argv, argv0, version } from 'node:process'; 2 | import { Builtins, Cli } from 'clipanion'; 3 | import { prepareCommands } from './command'; 4 | import { bootstrap } from './proxy'; 5 | import { cliMode, logger, parseBinaryName, validateSystem } from './utils'; 6 | 7 | declare global { 8 | // needs to be this to make eslint happy 9 | // eslint-disable-next-line no-var 10 | var CONTAINERBASE_VERSION: string | undefined; 11 | } 12 | 13 | export async function main(): Promise { 14 | logger.trace({ argv0, argv, version }, 'main'); 15 | bootstrap(); 16 | await validateSystem(); 17 | 18 | const mode = cliMode(); 19 | const [node, app, ...args] = argv; 20 | 21 | const cli = new Cli({ 22 | binaryLabel: `containerbase-cli`, 23 | binaryName: parseBinaryName(mode, node!, app!)!, 24 | binaryVersion: `${ 25 | globalThis.CONTAINERBASE_VERSION ?? '0.0.0-PLACEHOLDER' 26 | } (Node ${version})`, 27 | }); 28 | 29 | cli.register(Builtins.DefinitionsCommand); 30 | cli.register(Builtins.HelpCommand); 31 | cli.register(Builtins.VersionCommand); 32 | 33 | prepareCommands(cli, mode); 34 | 35 | await cli.runExit(args); 36 | } 37 | -------------------------------------------------------------------------------- /src/cli/prepare-tool/base-prepare.service.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from 'inversify'; 2 | import { EnvService, PathService } from '../services'; 3 | 4 | @injectable() 5 | export abstract class BasePrepareService { 6 | abstract readonly name: string; 7 | 8 | constructor( 9 | @inject(PathService) protected readonly pathSvc: PathService, 10 | @inject(EnvService) protected readonly envSvc: EnvService, 11 | ) {} 12 | 13 | prepare(): Promise | void { 14 | // noting to do; 15 | } 16 | initialize(): Promise | void { 17 | // noting to do; 18 | } 19 | 20 | toString(): string { 21 | return this.name; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/cli/prepare-tool/index.spec.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import { beforeAll, describe, expect, test, vi } from 'vitest'; 3 | import { rootPath } from '../../../test/path'; 4 | import { PathService, rootContainer } from '../services'; 5 | import { initializeTools, prepareTools } from '.'; 6 | 7 | vi.mock('del'); 8 | vi.mock('execa'); 9 | vi.mock('../tools/bun'); 10 | vi.mock('../tools/php/composer'); 11 | 12 | vi.mock('node:process', async (importOriginal) => ({ 13 | ...(await importOriginal()), 14 | geteuid: () => 0, 15 | })); 16 | 17 | describe('cli/prepare-tool/index', () => { 18 | beforeAll(async () => { 19 | for (const p of [ 20 | 'var/lib/containerbase/tool.prep.d', 21 | 'tmp/containerbase/tool.init.d', 22 | 'usr/local/containerbase/tools/v2', 23 | ]) { 24 | const prepDir = rootPath(p); 25 | await fs.mkdir(prepDir, { 26 | recursive: true, 27 | }); 28 | } 29 | 30 | await fs.writeFile( 31 | rootPath('usr/local/containerbase/tools/v2/dummy.sh'), 32 | '', 33 | ); 34 | 35 | const child = rootContainer.createChild(); 36 | const pathSvc = child.get(PathService); 37 | await pathSvc.setPrepared('bun'); 38 | }); 39 | 40 | test('prepareTools', async () => { 41 | expect(await prepareTools(['bun', 'dummy'])).toBeUndefined(); 42 | expect(await prepareTools(['not-exist'])).toBe(1); 43 | }); 44 | 45 | test('initializeTools', async () => { 46 | expect(await initializeTools(['bun', 'dummy'])).toBeUndefined(); 47 | expect(await initializeTools(['not-exist'])).toBeUndefined(); 48 | expect(await initializeTools(['all'])).toBeUndefined(); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/cli/prepare-tool/prepare-legacy-tools.service.ts: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa'; 2 | import { injectable } from 'inversify'; 3 | import { logger } from '../utils'; 4 | 5 | @injectable() 6 | export class PrepareLegacyToolsService { 7 | async prepare(tool: string): Promise { 8 | logger.debug(`Preparing legacy tool ${tool} ...`); 9 | await execa('/usr/local/containerbase/bin/prepare-tool.sh', [tool], { 10 | stdio: ['inherit', 'inherit', 1], 11 | }); 12 | } 13 | 14 | async initialize(tool: string): Promise { 15 | logger.debug(`Initializing legacy tool ${tool} ...`); 16 | await execa('/usr/local/containerbase/bin/init-tool.sh', [tool], { 17 | stdio: ['inherit', 'inherit', 1], 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/cli/proxy.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'node:process'; 2 | import { isNonEmptyString, isUndefined } from '@sindresorhus/is'; 3 | import { createGlobalProxyAgent } from 'global-agent'; 4 | 5 | const envVars = ['HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY']; 6 | 7 | export function bootstrap(): void { 8 | for (const envVar of envVars) { 9 | const lKey = envVar.toLowerCase(); 10 | if (isUndefined(env[envVar]) && isNonEmptyString(env[lKey])) { 11 | env[envVar] = env[lKey]; 12 | } 13 | 14 | if (env[envVar]) { 15 | env[lKey] = env[envVar]; 16 | } 17 | } 18 | 19 | if (isNonEmptyString(env.HTTP_PROXY) || isNonEmptyString(env.HTTPS_PROXY)) { 20 | createGlobalProxyAgent({ 21 | environmentVariableNamespace: '', 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/cli/services/apt.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'node:process'; 2 | import type { Container } from 'inversify'; 3 | import { beforeEach, describe, expect, test, vi } from 'vitest'; 4 | import { AptService, rootContainer } from '.'; 5 | 6 | const mocks = vi.hoisted(() => ({ 7 | execa: vi.fn(), 8 | rm: vi.fn(), 9 | writeFile: vi.fn(), 10 | })); 11 | 12 | vi.mock('execa', () => mocks); 13 | vi.mock('node:fs/promises', async (importActual) => ({ 14 | default: { ...(await importActual()), ...mocks }, 15 | ...mocks, 16 | })); 17 | 18 | describe('cli/services/apt.service', () => { 19 | let child!: Container; 20 | 21 | beforeEach(() => { 22 | child = rootContainer.createChild(); 23 | delete env.APT_HTTP_PROXY; 24 | }); 25 | 26 | test('skips install', async () => { 27 | const svc = child.get(AptService); 28 | 29 | mocks.execa.mockResolvedValueOnce({ 30 | stdout: 'Status: install ok installed', 31 | }); 32 | await svc.install('some-pkg'); 33 | expect(mocks.execa).toHaveBeenCalledTimes(1); 34 | }); 35 | 36 | test('works', async () => { 37 | const svc = child.get(AptService); 38 | 39 | mocks.execa.mockRejectedValueOnce(new Error('not installed')); 40 | await svc.install('some-pkg'); 41 | expect(mocks.execa).toHaveBeenCalledTimes(3); 42 | expect(mocks.writeFile).not.toHaveBeenCalled(); 43 | expect(mocks.rm).not.toHaveBeenCalled(); 44 | }); 45 | 46 | test('uses proxy', async () => { 47 | env.APT_HTTP_PROXY = 'http://proxy'; 48 | const svc = child.get(AptService); 49 | 50 | mocks.execa.mockRejectedValueOnce(new Error('not installed')); 51 | await svc.install('some-pkg', 'other-pkg'); 52 | expect(mocks.execa).toHaveBeenCalledTimes(4); 53 | expect(mocks.writeFile).toHaveBeenCalledOnce(); 54 | expect(mocks.rm).toHaveBeenCalledOnce(); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /src/cli/services/apt.service.ts: -------------------------------------------------------------------------------- 1 | import { rm, writeFile } from 'fs/promises'; 2 | import { join } from 'node:path'; 3 | import { execa } from 'execa'; 4 | import { inject, injectable } from 'inversify'; 5 | import { logger } from '../utils'; 6 | import { EnvService } from './env.service'; 7 | 8 | @injectable() 9 | export class AptService { 10 | constructor(@inject(EnvService) private readonly envSvc: EnvService) {} 11 | 12 | async install(...packages: string[]): Promise { 13 | const todo: string[] = []; 14 | 15 | for (const pkg of packages) { 16 | if (await this.isInstalled(pkg)) { 17 | continue; 18 | } 19 | todo.push(pkg); 20 | } 21 | 22 | if (todo.length === 0) { 23 | logger.debug({ packages }, 'all packages already installed'); 24 | return; 25 | } 26 | 27 | logger.debug({ packages: todo }, 'installing packages'); 28 | 29 | if (this.envSvc.aptProxy) { 30 | logger.debug({ proxy: this.envSvc.aptProxy }, 'using apt proxy'); 31 | await writeFile( 32 | join( 33 | this.envSvc.rootDir, 34 | 'etc/apt/apt.conf.d/containerbase-proxy.conf', 35 | ), 36 | `Acquire::http::Proxy "${this.envSvc.aptProxy}";\n`, 37 | ); 38 | } 39 | 40 | try { 41 | await execa('apt-get', ['-qq', 'update']); 42 | await execa('apt-get', ['-qq', 'install', '-y', ...todo]); 43 | } finally { 44 | if (this.envSvc.aptProxy) { 45 | await rm( 46 | join( 47 | this.envSvc.rootDir, 48 | 'etc/apt/apt.conf.d/containerbase-proxy.conf', 49 | ), 50 | { 51 | force: true, 52 | }, 53 | ); 54 | } 55 | } 56 | } 57 | 58 | private async isInstalled(pkg: string): Promise { 59 | try { 60 | const res = await execa('dpkg', ['-s', pkg]); 61 | return res.stdout.includes('Status: install ok installed'); 62 | } catch { 63 | return false; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/cli/services/compression.service.spec.ts: -------------------------------------------------------------------------------- 1 | import type { Container } from 'inversify'; 2 | import { beforeEach, describe, expect, test, vi } from 'vitest'; 3 | import { CompressionService, rootContainer } from '.'; 4 | 5 | vi.mock('execa'); 6 | 7 | describe('cli/services/compression.service', () => { 8 | let child!: Container; 9 | 10 | beforeEach(() => { 11 | child = rootContainer.createChild(); 12 | }); 13 | 14 | test('extracts with bstar', async () => { 15 | const svc = child.get(CompressionService); 16 | 17 | await expect( 18 | svc.extract({ file: 'some.txz', cwd: globalThis.cacheDir }), 19 | ).resolves.toBeUndefined(); 20 | 21 | await expect( 22 | svc.extract({ file: 'some.txz', cwd: globalThis.cacheDir, strip: 1 }), 23 | ).resolves.toBeUndefined(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/cli/services/compression.service.ts: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa'; 2 | import { inject, injectable } from 'inversify'; 3 | import { EnvService } from './env.service'; 4 | 5 | export interface ExtractConfig { 6 | file: string; 7 | cwd: string; 8 | strip?: number | undefined; 9 | 10 | files?: string[]; 11 | 12 | /** 13 | * Additional options to pass to the `bsdtar` command. 14 | */ 15 | options?: string[]; 16 | } 17 | 18 | @injectable() 19 | export class CompressionService { 20 | constructor(@inject(EnvService) private readonly envSvc: EnvService) {} 21 | async extract({ 22 | file, 23 | cwd, 24 | strip, 25 | files, 26 | options, 27 | }: ExtractConfig): Promise { 28 | await execa('bsdtar', [ 29 | '-xf', 30 | file, 31 | '-C', 32 | cwd, 33 | ...(strip ? ['--strip', `${strip}`] : []), 34 | '--uid', 35 | `${this.envSvc.userId}`, 36 | '--gid', 37 | '0', 38 | ...(options ?? []), 39 | ...(files ?? []), 40 | ]); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/cli/services/index.ts: -------------------------------------------------------------------------------- 1 | import { Container } from 'inversify'; 2 | import { AptService } from './apt.service'; 3 | import { CompressionService } from './compression.service'; 4 | import { EnvService } from './env.service'; 5 | import { HttpService } from './http.service'; 6 | import { PathService } from './path.service'; 7 | import { VersionService } from './version.service'; 8 | 9 | export { 10 | AptService, 11 | CompressionService, 12 | EnvService, 13 | HttpService, 14 | PathService, 15 | VersionService, 16 | }; 17 | 18 | export const rootContainer = new Container(); 19 | 20 | rootContainer.bind(AptService).toSelf(); 21 | rootContainer.bind(CompressionService).toSelf(); 22 | rootContainer.bind(EnvService).toSelf(); 23 | rootContainer.bind(HttpService).toSelf(); 24 | rootContainer.bind(PathService).toSelf(); 25 | rootContainer.bind(VersionService).toSelf(); 26 | -------------------------------------------------------------------------------- /src/cli/services/version.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { mkdir } from 'fs/promises'; 2 | import type { Container } from 'inversify'; 3 | import { beforeEach, describe, expect, test } from 'vitest'; 4 | import { VersionService, rootContainer } from '.'; 5 | import { rootPath } from '~test/path'; 6 | 7 | describe('cli/services/version.service', () => { 8 | let child!: Container; 9 | 10 | beforeEach(() => { 11 | child = rootContainer.createChild(); 12 | }); 13 | 14 | test('works', async () => { 15 | const svc = child.get(VersionService); 16 | 17 | // doesn't fail 18 | await svc.update('node', '14.17.0'); 19 | 20 | await mkdir(rootPath('opt/containerbase/versions'), { recursive: true }); 21 | 22 | expect(await svc.find('node')).toBeNull(); 23 | await svc.update('node', '14.17.0'); 24 | expect(await svc.find('node')).toBe('14.17.0'); 25 | await svc.update('node', ''); 26 | expect(await svc.find('node')).toBeNull(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/cli/services/version.service.ts: -------------------------------------------------------------------------------- 1 | import { chmod, readFile, stat, writeFile } from 'node:fs/promises'; 2 | import { join } from 'node:path'; 3 | import { inject, injectable } from 'inversify'; 4 | import { fileRights, logger, tool2path } from '../utils'; 5 | import { PathService } from './path.service'; 6 | 7 | @injectable() 8 | export class VersionService { 9 | constructor(@inject(PathService) private pathSvc: PathService) {} 10 | 11 | async find(tool: string): Promise { 12 | const path = join(this.pathSvc.versionPath, tool2path(tool)); 13 | try { 14 | return (await readFile(path, { encoding: 'utf8' })).trim() || null; 15 | } catch (err) { 16 | if (err instanceof Error && err.code === 'ENOENT') { 17 | logger.debug({ tool }, 'tool version not found'); 18 | /* v8 ignore next 3 */ 19 | } else { 20 | logger.error({ tool, err }, 'tool version not found'); 21 | } 22 | return null; 23 | } 24 | } 25 | 26 | async update(tool: string, version: string): Promise { 27 | const path = join(this.pathSvc.versionPath, tool2path(tool)); 28 | try { 29 | await writeFile(path, version, { encoding: 'utf8' }); 30 | const s = await stat(path); 31 | if ((s.mode & fileRights) !== 0o664) { 32 | await chmod(path, 0o664); 33 | } 34 | } catch (err) { 35 | logger.error({ tool, err }, 'tool version not found'); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/cli/tools/__mocks__/bun.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from 'inversify'; 2 | import { BaseInstallService } from '../../install-tool/base-install.service'; 3 | import { EnvService, PathService } from '../../services'; 4 | 5 | @injectable() 6 | export class BunInstallService extends BaseInstallService { 7 | readonly name = 'bun'; 8 | 9 | constructor( 10 | @inject(PathService) pathSvc: PathService, 11 | @inject(EnvService) envSvc: EnvService, 12 | ) { 13 | super(pathSvc, envSvc); 14 | } 15 | 16 | override isInstalled(_version: string): Promise { 17 | return Promise.resolve(false); 18 | } 19 | 20 | override install(_version: string): Promise { 21 | return Promise.resolve(); 22 | } 23 | 24 | override link(_version: string): Promise { 25 | return Promise.resolve(); 26 | } 27 | 28 | override test(_version: string): Promise { 29 | return Promise.resolve(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/cli/tools/bazelisk.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import { join } from 'node:path'; 3 | import { execa } from 'execa'; 4 | import { inject, injectable } from 'inversify'; 5 | import { BaseInstallService } from '../install-tool/base-install.service'; 6 | import { EnvService, HttpService, PathService } from '../services'; 7 | 8 | @injectable() 9 | export class BazeliskInstallService extends BaseInstallService { 10 | readonly name = 'bazelisk'; 11 | 12 | constructor( 13 | @inject(EnvService) envSvc: EnvService, 14 | @inject(PathService) pathSvc: PathService, 15 | @inject(HttpService) private http: HttpService, 16 | ) { 17 | super(pathSvc, envSvc); 18 | } 19 | 20 | override async install(version: string): Promise { 21 | const baseurl = `https://github.com/bazelbuild/bazelisk/releases/download/v${version}/`; 22 | const filename = `bazelisk-linux-${this.envSvc.arch}`; 23 | 24 | const file = await this.http.download({ 25 | url: `${baseurl}${filename}`, 26 | }); 27 | 28 | await this.pathSvc.ensureToolPath(this.name); 29 | 30 | const path = join( 31 | await this.pathSvc.createVersionedToolPath(this.name, version), 32 | 'bin', 33 | ); 34 | await fs.mkdir(path); 35 | 36 | const binarypath = join(path, 'bazelisk'); 37 | await fs.copyFile(file, binarypath); 38 | await this.pathSvc.setOwner({ 39 | path: binarypath, 40 | }); 41 | } 42 | 43 | override async link(version: string): Promise { 44 | const src = join(this.pathSvc.versionedToolPath(this.name, version), 'bin'); 45 | 46 | await this.shellwrapper({ 47 | srcDir: src, 48 | }); 49 | await fs.symlink( 50 | join(this.pathSvc.binDir, 'bazelisk'), 51 | join(this.pathSvc.binDir, 'bazel'), 52 | ); 53 | } 54 | 55 | override async test(_version: string): Promise { 56 | await execa('bazelisk', ['version'], { stdio: ['inherit', 'inherit', 1] }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/cli/tools/dart/utils.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import { join } from 'node:path'; 3 | import type { EnvService, PathService } from '../../services'; 4 | import { pathExists } from '../../utils'; 5 | 6 | export async function prepareDartHome( 7 | envSvc: EnvService, 8 | pathSvc: PathService, 9 | ): Promise { 10 | const dart = join(envSvc.userHome, '.dart'); 11 | if (!(await pathExists(dart))) { 12 | await fs.symlink(join(pathSvc.cachePath, '.dart'), dart); 13 | await fs.symlink( 14 | join(pathSvc.cachePath, '.dart-tool'), 15 | join(envSvc.userHome, '.dart-tool'), 16 | ); 17 | } 18 | 19 | // for root 20 | const rootDart = join(envSvc.rootDir, 'root', '.dart'); 21 | if (!(await pathExists(rootDart))) { 22 | await fs.mkdir(rootDart); 23 | await fs.writeFile( 24 | join(rootDart, 'dartdev.json'), 25 | '{ "firstRun": false, "enabled": false }', 26 | ); 27 | } 28 | } 29 | 30 | export async function initDartHome(pathSvc: PathService): Promise { 31 | // for user 32 | const dart = join(pathSvc.cachePath, '.dart'); 33 | if (!(await pathExists(dart))) { 34 | const dartTool = join(pathSvc.cachePath, '.dart-tool'); 35 | 36 | await pathSvc.createDir(dart); 37 | await pathSvc.createDir(dartTool); 38 | 39 | const dartDev = join(dart, 'dartdev.json'); 40 | await pathSvc.writeFile(dartDev, '{ "firstRun": false, "enabled": false }'); 41 | 42 | const dartToolTelemetry = join(dartTool, 'dart-flutter-telemetry.config'); 43 | await pathSvc.writeFile(dartToolTelemetry, 'reporting=0\n'); 44 | } 45 | } 46 | 47 | export async function preparePubCache( 48 | envSvc: EnvService, 49 | pathSvc: PathService, 50 | ): Promise { 51 | const pubCache = join(envSvc.userHome, '.pub-cache'); 52 | if (!(await pathExists(pubCache))) { 53 | await fs.symlink(join(pathSvc.cachePath, '.pub-cache'), pubCache); 54 | } 55 | } 56 | 57 | export async function initPubCache(pathSvc: PathService): Promise { 58 | await pathSvc.createDir(join(pathSvc.cachePath, '.pub-cache')); 59 | } 60 | -------------------------------------------------------------------------------- /src/cli/tools/devbox.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import { join } from 'node:path'; 3 | import { execa } from 'execa'; 4 | import { inject, injectable } from 'inversify'; 5 | import { BaseInstallService } from '../install-tool/base-install.service'; 6 | import { 7 | CompressionService, 8 | EnvService, 9 | HttpService, 10 | PathService, 11 | } from '../services'; 12 | 13 | @injectable() 14 | export class DevboxInstallService extends BaseInstallService { 15 | readonly name = 'devbox'; 16 | 17 | constructor( 18 | @inject(EnvService) envSvc: EnvService, 19 | @inject(PathService) pathSvc: PathService, 20 | @inject(HttpService) private http: HttpService, 21 | @inject(CompressionService) private compress: CompressionService, 22 | ) { 23 | super(pathSvc, envSvc); 24 | } 25 | 26 | override async install(version: string): Promise { 27 | const baseUrl = `https://github.com/jetify-com/devbox/releases/download/${version}/`; 28 | const filename = `devbox_${version}_linux_${this.envSvc.arch}.tar.gz`; 29 | 30 | const checksumFile = await this.http.download({ 31 | url: `${baseUrl}checksums.txt`, 32 | }); 33 | const expectedChecksum = (await fs.readFile(checksumFile, 'utf-8')) 34 | .split('\n') 35 | .find((l) => l.includes(filename)) 36 | ?.split(' ')[0]; 37 | 38 | const file = await this.http.download({ 39 | url: `${baseUrl}${filename}`, 40 | checksumType: 'sha256', 41 | expectedChecksum, 42 | }); 43 | 44 | await this.pathSvc.ensureToolPath(this.name); 45 | 46 | const path = join( 47 | await this.pathSvc.createVersionedToolPath(this.name, version), 48 | 'bin', 49 | ); 50 | await fs.mkdir(path); 51 | await this.compress.extract({ 52 | file, 53 | cwd: path, 54 | }); 55 | } 56 | 57 | override async link(version: string): Promise { 58 | const src = join(this.pathSvc.versionedToolPath(this.name, version), 'bin'); 59 | await this.shellwrapper({ srcDir: src }); 60 | } 61 | 62 | override async test(_version: string): Promise { 63 | await execa(this.name, ['version'], { stdio: ['inherit', 'inherit', 1] }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/cli/tools/flux.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import { join } from 'node:path'; 3 | import { execa } from 'execa'; 4 | import { inject, injectable } from 'inversify'; 5 | import { BaseInstallService } from '../install-tool/base-install.service'; 6 | import { 7 | CompressionService, 8 | EnvService, 9 | HttpService, 10 | PathService, 11 | } from '../services'; 12 | 13 | @injectable() 14 | export class FluxInstallService extends BaseInstallService { 15 | readonly name = 'flux'; 16 | 17 | private get arch(): string { 18 | return this.envSvc.arch; 19 | } 20 | 21 | constructor( 22 | @inject(EnvService) envSvc: EnvService, 23 | @inject(PathService) pathSvc: PathService, 24 | @inject(HttpService) private http: HttpService, 25 | @inject(CompressionService) private compress: CompressionService, 26 | ) { 27 | super(pathSvc, envSvc); 28 | } 29 | 30 | override async install(version: string): Promise { 31 | const baseUrl = `https://github.com/fluxcd/flux2/releases/download/v${version}/`; 32 | const filename = `flux_${version}_linux_${this.arch}.tar.gz`; 33 | 34 | const checksumFile = await this.http.download({ 35 | url: `${baseUrl}flux_${version}_checksums.txt`, 36 | }); 37 | const expectedChecksum = (await fs.readFile(checksumFile, 'utf-8')) 38 | .split('\n') 39 | .find((l) => l.includes(filename)) 40 | ?.split(' ')[0]; 41 | 42 | const file = await this.http.download({ 43 | url: `${baseUrl}${filename}`, 44 | checksumType: 'sha256', 45 | expectedChecksum, 46 | }); 47 | 48 | await this.pathSvc.ensureToolPath(this.name); 49 | 50 | const path = join( 51 | await this.pathSvc.createVersionedToolPath(this.name, version), 52 | 'bin', 53 | ); 54 | await fs.mkdir(path); 55 | await this.compress.extract({ 56 | file, 57 | cwd: path, 58 | }); 59 | } 60 | 61 | override async link(version: string): Promise { 62 | const src = join(this.pathSvc.versionedToolPath(this.name, version), 'bin'); 63 | 64 | await this.shellwrapper({ srcDir: src }); 65 | } 66 | 67 | override async test(_version: string): Promise { 68 | await execa('flux', ['--version'], { stdio: ['inherit', 'inherit', 1] }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/cli/tools/index.ts: -------------------------------------------------------------------------------- 1 | import type { InstallToolType } from '../install-tool'; 2 | 3 | export const NoPrepareTools = [ 4 | 'bazelisk', 5 | 'bower', 6 | 'bun', 7 | 'bundler', 8 | 'checkov', 9 | 'cocoapods', 10 | 'composer', 11 | 'copier', 12 | 'corepack', 13 | 'devbox', 14 | 'flux', 15 | 'gleam', 16 | 'gradle', 17 | 'hashin', 18 | 'helm', 19 | 'helmfile', 20 | 'kubectl', 21 | 'kustomize', 22 | 'lerna', 23 | 'maven', 24 | 'npm', 25 | 'pdm', 26 | 'pip-tools', 27 | 'pipenv', 28 | 'pnpm', 29 | 'pixi', 30 | 'poetry', 31 | 'protoc', 32 | 'renovate', 33 | 'skopeo', 34 | 'sops', 35 | 'uv', 36 | 'wally', 37 | 'yarn', 38 | 'yarn-slim', 39 | ]; 40 | 41 | export const NoInitTools = [...NoPrepareTools]; 42 | 43 | /** 44 | * Tools in this map are implicit mapped from `install-tool` to `install-`. 45 | * So no need for an extra install service. 46 | */ 47 | export const ResolverMap: Record = { 48 | bundler: 'gem', 49 | checkov: 'pip', 50 | copier: 'pip', 51 | corepack: 'npm', 52 | hashin: 'pip', 53 | npm: 'npm', 54 | pnpm: 'npm', 55 | pdm: 'pip', 56 | 'pip-tools': 'pip', 57 | pipenv: 'pip', 58 | poetry: 'pip', 59 | uv: 'pip', 60 | }; 61 | 62 | /** 63 | * This tools are deprecated and should not be used anymore via `install-tool`. 64 | * They are implicit mapped from `install-tool` to `install-`. 65 | */ 66 | export const DeprecatedTools: Record = { 67 | bower: 'npm', 68 | lerna: 'npm', 69 | }; 70 | -------------------------------------------------------------------------------- /src/cli/tools/java/resolver.ts: -------------------------------------------------------------------------------- 1 | import { isNonEmptyStringAndNotWhitespace } from '@sindresorhus/is'; 2 | import { injectable } from 'inversify'; 3 | import { ToolVersionResolver } from '../../install-tool/tool-version-resolver'; 4 | import { resolveLatestJavaLtsVersion } from './utils'; 5 | 6 | @injectable() 7 | export class JavaVersionResolver extends ToolVersionResolver { 8 | readonly tool: string = 'java'; 9 | 10 | async resolve(version: string | undefined): Promise { 11 | if (!isNonEmptyStringAndNotWhitespace(version) || version === 'latest') { 12 | // we know that the latest version is the first entry, so search for first lts 13 | return await resolveLatestJavaLtsVersion( 14 | this.http, 15 | this.tool === 'java-jre' ? 'jre' : 'jdk', 16 | this.env.arch, 17 | ); 18 | } 19 | return version; 20 | } 21 | } 22 | 23 | @injectable() 24 | export class JavaJreVersionResolver extends JavaVersionResolver { 25 | override readonly tool = 'java-jre'; 26 | } 27 | 28 | @injectable() 29 | export class JavaJdkVersionResolver extends JavaVersionResolver { 30 | override readonly tool = 'java-jdk'; 31 | } 32 | -------------------------------------------------------------------------------- /src/cli/tools/java/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | // https://api.adoptium.net/q/swagger-ui 4 | 5 | const AdoptiumVersionData = z.object({ 6 | semver: z.string(), 7 | }); 8 | 9 | export const AdoptiumReleaseVersions = z.object({ 10 | versions: z.array(AdoptiumVersionData), 11 | }); 12 | 13 | const AdoptiumPackage = z.object({ 14 | /** 15 | * sha256 checksum 16 | */ 17 | checksum: z.string(), 18 | link: z.string(), 19 | name: z.string(), 20 | }); 21 | 22 | export type AdoptiumPackage = z.infer; 23 | 24 | const AdoptiumBinary = z.object({ 25 | package: AdoptiumPackage, 26 | }); 27 | 28 | const AdoptiumRelease = z.object({ 29 | binaries: z.array(AdoptiumBinary), 30 | }); 31 | 32 | export const AdoptiumReleases = z.array(AdoptiumRelease); 33 | 34 | export const GradleVersionData = z.object({ 35 | version: z.string(), 36 | }); 37 | -------------------------------------------------------------------------------- /src/cli/tools/kubectl.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import { join } from 'node:path'; 3 | import { execa } from 'execa'; 4 | import { inject, injectable } from 'inversify'; 5 | import { BaseInstallService } from '../install-tool/base-install.service'; 6 | import { EnvService, HttpService, PathService } from '../services'; 7 | 8 | @injectable() 9 | export class KubectlInstallService extends BaseInstallService { 10 | readonly name = 'kubectl'; 11 | 12 | constructor( 13 | @inject(EnvService) envSvc: EnvService, 14 | @inject(PathService) pathSvc: PathService, 15 | @inject(HttpService) private http: HttpService, 16 | ) { 17 | super(pathSvc, envSvc); 18 | } 19 | 20 | override async install(version: string): Promise { 21 | const baseUrl = `https://dl.k8s.io/release/v${version}/bin/linux/${this.envSvc.arch}/`; 22 | const filename = this.name; 23 | 24 | const checksumFile = await this.http.download({ 25 | url: `${baseUrl}${filename}.sha256`, 26 | fileName: `${filename}-v${version}-${this.envSvc.arch}.sha256`, 27 | }); 28 | const expectedChecksum = (await fs.readFile(checksumFile, 'utf-8')) 29 | .split('\n') 30 | .find((l) => l.includes(filename)) 31 | ?.split(' ')[0]; 32 | 33 | const file = await this.http.download({ 34 | url: `${baseUrl}${filename}`, 35 | fileName: `${filename}-v${version}-${this.envSvc.arch}`, 36 | checksumType: 'sha256', 37 | expectedChecksum, 38 | }); 39 | 40 | await this.pathSvc.ensureToolPath(this.name); 41 | 42 | const path = join( 43 | await this.pathSvc.createVersionedToolPath(this.name, version), 44 | 'bin', 45 | ); 46 | await fs.mkdir(path); 47 | await fs.copyFile(file, join(path, filename)); 48 | await fs.chmod(join(path, filename), this.envSvc.umask); 49 | } 50 | 51 | override async link(version: string): Promise { 52 | const src = join(this.pathSvc.versionedToolPath(this.name, version), 'bin'); 53 | 54 | await this.shellwrapper({ srcDir: src }); 55 | } 56 | 57 | override async test(_version: string): Promise { 58 | await execa(this.name, ['version', '--client'], { 59 | stdio: ['inherit', 'inherit', 1], 60 | }); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/cli/tools/kustomize.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import path from 'node:path'; 3 | import { execa } from 'execa'; 4 | import { inject, injectable } from 'inversify'; 5 | import { BaseInstallService } from '../install-tool/base-install.service'; 6 | import { 7 | CompressionService, 8 | EnvService, 9 | HttpService, 10 | PathService, 11 | } from '../services'; 12 | 13 | @injectable() 14 | export class KustomizeInstallService extends BaseInstallService { 15 | readonly name = 'kustomize'; 16 | 17 | constructor( 18 | @inject(EnvService) envSvc: EnvService, 19 | @inject(PathService) pathSvc: PathService, 20 | @inject(HttpService) private http: HttpService, 21 | @inject(CompressionService) private compress: CompressionService, 22 | ) { 23 | super(pathSvc, envSvc); 24 | } 25 | 26 | override async install(version: string): Promise { 27 | const name = this.name; 28 | const filename = `${name}_v${version}_linux_${this.envSvc.arch}.tar.gz`; 29 | const baseUrl = `https://github.com/kubernetes-sigs/${name}/releases/download/${name}%2Fv${version}/`; 30 | 31 | const checksumFile = await this.http.download({ 32 | url: `${baseUrl}checksums.txt`, 33 | fileName: `${name}_v${version}_checksums.txt`, 34 | }); 35 | const expectedChecksum = (await fs.readFile(checksumFile, 'utf-8')) 36 | .split('\n') 37 | .find((l) => l.includes(filename)) 38 | ?.split(' ')[0]; 39 | 40 | const file = await this.http.download({ 41 | url: `${baseUrl}${filename}`, 42 | checksumType: 'sha256', 43 | expectedChecksum, 44 | }); 45 | await this.pathSvc.ensureToolPath(this.name); 46 | const cwd = path.join( 47 | await this.pathSvc.createVersionedToolPath(this.name, version), 48 | 'bin', 49 | ); 50 | await fs.mkdir(cwd); 51 | await this.compress.extract({ file, cwd }); 52 | } 53 | 54 | override async link(version: string): Promise { 55 | const src = path.join( 56 | this.pathSvc.versionedToolPath(this.name, version), 57 | 'bin', 58 | ); 59 | await this.shellwrapper({ srcDir: src }); 60 | } 61 | 62 | override async test(_version: string): Promise { 63 | await execa(this.name, ['version'], { 64 | stdio: ['inherit', 'inherit', 1], 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/cli/tools/node/npm.ts: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa'; 2 | import { injectable } from 'inversify'; 3 | import { logger, parse, semverSatisfies } from '../../utils'; 4 | import { NpmBaseInstallService } from './utils'; 5 | 6 | @injectable() 7 | export class RenovateInstallService extends NpmBaseInstallService { 8 | override readonly name: string = 'renovate'; 9 | 10 | override prepareEnv(version: string, tmp: string): NodeJS.ProcessEnv { 11 | const env = super.prepareEnv(version, tmp); 12 | 13 | if (semverSatisfies(version, '<37.234.0')) { 14 | env.RE2_DOWNLOAD_MIRROR = this.envSvc.replaceUrl( 15 | 'https://github.com/containerbase/node-re2-prebuild/releases/download', 16 | ); 17 | env.RE2_DOWNLOAD_SKIP_PATH = '1'; 18 | } 19 | return env; 20 | } 21 | } 22 | 23 | @injectable() 24 | export class YarnInstallService extends NpmBaseInstallService { 25 | override readonly name: string = 'yarn'; 26 | 27 | protected override tool(version: string): string { 28 | const ver = parse(version); 29 | if (ver.major >= 2) { 30 | logger.debug({ version }, 'Using yarnpkg/cli-dist'); 31 | return '@yarnpkg/cli-dist'; 32 | } 33 | return this.name; 34 | } 35 | 36 | override async test(): Promise { 37 | await execa(this.name, ['--version'], { stdio: 'inherit' }); 38 | } 39 | } 40 | 41 | @injectable() 42 | export class YarnSlimInstallService extends NpmBaseInstallService { 43 | override readonly name: string = 'yarn-slim'; 44 | 45 | protected override tool(): string { 46 | return 'yarn'; 47 | } 48 | 49 | override async install(version: string): Promise { 50 | await super.install(version); 51 | const node = await this.getNodeVersion(); 52 | // TODO: replace with javascript 53 | const prefix = this.pathSvc.versionedToolPath(this.name, version); 54 | await execa( 55 | 'sed', 56 | [ 57 | '-i', 58 | 's/ steps,/ steps.slice(0,1),/', 59 | `${prefix}/${node}/node_modules/yarn/lib/cli.js`, 60 | ], 61 | { stdio: ['inherit', 'inherit', 1] }, 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/cli/tools/node/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const NodeVersionMeta = z.object({ 4 | version: z.string(), 5 | lts: z.union([z.string(), z.boolean()]).optional(), 6 | }); 7 | export type NodeVersionMeta = z.infer; 8 | 9 | export const NpmPackageMetaList = z.array(NodeVersionMeta); 10 | 11 | export const NpmPackageMeta = z.object({ 12 | 'dist-tags': z.record(z.string()), 13 | name: z.string(), 14 | }); 15 | 16 | export type NpmPackageMeta = z.infer; 17 | -------------------------------------------------------------------------------- /src/cli/tools/php/__mocks__/composer.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from 'inversify'; 2 | import { BaseInstallService } from '../../../install-tool/base-install.service'; 3 | import { ToolVersionResolver } from '../../../install-tool/tool-version-resolver'; 4 | import { EnvService, PathService } from '../../../services'; 5 | 6 | @injectable() 7 | export class ComposerVersionResolver extends ToolVersionResolver { 8 | readonly tool = 'composer'; 9 | 10 | resolve(version: string | undefined): Promise { 11 | return Promise.resolve(version); 12 | } 13 | } 14 | 15 | @injectable() 16 | export class ComposerInstallService extends BaseInstallService { 17 | readonly name = 'composer'; 18 | 19 | constructor( 20 | @inject(PathService) pathSvc: PathService, 21 | @inject(EnvService) envSvc: EnvService, 22 | ) { 23 | super(pathSvc, envSvc); 24 | } 25 | 26 | override isInstalled(_version: string): Promise { 27 | return Promise.resolve(false); 28 | } 29 | 30 | override install(_version: string): Promise { 31 | return Promise.resolve(); 32 | } 33 | 34 | override link(_version: string): Promise { 35 | return Promise.resolve(); 36 | } 37 | 38 | override test(_version: string): Promise { 39 | return Promise.resolve(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/cli/tools/protoc.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { execa } from 'execa'; 3 | import { inject, injectable } from 'inversify'; 4 | import { BaseInstallService } from '../install-tool/base-install.service'; 5 | import { 6 | CompressionService, 7 | EnvService, 8 | HttpService, 9 | PathService, 10 | } from '../services'; 11 | import { semverCoerce } from '../utils'; 12 | 13 | @injectable() 14 | export class ProtocInstallService extends BaseInstallService { 15 | readonly name = 'protoc'; 16 | 17 | private get ghArch(): string { 18 | switch (this.envSvc.arch) { 19 | case 'arm64': 20 | return 'aarch_64'; 21 | case 'amd64': 22 | return 'x86_64'; 23 | } 24 | } 25 | 26 | constructor( 27 | @inject(EnvService) envSvc: EnvService, 28 | @inject(PathService) pathSvc: PathService, 29 | @inject(HttpService) private http: HttpService, 30 | @inject(CompressionService) private compress: CompressionService, 31 | ) { 32 | super(pathSvc, envSvc); 33 | } 34 | 35 | override async install(version: string): Promise { 36 | const name = this.name; 37 | 38 | const url = `https://github.com/protocolbuffers/protobuf/releases/download/v${version}/${name}-${version}-linux-${this.ghArch}.zip`; 39 | 40 | const file = await this.http.download({ 41 | url, 42 | }); 43 | 44 | const cwd = await this.pathSvc.createVersionedToolPath(name, version); 45 | 46 | await this.compress.extract({ file, cwd }); 47 | } 48 | 49 | override async link(version: string): Promise { 50 | const src = path.join( 51 | this.pathSvc.versionedToolPath(this.name, version), 52 | 'bin', 53 | ); 54 | await this.shellwrapper({ srcDir: src }); 55 | } 56 | 57 | override async test(_version: string): Promise { 58 | await execa(this.name, ['--version'], { 59 | stdio: ['inherit', 'inherit', 1], 60 | }); 61 | } 62 | 63 | override validate(version: string): Promise { 64 | return Promise.resolve(semverCoerce(version) !== null); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/cli/tools/python/pip.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from 'inversify'; 2 | import { ToolVersionResolver } from '../../install-tool/tool-version-resolver'; 3 | import { PypiJson } from './schema'; 4 | 5 | @injectable() 6 | export abstract class PipVersionResolver extends ToolVersionResolver { 7 | async resolve(version: string | undefined): Promise { 8 | if (version === undefined || version === 'latest') { 9 | const meta = await this.fetchMeta(this.tool); 10 | return meta.info.version; 11 | } 12 | return version; 13 | } 14 | 15 | protected async fetchMeta(tool: string): Promise { 16 | return PypiJson.parse( 17 | await this.http.getJson( 18 | `https://pypi.org/pypi/${normalizePythonDepName(tool)}/json`, 19 | ), 20 | ); 21 | } 22 | } 23 | 24 | // https://packaging.python.org/en/latest/specifications/name-normalization/ 25 | export function normalizePythonDepName(name: string): string { 26 | return name.replace(/[-_.]+/g, '-').toLowerCase(); 27 | } 28 | -------------------------------------------------------------------------------- /src/cli/tools/python/poetry.ts: -------------------------------------------------------------------------------- 1 | import { maxSatisfying } from '@renovatebot/pep440'; 2 | import { injectable } from 'inversify'; 3 | import { logger } from '../../utils'; 4 | import { PipVersionResolver } from './pip'; 5 | 6 | @injectable() 7 | export class PoetryVersionResolver extends PipVersionResolver { 8 | override tool = 'poetry'; 9 | 10 | override async resolve( 11 | version: string | undefined, 12 | ): Promise { 13 | if (version === undefined || version === 'latest') { 14 | const mirrorMeta = await this.fetchMeta('poetry-plugin-pypi-mirror'); 15 | logger.debug({ info: mirrorMeta.info }, 'poetry-plugin-pypi-mirror'); 16 | 17 | const poetryVersion = mirrorMeta.info.requires_dist?.poetry; 18 | 19 | if (!poetryVersion) { 20 | throw new Error('poetry-plugin-pypi-mirror has missing poetry version'); 21 | } 22 | 23 | const meta = await this.fetchMeta(this.tool); 24 | const version = maxSatisfying( 25 | Object.keys(meta.releases).filter((v) => !meta.releases[v]!.yanked), 26 | poetryVersion, 27 | ); 28 | logger.debug({ version }, 'Resolved poetry version'); 29 | return version ?? meta.info.version; 30 | } 31 | return version; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/cli/tools/python/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { logger } from '../../utils'; 3 | 4 | // function fixPythonVersion(version: string): string { 5 | // return version; //.replace('\u003C', '<').replace('\u003E', '>'); 6 | // } 7 | 8 | const depRe = /^(?[a-z-]+)(?\[[a-z,]+\])?(?.+)(?:$|;)/; 9 | 10 | function parseDep(dep: string): [string, string] | null { 11 | const groups = depRe.exec(dep)?.groups; 12 | if (!groups) { 13 | logger.debug({ dep }, 'Failed to parse dependency'); 14 | return null; 15 | } 16 | return [groups.name!, groups.version!]; 17 | } 18 | 19 | const PypiRelease = z.object({ 20 | packagetype: z.enum(['sdist', 'bdist_wheel', 'unknown']).catch('unknown'), 21 | requires_python: z.string().nullish(), 22 | yanked: z.boolean(), 23 | }); 24 | 25 | export const PypiJson = z.object({ 26 | info: z.object({ 27 | version: z.string(), 28 | requires_python: z.string().nullish(), 29 | requires_dist: z 30 | .array(z.string().transform(parseDep)) 31 | .transform((v) => Object.fromEntries(v.filter((d) => !!d))) 32 | .nullish(), 33 | }), 34 | releases: z.record(z.array(PypiRelease).transform((v) => v[0])), 35 | }); 36 | 37 | export type PypiJson = z.infer; 38 | -------------------------------------------------------------------------------- /src/cli/tools/ruby/cocoapods.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'node:path'; 2 | import { execa } from 'execa'; 3 | import { injectable } from 'inversify'; 4 | import { semverSatisfies } from '../../utils'; 5 | import { RubyBaseInstallService, RubyGemVersionResolver } from './utils'; 6 | 7 | @injectable() 8 | export class CocoapodsInstallService extends RubyBaseInstallService { 9 | override readonly name: string = 'cocoapods'; 10 | 11 | override async test(_version: string): Promise { 12 | await execa('pod', ['--version', '--allow-root'], { stdio: 'inherit' }); 13 | } 14 | 15 | protected override async _postInstall( 16 | gem: string, 17 | version: string, 18 | prefix: string, 19 | env: NodeJS.ProcessEnv, 20 | ): Promise { 21 | // https://github.com/containerbase/base/issues/1547 22 | if (!semverSatisfies(version, '1.12.0 - 1.13.0')) { 23 | return; 24 | } 25 | 26 | await execa( 27 | gem, 28 | [ 29 | 'install', 30 | 'activesupport', 31 | '--install-dir', 32 | prefix, 33 | '--bindir', 34 | join(prefix, 'bin'), 35 | '--version', 36 | '<7.1.0', 37 | ], 38 | { stdio: ['inherit', 'inherit', 1], env, cwd: this.pathSvc.installDir }, 39 | ); 40 | 41 | await execa( 42 | gem, 43 | [ 44 | 'uninstall', 45 | 'activesupport', 46 | '--install-dir', 47 | prefix, 48 | '--bindir', 49 | join(prefix, 'bin'), 50 | '--version', 51 | '>=7.1.0', 52 | ], 53 | { stdio: ['inherit', 'inherit', 1], env, cwd: this.pathSvc.installDir }, 54 | ); 55 | } 56 | } 57 | 58 | @injectable() 59 | export class CocoapodsVersionResolver extends RubyGemVersionResolver { 60 | override readonly tool: string = 'cocoapods'; 61 | } 62 | -------------------------------------------------------------------------------- /src/cli/tools/ruby/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const RubyGemJson = z.object({ 4 | version: z.string(), 5 | }); 6 | -------------------------------------------------------------------------------- /src/cli/tools/skopeo.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import { join } from 'node:path'; 3 | import { execa } from 'execa'; 4 | import { inject, injectable } from 'inversify'; 5 | import { BaseInstallService } from '../install-tool/base-install.service'; 6 | import { 7 | CompressionService, 8 | EnvService, 9 | HttpService, 10 | PathService, 11 | } from '../services'; 12 | 13 | @injectable() 14 | export class SkopeoInstallService extends BaseInstallService { 15 | readonly name = 'skopeo'; 16 | 17 | private get ghArch(): string { 18 | switch (this.envSvc.arch) { 19 | case 'arm64': 20 | return 'aarch64'; 21 | case 'amd64': 22 | return 'x86_64'; 23 | } 24 | } 25 | 26 | constructor( 27 | @inject(EnvService) envSvc: EnvService, 28 | @inject(PathService) pathSvc: PathService, 29 | @inject(HttpService) private http: HttpService, 30 | @inject(CompressionService) private compress: CompressionService, 31 | ) { 32 | super(pathSvc, envSvc); 33 | } 34 | 35 | override async install(version: string): Promise { 36 | const name = this.name; 37 | const filename = `${name}-${version}-${this.ghArch}.tar.xz`; 38 | const url = `https://github.com/containerbase/${name}-prebuild/releases/download/${version}/${filename}`; 39 | const checksumFileUrl = `${url}.sha512`; 40 | 41 | const checksumFile = await this.http.download({ url: checksumFileUrl }); 42 | const expectedChecksum = (await fs.readFile(checksumFile, 'utf-8')).trim(); 43 | const file = await this.http.download({ 44 | url, 45 | checksumType: 'sha512', 46 | expectedChecksum, 47 | }); 48 | await this.compress.extract({ file, cwd: await this.getToolPath() }); 49 | } 50 | 51 | override async link(version: string): Promise { 52 | const src = join(this.pathSvc.versionedToolPath(this.name, version), 'bin'); 53 | await this.shellwrapper({ srcDir: src }); 54 | } 55 | 56 | override async test(_version: string): Promise { 57 | await execa('skopeo', ['--version'], { 58 | stdio: ['inherit', 'inherit', 1], 59 | }); 60 | } 61 | 62 | private async getToolPath(): Promise { 63 | return await this.pathSvc.ensureToolPath(this.name); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/cli/tools/sops.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import { join } from 'node:path'; 3 | import { execa } from 'execa'; 4 | import { inject, injectable } from 'inversify'; 5 | import { BaseInstallService } from '../install-tool/base-install.service'; 6 | import { EnvService, HttpService, PathService } from '../services'; 7 | 8 | @injectable() 9 | export class SopsInstallService extends BaseInstallService { 10 | readonly name = 'sops'; 11 | 12 | constructor( 13 | @inject(EnvService) envSvc: EnvService, 14 | @inject(PathService) pathSvc: PathService, 15 | @inject(HttpService) private http: HttpService, 16 | ) { 17 | super(pathSvc, envSvc); 18 | } 19 | 20 | override async install(version: string): Promise { 21 | const baseUrl = `https://github.com/getsops/${this.name}/releases/download/v${version}/`; 22 | const filename = `${this.name}-v${version}.linux.${this.envSvc.arch}`; 23 | 24 | const checksumFile = await this.http.download({ 25 | url: `${baseUrl}${this.name}-v${version}.checksums.txt`, 26 | }); 27 | const expectedChecksum = (await fs.readFile(checksumFile, 'utf-8')) 28 | .split('\n') 29 | .find((l) => l.includes(filename)) 30 | ?.split(' ')[0]; 31 | 32 | const file = await this.http.download({ 33 | url: `${baseUrl}${filename}`, 34 | checksumType: 'sha256', 35 | expectedChecksum, 36 | }); 37 | 38 | await this.pathSvc.ensureToolPath(this.name); 39 | 40 | const path = join( 41 | await this.pathSvc.createVersionedToolPath(this.name, version), 42 | 'bin', 43 | ); 44 | await fs.mkdir(path); 45 | await fs.copyFile(file, join(path, this.name)); 46 | await fs.chmod(join(path, this.name), this.envSvc.umask); 47 | } 48 | 49 | override async link(version: string): Promise { 50 | const src = join(this.pathSvc.versionedToolPath(this.name, version), 'bin'); 51 | 52 | await this.shellwrapper({ srcDir: src }); 53 | } 54 | 55 | override async test(_version: string): Promise { 56 | await execa(this.name, ['--version'], { 57 | stdio: ['inherit', 'inherit', 1], 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/cli/types.d.ts: -------------------------------------------------------------------------------- 1 | interface Error { 2 | code?: string; 3 | } 4 | 5 | /* eslint-disable no-var */ 6 | declare var rootDir: string | undefined; 7 | -------------------------------------------------------------------------------- /src/cli/utils/codes.ts: -------------------------------------------------------------------------------- 1 | export const MissingVersion = 15; 2 | -------------------------------------------------------------------------------- /src/cli/utils/hash.spec.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import { env } from 'node:process'; 3 | import { describe, expect, test } from 'vitest'; 4 | import { hash, hashFile } from './hash'; 5 | 6 | describe('cli/utils/hash', () => { 7 | test('should hash data with sha256', () => { 8 | expect(hash('https://example.com/test.txt', 'sha256')).toBe( 9 | 'd1dc63218c42abba594fff6450457dc8c4bfdd7c22acf835a50ca0e5d2693020', 10 | ); 11 | }); 12 | 13 | test('should hash file with sha256', async () => { 14 | const file = `${env.CONTAINERBASE_CACHE_DIR}/test.txt`; 15 | await fs.writeFile(file, 'https://example.com/test.txt'); 16 | expect(await hashFile(file, 'sha256')).toBe( 17 | 'd1dc63218c42abba594fff6450457dc8c4bfdd7c22acf835a50ca0e5d2693020', 18 | ); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/cli/utils/hash.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'node:crypto'; 2 | import fs from 'node:fs/promises'; 3 | import type { LiteralUnion } from 'type-fest'; 4 | 5 | export type AlgorithmName = LiteralUnion< 6 | 'sha1' | 'sha224' | 'sha256' | 'sha384' | 'sha512', 7 | string 8 | >; 9 | 10 | export function hash(data: string | Buffer, algorithm: AlgorithmName): string { 11 | const hash = crypto.createHash(algorithm); 12 | hash.update(data); 13 | return hash.digest('hex'); 14 | } 15 | 16 | export async function hashFile( 17 | file: string, 18 | algorithm: AlgorithmName, 19 | ): Promise { 20 | const data = await fs.readFile(file); 21 | const hash = crypto.createHash(algorithm); 22 | hash.update(data); 23 | return hash.digest('hex'); 24 | } 25 | -------------------------------------------------------------------------------- /src/cli/utils/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test, vi } from 'vitest'; 2 | import { cliMode } from '.'; 3 | 4 | const procMocks = vi.hoisted(() => ({ argv0: '', env: {} })); 5 | vi.mock('node:process', () => procMocks); 6 | 7 | describe('cli/utils/index', () => { 8 | test('cliMode', async () => { 9 | expect(cliMode()).toBeNull(); 10 | procMocks.argv0 = 'containerbase-cli'; 11 | expect((await import('.')).cliMode()).toBe('containerbase-cli'); 12 | procMocks.argv0 = 'install-gem'; 13 | expect((await import('.')).cliMode()).toBe('install-gem'); 14 | procMocks.argv0 = 'install-npm'; 15 | expect((await import('.')).cliMode()).toBe('install-npm'); 16 | procMocks.argv0 = 'install-tool'; 17 | expect((await import('.')).cliMode()).toBe('install-tool'); 18 | procMocks.argv0 = 'prepare-tool'; 19 | expect((await import('.')).cliMode()).toBe('prepare-tool'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/cli/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { argv0 } from 'node:process'; 2 | import type { CliMode } from './types'; 3 | 4 | export type * from './types'; 5 | export * from './versions'; 6 | export * from './logger'; 7 | export * from './common'; 8 | 9 | export function cliMode(): CliMode | null { 10 | if (argv0.endsWith('/containerbase-cli') || argv0 === 'containerbase-cli') { 11 | return 'containerbase-cli'; 12 | } 13 | if (argv0.endsWith('/install-gem') || argv0 === 'install-gem') { 14 | return 'install-gem'; 15 | } 16 | if (argv0.endsWith('/install-npm') || argv0 === 'install-npm') { 17 | return 'install-npm'; 18 | } 19 | if (argv0.endsWith('/install-pip') || argv0 === 'install-pip') { 20 | return 'install-pip'; 21 | } 22 | if (argv0.endsWith('/install-tool') || argv0 === 'install-tool') { 23 | return 'install-tool'; 24 | } 25 | if (argv0.endsWith('/prepare-tool') || argv0 === 'prepare-tool') { 26 | return 'prepare-tool'; 27 | } 28 | 29 | return null; 30 | } 31 | -------------------------------------------------------------------------------- /src/cli/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'node:process'; 2 | import { isNonEmptyStringAndNotWhitespace } from '@sindresorhus/is'; 3 | import { type TransportTargetOptions, levels, pino, transport } from 'pino'; 4 | 5 | const level = 6 | [ 7 | env.CONTAINERBASE_LOG_LEVEL, 8 | env.CONTAINERBASE_DEBUG ? 'debug' : undefined, 9 | env.LOG_LEVEL, 10 | ] 11 | .filter(isNonEmptyStringAndNotWhitespace) 12 | .shift() ?? 'info'; 13 | 14 | const format = 15 | [env.CONTAINERBASE_LOG_FORMAT, env.LOG_FORMAT] 16 | .filter(isNonEmptyStringAndNotWhitespace) 17 | .shift() 18 | ?.toLowerCase() ?? 'pretty'; 19 | 20 | const stdoutTransportTarget = format === 'json' ? 'pino/file' : 'pino-pretty'; 21 | 22 | let fileLevel = 'silent'; 23 | 24 | const targets: TransportTargetOptions[] = [ 25 | { target: stdoutTransportTarget, level, options: {} }, 26 | ]; 27 | 28 | if (isNonEmptyStringAndNotWhitespace(env.CONTAINERBASE_LOG_FILE)) { 29 | fileLevel = env.CONTAINERBASE_LOG_FILE_LEVEL ?? 'debug'; 30 | targets.push({ 31 | target: 'pino/file', 32 | level: fileLevel, 33 | options: { 34 | destination: env.CONTAINERBASE_LOG_FILE, 35 | }, 36 | }); 37 | } 38 | 39 | const transports = transport({ 40 | targets, 41 | }); 42 | 43 | const numLevel = levels.values[level] ?? Infinity; 44 | const numFileLevel = levels.values[fileLevel] ?? Infinity; 45 | export const logger = pino( 46 | { level: numLevel < numFileLevel ? level : fileLevel }, 47 | transports, 48 | ); 49 | -------------------------------------------------------------------------------- /src/cli/utils/types.ts: -------------------------------------------------------------------------------- 1 | export interface Distro { 2 | readonly name: string; 3 | readonly versionCode: string; 4 | readonly versionId: string; 5 | } 6 | 7 | export type CliMode = 8 | | 'containerbase-cli' 9 | | 'install-gem' 10 | | 'install-npm' 11 | | 'install-pip' 12 | | 'install-tool' 13 | | 'prepare-tool'; 14 | 15 | export type Arch = 'arm64' | 'amd64'; 16 | -------------------------------------------------------------------------------- /src/cli/utils/versions.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { isValid, parse } from './versions'; 3 | 4 | describe('cli/utils/versions', () => { 5 | test('isValid', () => { 6 | expect(isValid('1.0.0')).toBe(true); 7 | expect(isValid('abc')).toBe(false); 8 | }); 9 | 10 | test('parse', () => { 11 | expect(parse('1.0.0')).not.toBeNull(); 12 | expect(() => parse('abc')).toThrow('Invalid version: abc'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/cli/utils/versions.ts: -------------------------------------------------------------------------------- 1 | import type SemVer from 'semver/classes/semver'; 2 | import semverCoerce from 'semver/functions/coerce'; 3 | import semverGte from 'semver/functions/gte'; 4 | import semverParse from 'semver/functions/parse'; 5 | import semverSatisfies from 'semver/functions/satisfies'; 6 | import semverSort from 'semver/functions/sort'; 7 | import semverValid from 'semver/functions/valid'; 8 | 9 | export { semverGte, semverSort, semverCoerce, semverSatisfies }; 10 | 11 | export function isValid(version: string): boolean { 12 | return semverValid(version) !== null; 13 | } 14 | 15 | export function parse(version: string | undefined): SemVer { 16 | const res = semverParse(version); 17 | if (!res) { 18 | throw new Error(`Invalid version: ${version}`); 19 | } 20 | return res; 21 | } 22 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/bin/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ -f "/usr/local/etc/env" && -z "${CONTAINERBASE_ENV+x}" ]]; then 4 | # shellcheck source=/dev/null 5 | . /usr/local/etc/env 6 | fi 7 | 8 | if [[ -n "${CONTAINERBASE_CLEANUP_PATH}" ]]; then 9 | # cleanup path via https://www.npmjs.com/package/del 10 | containerbase-cli cleanup path "${CONTAINERBASE_CLEANUP_PATH}" 11 | fi 12 | 13 | if [[ ! -d "/tmp/containerbase" ]]; then 14 | # initialize all prepared tools 15 | containerbase-cli init tool all 16 | fi 17 | 18 | exec dumb-init -- "$@" 19 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/bin/init-tool.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # shellcheck source=/dev/null 6 | . /usr/local/containerbase/util.sh 7 | 8 | function main() { 9 | local TOOL_NAME 10 | 11 | TOOL_NAME=${1} 12 | check TOOL_NAME true 13 | 14 | V2_TOOL="${CONTAINERBASE_DIR}/tools/v2/${TOOL_NAME}.sh" 15 | 16 | if [[ -f "$V2_TOOL" ]]; then 17 | # init v2 tool 18 | # load overrides needed for v2 tools 19 | # shellcheck source=/dev/null 20 | . "${CONTAINERBASE_DIR}/utils/v2/overrides.sh" 21 | # shellcheck source=/dev/null 22 | . "${V2_TOOL}" 23 | init_v2_tool 24 | else 25 | echo "No tool defined - skipping: ${TOOL_NAME}" >&2 26 | exit 1; 27 | fi 28 | } 29 | 30 | main "$@" 31 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/bin/install-apt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # shellcheck source=/dev/null 6 | . /usr/local/containerbase/util.sh 7 | 8 | require_root 9 | 10 | apt_install "$@" 11 | 12 | # cleanup 13 | rm -rf /var/lib/apt/lists/* /var/log/dpkg.* /var/log/apt 14 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/bin/install-tool.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # shellcheck source=/dev/null 6 | . /usr/local/containerbase/util.sh 7 | 8 | require_arch 9 | require_distro 10 | require_user 11 | require_tool "$@" 12 | 13 | 14 | TOOL="${CONTAINERBASE_DIR}/tools/${TOOL_NAME}.sh" 15 | V2_TOOL="${CONTAINERBASE_DIR}/tools/v2/${TOOL_NAME}.sh" 16 | 17 | if [[ -f "$V2_TOOL" ]]; then 18 | # install v2 tool 19 | install_v2_tool "${V2_TOOL}" 20 | elif [[ -f "$TOOL" ]]; then 21 | echo "Installing v1 tool ${TOOL_NAME} v${TOOL_VERSION}" 22 | # shellcheck source=/dev/null 23 | . "$TOOL" 24 | else 25 | echo "No tool defined - skipping: ${TOOL_NAME}" >&2 26 | exit 1; 27 | fi 28 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/bin/prepare-tool.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | 6 | # shellcheck source=/dev/null 7 | . /usr/local/containerbase/util.sh 8 | 9 | require_root 10 | prepare_tools "${@}" 11 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/tools/git.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | require_root 4 | 5 | version_codename=$(get_distro) 6 | 7 | install -m 0755 -d /etc/apt/keyrings 8 | curl --retry 3 -fsSL -o /etc/apt/keyrings/git.asc \ 9 | 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0xF911AB184317630C59970973E363C90F8F1B6217' 10 | chmod a+r /etc/apt/keyrings/git.asc 11 | 12 | echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/git.asc] http://ppa.launchpad.net/git-core/ppa/ubuntu ${version_codename} main" | tee /etc/apt/sources.list.d/git.list 13 | 14 | 15 | # TODO: Only latest version available on launchpad :-/ 16 | #apt_install git=1:${TOOL_VERSION}* 17 | 18 | apt_install git 19 | 20 | # flutter workaround 21 | git config --system safe.directory "/opt/containerbase/tools/flutter/*" 22 | 23 | [[ -n $SKIP_VERSION ]] || git --version 24 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/tools/v2/git-lfs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function check_tool_requirements () { 4 | check_semver "$TOOL_VERSION" "all" 5 | } 6 | 7 | function install_tool () { 8 | local versioned_tool_path 9 | local file 10 | local arch=linux-amd64 11 | local lfs_file 12 | local strip=0 13 | 14 | if [[ "$(uname -p)" = "aarch64" ]]; then 15 | arch=linux-arm64 16 | fi 17 | 18 | lfs_file="${TOOL_NAME}-${arch}-v${TOOL_VERSION}.tar.gz" 19 | file=$(get_from_url "https://github.com/${TOOL_NAME}/${TOOL_NAME}/releases/download/v${TOOL_VERSION}/${lfs_file}") 20 | 21 | # v3.2+ has a subdir https://github.com/git-lfs/git-lfs/pull/4980 22 | if [[ ${MAJOR} -gt 3 || (${MAJOR} -eq 3 && ${MINOR} -ge 2) ]]; then 23 | strip=1 24 | fi 25 | 26 | temp_dir="$(mktemp -d)" 27 | bsdtar --strip $strip -C "${temp_dir}" -xf "${file}" 28 | 29 | versioned_tool_path=$(create_versioned_tool_path) 30 | mkdir "${versioned_tool_path}/bin" 31 | mv "${temp_dir}/git-lfs" "${versioned_tool_path}/bin/" 32 | rm -rf "${temp_dir}" 33 | } 34 | 35 | function link_tool () { 36 | shell_wrapper "${TOOL_NAME}" "$(find_versioned_tool_path)/bin" 37 | 38 | git lfs version 39 | 40 | if [ "$(is_root)" -eq 0 ]; then 41 | git lfs install --system 42 | else 43 | git lfs install 44 | fi 45 | } 46 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/tools/v2/jb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function install_tool () { 4 | local versioned_tool_path 5 | local file 6 | local arch=linux-amd64 7 | if [[ "$(uname -p)" = "aarch64" ]]; then 8 | arch=linux-arm64 9 | fi 10 | 11 | file=$(get_from_url "https://github.com/jsonnet-bundler/jsonnet-bundler/releases/download/v${TOOL_VERSION}/${TOOL_NAME}-${arch}") 12 | 13 | versioned_tool_path=$(create_versioned_tool_path) 14 | create_folder "${versioned_tool_path}/bin" 15 | cp "${file}" "${versioned_tool_path}/bin/jb" 16 | chmod +x "${versioned_tool_path}/bin/jb" 17 | } 18 | 19 | function link_tool () { 20 | local versioned_tool_path 21 | versioned_tool_path=$(find_versioned_tool_path) 22 | 23 | shell_wrapper "${TOOL_NAME}" "${versioned_tool_path}/bin" 24 | [[ -n $SKIP_VERSION ]] || jb --version 25 | } 26 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/tools/v2/nix.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function install_tool() { 4 | local arch 5 | local checksum_file 6 | local expected_checksum 7 | local file 8 | local name=${TOOL_NAME} 9 | local tool_path 10 | local version=${TOOL_VERSION} 11 | local versioned_tool_path 12 | 13 | if [[ ${MAJOR} -lt 2 || (${MAJOR} -eq 2 && ${MINOR} -lt 10) || (${MAJOR} -eq 2 && ${MINOR} -eq 10 && ${PATCH} -lt 3) ]]; then 14 | echo "Nix version ${version} is not supported! Use v2.10.3 or higher." >&2 15 | exit 1 16 | fi 17 | 18 | arch=$(uname -m) 19 | base_url="https://github.com/containerbase/${name}-prebuild/releases/download" 20 | 21 | tool_path=$(create_tool_path) 22 | checksum_file=$(get_from_url "${base_url}/${version}/${name}-${version}-${arch}.tar.xz.sha512") 23 | expected_checksum=$(cat "${checksum_file}") 24 | file=$(get_from_url \ 25 | "${base_url}/${version}/${name}-${version}-${arch}.tar.xz" \ 26 | "${name}-${version}-${arch}.tar.xz" \ 27 | "${expected_checksum}" \ 28 | sha512sum 29 | ) 30 | 31 | bsdtar -C "${tool_path}" -xf "${file}" 32 | } 33 | 34 | function link_tool() { 35 | local versioned_tool_path 36 | versioned_tool_path=$(find_versioned_tool_path) 37 | 38 | shell_wrapper "${TOOL_NAME}" "${versioned_tool_path}/bin" "NIX_STORE_DIR=$(get_cache_path)/nix/store NIX_DATA_DIR=$(get_cache_path)/nix/data NIX_LOG_DIR=$(get_cache_path)/nix/log NIX_STATE_DIR=$(get_cache_path)/nix/state NIX_CONF_DIR=$(get_cache_path)/nix/conf" 39 | [[ -n $SKIP_VERSION ]] || nix --version 40 | } 41 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/tools/v2/powershell.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export NEEDS_PREPARE=1 4 | 5 | function prepare_tool() { 6 | local version_codename 7 | 8 | version_codename="$(get_distro)" 9 | case "${version_codename}" in 10 | "focal") apt_install libc6 libgcc1 libgssapi-krb5-2 libicu66 libssl1.1 libstdc++6 zlib1g;; 11 | "jammy") apt_install libc6 libgcc1 libgssapi-krb5-2 libicu70 libssl3 libstdc++6 zlib1g;; 12 | "noble") apt_install libc6 libgcc1 libgssapi-krb5-2 libicu74 libssl3 libstdc++6 zlib1g;; 13 | *) 14 | echo "Tool '${TOOL_NAME}' not supported on: ${version_codename}! Please use ubuntu 'focal' or 'jammy'." >&2 15 | exit 1 16 | ;; 17 | esac 18 | } 19 | 20 | function install_tool () { 21 | local file 22 | local versioned_tool_path 23 | local arch=linux-x64 24 | 25 | if [[ "$(uname -p)" = "aarch64" ]]; then 26 | arch=linux-arm64 27 | fi 28 | 29 | file=$(get_from_url "https://github.com/PowerShell/PowerShell/releases/download/v${TOOL_VERSION}/powershell-${TOOL_VERSION}-${arch}.tar.gz") 30 | 31 | versioned_tool_path=$(create_versioned_tool_path) 32 | bsdtar -C "${versioned_tool_path}" -xzf "${file}" 33 | # Happened on v7.3.0 34 | if [[ ! -x "${versioned_tool_path}/pwsh" ]]; then 35 | chmod +x "${versioned_tool_path}/pwsh" 36 | fi 37 | } 38 | 39 | function link_tool () { 40 | shell_wrapper pwsh "$(find_versioned_tool_path)" 41 | pwsh -version 42 | } 43 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/tools/v2/sbt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export NEEDS_PREPARE=1 3 | 4 | function check_tool_requirements () { 5 | check_command java 6 | check_semver "$TOOL_VERSION" "all" 7 | } 8 | 9 | function prepare_tool() { 10 | init_tool 11 | 12 | # Redirect mix home 13 | path="$(get_cache_path)/.sbt" 14 | ln -sf "${path}" "${USER_HOME}/.sbt" 15 | } 16 | 17 | function init_tool () { 18 | local path 19 | path="$(get_cache_path)/.sbt" 20 | 21 | if [ -d "${path}" ]; then 22 | return 23 | fi 24 | 25 | # Init mix home 26 | create_folder "${path}" 775 27 | chown -R "${USER_ID}" "${path}" 28 | } 29 | 30 | 31 | function install_tool () { 32 | local versioned_tool_path 33 | local file 34 | local URL='https://github.com' 35 | 36 | # https://github.com/sbt/sbt/releases/download/v1.5.2/sbt-1.5.2.tgz 37 | file=$(get_from_url "${URL}/${TOOL_NAME}/${TOOL_NAME}/releases/download/v${TOOL_VERSION}/${TOOL_NAME}-${TOOL_VERSION}.tgz") 38 | 39 | versioned_tool_path=$(create_versioned_tool_path) 40 | tar --strip 1 -C "${versioned_tool_path}" -xf "${file}" 41 | rm "${versioned_tool_path}"/bin/*-darwin "${versioned_tool_path}"/bin/*.exe "${versioned_tool_path}"/bin/*.bat 42 | } 43 | 44 | function link_tool () { 45 | local versioned_tool_path 46 | local temp_dir 47 | versioned_tool_path=$(find_versioned_tool_path) 48 | 49 | shell_wrapper sbt "${versioned_tool_path}/bin" 50 | 51 | # https://github.com/sbt/sbt/issues/1458 52 | temp_dir="$(mktemp -d)" 53 | pushd "$temp_dir" || exit 1 54 | [[ -n $SKIP_VERSION ]] || sbt --version 55 | popd || exit 1 56 | 57 | # fix, cleanup sbt temp data 58 | rm -rf /tmp/.sbt ~/.sbt/* "$temp_dir" 59 | } 60 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/tools/v2/scala.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function check_tool_requirements () { 4 | check_command java 5 | check_semver "$TOOL_VERSION" "all" 6 | } 7 | 8 | function install_tool () { 9 | local versioned_tool_path 10 | local file 11 | local URL='https://downloads.lightbend.com' 12 | 13 | file=$(get_from_url "${URL}/${TOOL_NAME}/${TOOL_VERSION}/${TOOL_NAME}-${TOOL_VERSION}.tgz") 14 | 15 | versioned_tool_path=$(create_versioned_tool_path) 16 | tar --strip 1 -C "${versioned_tool_path}" -xf "${file}" 17 | } 18 | 19 | function link_tool () { 20 | local versioned_tool_path 21 | versioned_tool_path=$(find_versioned_tool_path) 22 | 23 | shell_wrapper scala "${versioned_tool_path}/bin" 24 | 25 | [[ -n $SKIP_VERSION ]] || scala --version 26 | } 27 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/tools/v2/terraform.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function install_tool () { 4 | local versioned_tool_path 5 | local file 6 | local arch=linux_amd64 7 | 8 | if [[ "$(uname -p)" = "aarch64" ]]; then 9 | arch=linux_arm64 10 | fi 11 | 12 | file=$(get_from_url "https://releases.hashicorp.com/terraform/${TOOL_VERSION}/terraform_${TOOL_VERSION}_${arch}.zip") 13 | 14 | versioned_tool_path=$(create_versioned_tool_path) 15 | create_folder "${versioned_tool_path}/bin" 16 | 17 | bsdtar -C "${versioned_tool_path}/bin" -xf "${file}" 18 | } 19 | 20 | function link_tool () { 21 | local versioned_tool_path 22 | versioned_tool_path=$(find_versioned_tool_path) 23 | 24 | shell_wrapper "${TOOL_NAME}" "${versioned_tool_path}/bin" 25 | terraform version 26 | } 27 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/tools/v2/vendir.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function install_tool () { 4 | local versioned_tool_path 5 | local file 6 | local arch=linux-amd64 7 | 8 | if [[ "$(uname -p)" = "aarch64" ]]; then 9 | arch=linux-arm64 10 | fi 11 | 12 | file=$(get_from_url "https://github.com/vmware-tanzu/carvel-vendir/releases/download/v${TOOL_VERSION}/vendir-${arch}") 13 | 14 | versioned_tool_path=$(create_versioned_tool_path) 15 | create_folder "${versioned_tool_path}/bin" 16 | cp "${file}" "${versioned_tool_path}/bin/vendir" 17 | chmod +x "${versioned_tool_path}/bin/vendir" 18 | } 19 | 20 | function link_tool () { 21 | shell_wrapper "${TOOL_NAME}" "$(find_versioned_tool_path)/bin" 22 | [[ -n $SKIP_VERSION ]] || vendir --version 23 | } 24 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/utils/constants.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # defines the location of the env file that gets sourced for every command 4 | export ENV_FILE=/usr/local/etc/env 5 | # defines the location of the global bashrc 6 | export BASH_RC=/etc/bash.bashrc 7 | # defines the root directory where tools will be installed 8 | export ROOT_DIR=/usr/local 9 | # defines the directory where shims to tools will be installed 10 | export BIN_DIR=/usr/local/bin 11 | export LIB_DIR=/usr/local/lib 12 | # defines the directory where user tools will be installed 13 | # shellcheck disable=SC2153 14 | export USER_HOME="/home/${USER_NAME}" 15 | # defines the umask for folders created by the root 16 | export ROOT_UMASK=755 17 | # defines the umask fo folders created by the user 18 | export USER_UMASK=775 19 | # defines the cache folder for downloaded tools, if empty no cache will be used 20 | export CONTAINERBASE_CACHE_DIR=${CONTAINERBASE_CACHE_DIR} 21 | # defines the max amount of filled space (in percent from 0-100) that is allowed 22 | # before the installation tries to free space by cleaning the cache folder 23 | # If empty, then cache cleanup is disabled 24 | export CONTAINERBASE_MAX_ALLOCATED_DISK=${CONTAINERBASE_MAX_ALLOCATED_DISK} 25 | # defines the temp directory that will be used when the cache is not active 26 | # it is used for all downloads and will be cleaned up after each install 27 | export TEMP_DIR=/tmp 28 | 29 | # used to source helper from tools 30 | export CONTAINERBASE_DIR=/usr/local/containerbase 31 | 32 | export CONTAINERBASE_VAR_DIR=/var/lib/containerbase 33 | export CONTAINERBASE_TMP_DIR=/tmp/containerbase 34 | 35 | # Used to find matching tool downloads 36 | ARCHITECTURE=$(uname -p) 37 | export ARCHITECTURE 38 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/utils/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function init_v2_tool () { 4 | 5 | if [[ -f "$(get_tool_init)" ]]; then 6 | # tool already initialized 7 | return 8 | fi 9 | 10 | # ensure tool path exists 11 | create_tool_path > /dev/null 12 | 13 | # init tool 14 | init_tool 15 | 16 | set_tool_init 17 | } 18 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/utils/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Will install the tool in the given path according to the v2 tool spec 4 | function install_v2_tool () { 5 | local path=${1} 6 | check path 7 | 8 | # load overrides needed for v2 tools 9 | # shellcheck source=/dev/null 10 | . "${CONTAINERBASE_DIR}/utils/v2/overrides.sh" 11 | 12 | # shellcheck source=/dev/null 13 | . "${path}" 14 | 15 | if [[ ! -f "$(get_tool_prep)" && "${NEEDS_PREPARE}" -eq 1 ]]; then 16 | if [[ $(is_root) -ne 0 ]]; then 17 | echo "${TOOL_NAME} not prepared" 18 | exit 1 19 | fi 20 | prepare_tool_wrapper 21 | fi 22 | 23 | # init tool if required 24 | init_v2_tool 25 | 26 | check_tool_requirements 27 | 28 | if ! check_tool_installed; then 29 | echo "installing v2 tool ${TOOL_NAME} v${TOOL_VERSION}" 30 | install_tool 31 | else 32 | echo "tool ${TOOL_NAME} v${TOOL_VERSION} is already installed" 33 | fi 34 | 35 | # only link tools if the version is not active yet 36 | local current_version 37 | current_version="$(get_tool_version)" 38 | 39 | if [[ -n "${current_version}" ]] && [[ "${current_version}" = "${TOOL_VERSION}" ]]; then 40 | echo "tool is already linked: ${TOOL_NAME} v${TOOL_VERSION}" 41 | else 42 | echo "linking tool ${TOOL_NAME} v${TOOL_VERSION}" 43 | link_tool 44 | fi 45 | 46 | # Allow tools to do some additional stuff, like overwriting additional shell wrapper 47 | post_install 48 | 49 | # cleanup 50 | cleanup_cache 51 | } 52 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/utils/linking.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # use this if custom env is required, creates a shell wrapper to /opt/containerbase/bin 4 | function shell_wrapper () { 5 | local TARGET 6 | local SOURCE=$2 7 | local EXPORTS=$3 8 | local args=$4 9 | local content=$5 10 | local tool_init 11 | TARGET="$(get_bin_path)/${1}" 12 | if [[ -z "$SOURCE" ]]; then 13 | SOURCE=$(command -v "${1}") 14 | fi 15 | if [[ -d "$SOURCE" ]]; then 16 | SOURCE=$SOURCE/${1} 17 | fi 18 | check SOURCE true 19 | check_command "$SOURCE" 20 | 21 | tool_init=$(get_tool_init "${TOOL_NAME//\//__}") 22 | 23 | cat > "$TARGET" <<- EOM 24 | #!/bin/bash 25 | 26 | if [[ -z "\${CONTAINERBASE_ENV+x}" ]]; then 27 | . $ENV_FILE 28 | fi 29 | if [[ ! -f "${tool_init}" ]]; then 30 | # set logging to only warn and above to not interfere with tool output 31 | CONTAINERBASE_LOG_LEVEL=warn containerbase-cli init tool "${TOOL_NAME}" 32 | fi 33 | EOM 34 | 35 | if [ -n "$EXPORTS" ]; then 36 | echo "export $EXPORTS" >> "$TARGET" 37 | fi 38 | 39 | if [ -n "$content" ]; then 40 | echo "$content" >> "$TARGET" 41 | fi 42 | 43 | echo "${SOURCE} ${args} \"\$@\"" >> "$TARGET" 44 | 45 | set_file_owner "${TARGET}" 775 46 | } 47 | 48 | # use this for simple symlink to /opt/containerbase/bin 49 | function link_wrapper () { 50 | local TARGET 51 | local SOURCE=$2 52 | TARGET="$(get_bin_path)/${1}" 53 | if [[ -z "$SOURCE" ]]; then 54 | SOURCE=$(command -v "${1}") 55 | fi 56 | if [[ -d "$SOURCE" ]]; then 57 | SOURCE=$SOURCE/${1} 58 | fi 59 | check_command "$SOURCE" 60 | ln -sf "$SOURCE" "$TARGET" 61 | } 62 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/utils/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Will install the tool in the given path according to the v2 tool spec 4 | function prepare_tools () { 5 | local TOOL_NAME 6 | TOOL_NAME=${1} 7 | check TOOL_NAME true 8 | 9 | require_root 10 | 11 | if [[ $(ignore_tool) -eq 1 ]]; then 12 | echo "Tool ignored - skipping: ${TOOL_NAME}" 13 | return 14 | fi 15 | 16 | TOOL="${CONTAINERBASE_DIR}/tools/v2/${TOOL_NAME}.sh" 17 | 18 | # load overrides needed for v2 tools 19 | # shellcheck source=/dev/null 20 | . "${CONTAINERBASE_DIR}/utils/v2/overrides.sh" 21 | 22 | if [[ -f "$TOOL" ]]; then 23 | # shellcheck source=/dev/null 24 | . "${TOOL}" 25 | else 26 | echo "tool ${TOOL_NAME} does not exist" 27 | exit 1 28 | fi 29 | 30 | prepare_tool_wrapper 31 | } 32 | 33 | function prepare_tool_wrapper () { 34 | # force root check 35 | require_root 36 | 37 | if [[ -f "$(get_tool_prep)" ]]; then 38 | # tool already prepared 39 | return 40 | fi 41 | 42 | # ensure tool path exists 43 | create_tool_path > /dev/null 44 | 45 | # prepare tool 46 | prepare_tool 47 | 48 | # set tool preped 49 | set_tool_prep 50 | 51 | # init tool 52 | init_v2_tool 53 | } 54 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/utils/ruby.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function check_tool_requirements () { 4 | check_command ruby 5 | check_semver "$TOOL_VERSION" "all" 6 | } 7 | 8 | function find_gem_versioned_path() { 9 | local ruby_version 10 | local tool_dir 11 | ruby_version=$(get_tool_version ruby) 12 | tool_dir="$(find_versioned_tool_path)/${ruby_version}" 13 | 14 | if [[ -d "${tool_dir}" ]]; then 15 | echo "${tool_dir}" 16 | fi 17 | } 18 | 19 | function check_tool_installed() { 20 | test -n "$(find_gem_versioned_path)" 21 | } 22 | 23 | function install_tool() { 24 | # always install with user umask 25 | # shellcheck disable=SC2034 26 | local ROOT_UMASK=${USER_UMASK} 27 | local ruby_version 28 | local tool_path 29 | ruby_version=$(get_tool_version ruby) 30 | tool_path="$(create_versioned_tool_path)/${ruby_version}" 31 | mkdir -p "${tool_path}" 32 | 33 | if [[ $(restore_folder_from_cache "${tool_path}" "${TOOL_NAME}/${TOOL_VERSION}/${ruby_version}") -ne 0 ]]; then 34 | # restore from cache not possible 35 | # either not in cache or error, install 36 | 37 | gem install --install-dir "${tool_path}" --bindir "${tool_path}/bin" "${TOOL_NAME}" -v "${TOOL_VERSION}" # --silent 38 | 39 | # TODO: clear gem cache 40 | 41 | # store in cache 42 | cache_folder "${tool_path}" "${TOOL_NAME}/${TOOL_VERSION}/${ruby_version}" 43 | fi 44 | } 45 | 46 | function post_install () { 47 | local tool_path 48 | 49 | tool_path=$(find_gem_versioned_path) 50 | 51 | while IFS= read -r -d '' tool 52 | do 53 | [ -e "${tool_path}/bin/$tool" ] || continue 54 | shell_wrapper "$tool" "${tool_path}/bin" "GEM_PATH=\$GEM_PATH:${tool_path}" 55 | done < <(find "${tool_path}/bin" -type f -printf "%f\0") 56 | } 57 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/utils/user.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function createUser() { 4 | # Set up user and home directory with access to users in the root group (0) 5 | # https://docs.openshift.com/container-platform/3.6/creating_images/guidelines.html#use-uid 6 | groupadd --gid "${USER_ID}" "${USER_NAME}"; 7 | useradd --uid "${USER_ID}" --gid "${PRIMARY_GROUP_ID}" --groups "0,${USER_ID}" --shell /bin/bash --create-home "${USER_NAME}" 8 | } 9 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/utils/v2/defaults.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file contains all functions a tool must implement to be properly supported 4 | # The containerbase scripts rely on the functions to decide if a tool needs to be handled in a special way 5 | # This defaults are loaded before any tool file so not overwriting the function will 6 | # result in the install process being aborted 7 | 8 | # Is used to check if all requirements are met to install the tool 9 | function check_tool_requirements () { 10 | # Sensitive default that can be overwritten by tools if needed 11 | check_semver "${TOOL_VERSION}" all 12 | } 13 | 14 | # Is used to check if the tool has already been installed in the given version 15 | function check_tool_installed () { 16 | # Sensitive default that can be overwritten by tools if needed 17 | test -n "$(find_versioned_tool_path)" 18 | } 19 | 20 | # Installs the tool with the given version 21 | function install_tool () { 22 | echo "'install_tool' not defined for tool ${TOOL_NAME}" 23 | exit 1 24 | } 25 | 26 | # Links the tools installation to the global bin folders 27 | function link_tool () { 28 | echo "'link_tool' not defined for tool ${TOOL_NAME}" 29 | exit 1 30 | } 31 | 32 | # Installs needed packages to make the tool runtime installable 33 | function prepare_tool() { 34 | true 35 | } 36 | 37 | # creates required files and folders for the tool 38 | function init_tool() { 39 | true 40 | } 41 | 42 | # Called after install_tool and link_tool. It's always called. 43 | # Allow tools to do some additional stuff, like overwriting additional shell wrapper 44 | function post_install () { 45 | true 46 | } 47 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/utils/v2/filesystem.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file will overwrite certain functionality that is required for v2 tools 4 | # e.g. for v2 tools we only support a single install directory for root and user installs 5 | # Whenever a v2 tool is installed, this file gets sourced 6 | 7 | # OVERWRITE: 8 | # 9 | # Will always return the root dir, no matter what user is calling the function 10 | function get_install_dir () { 11 | echo "${ROOT_DIR}" 12 | } 13 | 14 | # OVERWRITE: 15 | # 16 | # Will return the path to the tools path, which is {installdir}/tools/{toolname} instead of {installdir}/{toolname} 17 | function find_tool_path () { 18 | local tools_path 19 | tools_path=$(get_tools_path) 20 | 21 | if [[ -d "${tools_path}/${TOOL_NAME}" ]]; then 22 | echo "${tools_path}/${TOOL_NAME}" 23 | fi 24 | } 25 | 26 | # OVERWRITE: 27 | # 28 | # Creates the tool path in {installdir}/tools/{toolname} with 775 instead of in {installdir}/{toolname} with default umask 29 | function create_tool_path () { 30 | local tools_path 31 | tools_path=$(get_tools_path) 32 | 33 | if [ -d "${tools_path}/${TOOL_NAME}" ]; then 34 | echo "${tools_path}/${TOOL_NAME}" 35 | return 36 | fi 37 | 38 | create_folder "${tools_path}/${TOOL_NAME}" 775 39 | echo "${tools_path}/${TOOL_NAME}" 40 | } 41 | 42 | # OVERWRITE: 43 | # 44 | # Creates the versioned tool path in {installdir}/tools/{toolname}/{version} with user specific umask 45 | # instead of in {installdir}/{toolname}/{version} with default umask 46 | function create_versioned_tool_path () { 47 | local tool_path 48 | tool_path=$(create_tool_path) 49 | 50 | if [ -d "${tool_path}/${TOOL_VERSION}" ]; then 51 | echo "${tool_path}/${TOOL_VERSION}" 52 | return 53 | fi 54 | 55 | local umask 56 | umask=$(get_umask) 57 | 58 | mkdir -m "${umask}" "${tool_path}/${TOOL_VERSION}" 59 | echo "${tool_path}/${TOOL_VERSION}" 60 | } 61 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/utils/v2/overrides.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # defines the legacy root directory where old tools will be installed 4 | # reguired for some legacy tools for bat test redirection 5 | export ROOT_DIR_LEGACY="${ROOT_DIR}" 6 | 7 | # OVERWRITE: 8 | # 9 | # defines the root directory where tools will be installed 10 | # shellcheck disable=SC2168,SC2034 11 | export ROOT_DIR=/opt/containerbase 12 | 13 | # get path location 14 | DIR="${BASH_SOURCE%/*}" 15 | if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi 16 | 17 | # source the helper files 18 | # shellcheck source=/dev/null 19 | . "${DIR}/filesystem.sh" 20 | # shellcheck source=/dev/null 21 | . "${DIR}/defaults.sh" 22 | -------------------------------------------------------------------------------- /src/usr/local/containerbase/utils/version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Will set the version of the given tool to the given version in the versions folder 4 | function set_tool_version () { 5 | local tool=${1:-$TOOL_NAME} 6 | local version=${2:-$TOOL_VERSION} 7 | 8 | check tool true 9 | check version true 10 | 11 | local version_path 12 | version_path=$(get_version_path) 13 | 14 | # set umask for subshell and enter version 15 | # will only affect if we write the file initially 16 | # umask 117 -> chmod 660 17 | (umask 117 && echo "${version}" > "${version_path}/${tool}") 18 | } 19 | 20 | # Gets the version of the tool behind $TOOL_NAME or the first argument 21 | # if it is set, empty otherwise 22 | function get_tool_version () { 23 | local version_path 24 | local tool=${1:-$TOOL_NAME} 25 | check tool 26 | 27 | version_path=$(get_version_path) 28 | 29 | cat "${version_path}/${tool}" 2>&- || true 30 | } 31 | 32 | # Gets the version env var for the given tool 33 | # e.g 34 | # get_tool_version_env foo-bar 35 | # returns 36 | # FOO_BAR_VERSION 37 | function get_tool_version_env () { 38 | local tool=${1//-/_} 39 | check tool true 40 | 41 | tool=${tool^^}_VERSION 42 | echo "${tool}" 43 | } 44 | -------------------------------------------------------------------------------- /src/usr/local/sbin/install-containerbase: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # simple wrapper to install containerbase 4 | /usr/local/containerbase/bin/install-containerbase.sh "$@" 5 | -------------------------------------------------------------------------------- /test/bash/cache.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Create a temp directory for the test or use 4 | # the global one if defined in the given var. 5 | # Returns the path to the temp directory 6 | # 7 | # The caller is responible to create the 8 | # folder in the given var if it is set 9 | function create_temp_dir () { 10 | local global_var=$1 11 | 12 | if [[ -z "${!global_var}" ]]; then 13 | temp_dir="$(mktemp -u)" 14 | # shellcheck disable=SC2174 15 | mkdir -m 777 -p "${temp_dir}" >/dev/null 2>&1 16 | echo "${temp_dir}" 17 | else 18 | echo "${!global_var}" 19 | fi 20 | } 21 | 22 | # Removes the temp dir in the first var 23 | # if it is created for the test 24 | # If the global env is set, nothing will be done 25 | function clean_temp_dir () { 26 | local temp_dir=$1 27 | local global_var=$2 28 | 29 | if [[ -z "${!global_var}" ]]; then 30 | rm -rf "${temp_dir}" 31 | fi 32 | } 33 | 34 | # generates a random word to be used in tests 35 | function random_word () { 36 | tr -dc A-Za-z0-9 /dev/null 2>&1 && pwd)" 8 | TEST_ROOT_DIR=$(mktemp -u) 9 | 10 | load "$TEST_DIR/../../src/usr/local/containerbase/util.sh" 11 | 12 | # load test overwrites 13 | load "$TEST_DIR/util.sh" 14 | 15 | mkdir "${ROOT_DIR}/bin" 16 | 17 | setup_directories 18 | } 19 | 20 | teardown() { 21 | rm -rf "${TEST_ROOT_DIR}" 22 | } 23 | 24 | @test "link_wrapper" { 25 | 26 | mkdir -p "${USER_HOME}/bin" 27 | mkdir -p "${USER_HOME}/bin2" 28 | mkdir -p "${USER_HOME}/bin3" 29 | 30 | run link_wrapper 31 | assert_failure 32 | 33 | run link_wrapper foo 34 | assert_failure 35 | 36 | run link_wrapper git 37 | assert_success 38 | assert [ -f "${BIN_DIR}/git" ] 39 | 40 | printf "#!/bin/bash\n\necho 'foobar'" > "${USER_HOME}/bin2/foobar" 41 | chmod +x "${USER_HOME}/bin2/foobar" 42 | 43 | run link_wrapper foobar "${USER_HOME}/bin2/foobar" 44 | assert_success 45 | assert [ -f "${BIN_DIR}/foobar" ] 46 | rm "${BIN_DIR}/foobar" 47 | 48 | printf "#!/bin/bash\n\necho 'foobar'" > "${USER_HOME}/bin3/foobar" 49 | chmod +x "${USER_HOME}/bin3/foobar" 50 | 51 | run link_wrapper foobar "${USER_HOME}/bin3" 52 | assert_success 53 | assert [ -f "${BIN_DIR}/foobar" ] 54 | 55 | } 56 | 57 | @test "shell_wrapper" { 58 | 59 | mkdir -p "${USER_HOME}/bin" 60 | printf "#!/bin/bash\n\necho 'foobar'" > "${USER_HOME}/bin/foobar" 61 | chmod +x "${USER_HOME}/bin/foobar" 62 | 63 | run shell_wrapper 64 | assert_failure 65 | assert_output --partial "param SOURCE is set but empty" 66 | 67 | run shell_wrapper foo 68 | assert_failure 69 | assert_output --partial "param SOURCE is set but empty" 70 | 71 | run shell_wrapper ls 72 | assert_success 73 | assert [ -f "${BIN_DIR}/ls" ] 74 | assert [ "$(stat --format '%a' "${BIN_DIR}/ls")" -eq 775 ] 75 | 76 | PATH="${USER_HOME}/bin":$PATH run shell_wrapper foobar 77 | assert_success 78 | assert [ -f "${BIN_DIR}/foobar" ] 79 | assert [ "$(stat --format '%a' "${BIN_DIR}/ls")" -eq 775 ] 80 | } 81 | -------------------------------------------------------------------------------- /test/bash/prepare.bats: -------------------------------------------------------------------------------- 1 | # shellcheck disable=SC2148 2 | 3 | setup() { 4 | load "$BATS_SUPPORT_LOAD_PATH" 5 | load "$BATS_ASSERT_LOAD_PATH" 6 | 7 | TEST_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")" >/dev/null 2>&1 && pwd)" 8 | TEST_ROOT_DIR=$(mktemp -u) 9 | 10 | load "$TEST_DIR/../../src/usr/local/containerbase/util.sh" 11 | 12 | # load test overwrites 13 | load "$TEST_DIR/util.sh" 14 | 15 | setup_directories 16 | 17 | # copy containerbase files 18 | cp -r "$TEST_DIR/../../src/usr/local/containerbase" "${ROOT_DIR}/containerbase" 19 | } 20 | 21 | teardown() { 22 | rm -rf "${TEST_ROOT_DIR}" 23 | } 24 | 25 | @test "prepare-tool" { 26 | run prepare_tools 27 | assert_failure 28 | assert_output "param TOOL_NAME is set but empty" 29 | 30 | run prepare_tools foobar 31 | assert_failure 32 | assert_output "This script must be run as root" 33 | 34 | TEST_ROOT_USER=0 \ 35 | CONTAINERBASE_DEBUG=1 \ 36 | run prepare_tools foobar2 37 | assert_failure 38 | assert_output "tool foobar2 does not exist" 39 | } 40 | -------------------------------------------------------------------------------- /test/bash/util.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Will overwrite certain util functions to make them testable 4 | 5 | # set directories for test 6 | export REPO_DIR="${TEST_DIR}/../.." 7 | export CONTAINERBASE_DIR="${REPO_DIR}/src/usr/local/containerbase" 8 | export ROOT_DIR="${TEST_ROOT_DIR}/root" 9 | export BIN_DIR="${TEST_ROOT_DIR}/bin" 10 | export LIB_DIR="${TEST_ROOT_DIR}/lib" 11 | export USER_HOME="${TEST_ROOT_DIR}/user" 12 | export ENV_FILE="${TEST_ROOT_DIR}/env" 13 | export CONTAINERBASE_VAR_DIR="${TEST_ROOT_DIR}/var" 14 | export CONTAINERBASE_TMP_DIR="${TEST_ROOT_DIR}/tmp" 15 | 16 | # set default test user 17 | export TEST_ROOT_USER=12021 18 | 19 | # Overwrite is_root function to check a test root user 20 | # instead of the effective caller 21 | function is_root () { 22 | if [[ $TEST_ROOT_USER -ne 0 ]]; then 23 | echo 1 24 | else 25 | echo 0 26 | fi 27 | } 28 | 29 | function link_cli_tool () { 30 | local arch=amd64 31 | 32 | if [[ "${ARCHITECTURE}" = "aarch64" ]];then 33 | arch=arm64 34 | fi 35 | export PATH="${TEST_ROOT_DIR}/sbin:${PATH}" 36 | ln -sf "${REPO_DIR}/dist/cli/containerbase-cli-${arch}" "${TEST_ROOT_DIR}/sbin/containerbase-cli" 37 | } 38 | 39 | # ensure directories exist 40 | mkdir -p "${TEST_ROOT_DIR}"/{sbin,root,user} 41 | link_cli_tool 42 | -------------------------------------------------------------------------------- /test/bash/v2/defaults.bats: -------------------------------------------------------------------------------- 1 | # shellcheck disable=SC2034,SC2148 2 | 3 | setup() { 4 | load "$BATS_SUPPORT_LOAD_PATH" 5 | load "$BATS_ASSERT_LOAD_PATH" 6 | 7 | TEST_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")" >/dev/null 2>&1 && pwd)" 8 | TEST_ROOT_DIR=$(mktemp -u) 9 | 10 | load "$TEST_DIR/../../../src/usr/local/containerbase/util.sh" 11 | 12 | # load v2 overwrites 13 | load "$TEST_DIR/../../../src/usr/local/containerbase/utils/v2/overrides.sh" 14 | 15 | # load test overwrites 16 | load "$TEST_DIR/../util.sh" 17 | } 18 | 19 | teardown() { 20 | rm -rf "${TEST_ROOT_DIR}" 21 | } 22 | 23 | @test "overwrite: test default functions" { 24 | 25 | run check_tool_requirements 26 | assert_failure 27 | assert_output --partial "Not a semver like version" 28 | 29 | TOOL_VERSION=1.2.3 30 | run check_tool_requirements 31 | assert_success 32 | 33 | run check_tool_installed 34 | assert_failure 35 | 36 | TOOL_NAME=foo \ 37 | TOOL_VERSION=1.2.3 38 | run check_tool_installed 39 | assert_failure 40 | 41 | TOOL_NAME=foo \ 42 | TOOL_VERSION=1.2.3 43 | run create_versioned_tool_path 44 | assert_success 45 | 46 | TOOL_NAME=foo \ 47 | TOOL_VERSION=1.2.3 48 | run check_tool_installed 49 | assert_success 50 | 51 | run install_tool 52 | assert_failure 53 | assert_output --partial "not defined" 54 | 55 | run link_tool 56 | assert_failure 57 | assert_output --partial "not defined" 58 | 59 | run prepare_tool 60 | assert_success 61 | } 62 | -------------------------------------------------------------------------------- /test/dart/Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | #-------------------------------------- 23 | # Image: base 24 | #-------------------------------------- 25 | FROM ${BASE_IMAGE} AS base 26 | 27 | RUN uname -p | tee | grep aarch64 28 | RUN touch /.dummy 29 | 30 | ARG APT_HTTP_PROXY 31 | ARG CONTAINERBASE_CDN 32 | ARG CONTAINERBASE_DEBUG 33 | ARG CONTAINERBASE_LOG_LEVEL 34 | 35 | #-------------------------------------- 36 | # Image: test 37 | #-------------------------------------- 38 | FROM base AS test-dart 39 | 40 | # renovate: datasource=docker 41 | RUN install-tool dart 3.7.3 42 | 43 | #-------------------------------------- 44 | # Image: final 45 | #-------------------------------------- 46 | FROM base 47 | 48 | COPY --from=test-dart /.dummy /.dummy 49 | -------------------------------------------------------------------------------- /test/dart/test/.gitignore: -------------------------------------------------------------------------------- 1 | .dart_tool/ 2 | .packages 3 | -------------------------------------------------------------------------------- /test/dart/test/a/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: vector_victor 2 | description: A sample command-line application. 3 | version: 1.0.0 4 | # homepage: https://www.example.com 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | 9 | dependencies: 10 | path: ^1.8.0 11 | 12 | dev_dependencies: 13 | lints: ^1.0.1 14 | test: ^1.16.0 15 | -------------------------------------------------------------------------------- /test/dotnet/Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | #-------------------------------------- 23 | # Image: base 24 | #-------------------------------------- 25 | FROM ${BASE_IMAGE} AS base 26 | 27 | RUN uname -p | tee | grep aarch64 28 | RUN touch /.dummy 29 | 30 | ARG APT_HTTP_PROXY 31 | ARG CONTAINERBASE_CDN 32 | ARG CONTAINERBASE_DEBUG 33 | ARG CONTAINERBASE_LOG_LEVEL 34 | 35 | #-------------------------------------- 36 | # Image: dotnet 37 | #-------------------------------------- 38 | FROM base AS test-dotnet 39 | 40 | # renovate: datasource=dotnet packageName=dotnet-sdk 41 | RUN install-tool dotnet 8.0.410 42 | 43 | #-------------------------------------- 44 | # Image: final 45 | #-------------------------------------- 46 | FROM base 47 | 48 | COPY --from=test-dotnet /.dummy /.dummy 49 | -------------------------------------------------------------------------------- /test/dotnet/test/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace test 4 | { 5 | public class Class1 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/dotnet/test/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | ".NETCoreApp,Version=v3.1": { 5 | "Newtonsoft.Json": { 6 | "type": "Direct", 7 | "requested": "[12.0.1, )", 8 | "resolved": "12.0.1", 9 | "contentHash": "pBR3wCgYWZGiaZDYP+HHYnalVnPJlpP1q55qvVb+adrDHmFMDc1NAKio61xTwftK3Pw5h7TZJPJEEVMd6ty8rg==" 10 | } 11 | }, 12 | ".NETStandard,Version=v2.0": { 13 | "NETStandard.Library": { 14 | "type": "Direct", 15 | "requested": "[2.0.3, )", 16 | "resolved": "2.0.3", 17 | "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", 18 | "dependencies": { 19 | "Microsoft.NETCore.Platforms": "1.1.0" 20 | } 21 | }, 22 | "Newtonsoft.Json": { 23 | "type": "Direct", 24 | "requested": "[12.0.1, )", 25 | "resolved": "12.0.1", 26 | "contentHash": "pBR3wCgYWZGiaZDYP+HHYnalVnPJlpP1q55qvVb+adrDHmFMDc1NAKio61xTwftK3Pw5h7TZJPJEEVMd6ty8rg==" 27 | }, 28 | "Microsoft.NETCore.Platforms": { 29 | "type": "Transitive", 30 | "resolved": "1.1.0", 31 | "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" 32 | } 33 | }, 34 | "net6.0": { 35 | "Newtonsoft.Json": { 36 | "type": "Direct", 37 | "requested": "[12.0.1, )", 38 | "resolved": "12.0.1", 39 | "contentHash": "pBR3wCgYWZGiaZDYP+HHYnalVnPJlpP1q55qvVb+adrDHmFMDc1NAKio61xTwftK3Pw5h7TZJPJEEVMd6ty8rg==" 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/dotnet/test/test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;netcoreapp3.1;net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/erlang/Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | #-------------------------------------- 23 | # Image: base 24 | #-------------------------------------- 25 | FROM ${BASE_IMAGE} AS base 26 | 27 | RUN uname -p | tee | grep aarch64 28 | RUN touch /.dummy 29 | 30 | ARG APT_HTTP_PROXY 31 | ARG CONTAINERBASE_CDN 32 | ARG CONTAINERBASE_DEBUG 33 | ARG CONTAINERBASE_LOG_LEVEL 34 | 35 | #-------------------------------------- 36 | # Image: erlang 37 | #-------------------------------------- 38 | FROM base AS test-erlang 39 | 40 | # renovate: datasource=github-releases packageName=containerbase/erlang-prebuild versioning=docker 41 | RUN install-tool erlang 27.3.4.0 42 | 43 | #-------------------------------------- 44 | # Image: elixir 45 | #-------------------------------------- 46 | FROM test-erlang AS test-elixir 47 | 48 | # renovate: datasource=github-releases packageName=elixir-lang/elixir 49 | RUN install-tool elixir 1.18.4 50 | 51 | #-------------------------------------- 52 | # Image: final 53 | #-------------------------------------- 54 | FROM base 55 | 56 | COPY --from=test-erlang /.dummy /.dummy 57 | COPY --from=test-elixir /.dummy /.dummy 58 | -------------------------------------------------------------------------------- /test/erlang/test/a/mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "benchee": {:hex, :benchee, "0.13.2", "30cd4ff5f593fdd218a9b26f3c24d580274f297d88ad43383afe525b1543b165", [:mix], [{:deep_merge, "~> 0.1", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"}, 3 | "deep_merge": {:hex, :deep_merge, "0.2.0", "c1050fa2edf4848b9f556fba1b75afc66608a4219659e3311d9c9427b5b680b3", [:mix], [], "hexpm"}, 4 | "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"}, 5 | "earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"}, 6 | "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, 7 | "gen_state_machine": {:hex, :gen_state_machine, "2.0.3", "477ea51b466a749ab23a0d6090e9e84073f41f9aa28c7efc40eac18f3d4a9f77", [:mix], [], "hexpm"}, 8 | "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"}, 9 | "makeup": {:hex, :makeup, "0.5.1", "966c5c2296da272d42f1de178c1d135e432662eca795d6dc12e5e8787514edf7", [:mix], [{:nimble_parsec, "~> 0.2.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, 10 | "makeup_elixir": {:hex, :makeup_elixir, "0.8.0", "1204a2f5b4f181775a0e456154830524cf2207cf4f9112215c05e0b76e4eca8b", [:mix], [{:makeup, "~> 0.5.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.2.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, 11 | "nimble_parsec": {:hex, :nimble_parsec, "0.2.2", "d526b23bdceb04c7ad15b33c57c4526bf5f50aaa70c7c141b4b4624555c68259", [:mix], [], "hexpm"}, 12 | "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []}, 13 | "porcelain": {:hex, :porcelain, "2.0.3", "2d77b17d1f21fed875b8c5ecba72a01533db2013bd2e5e62c6d286c029150fdc", [:mix], []}, 14 | } -------------------------------------------------------------------------------- /test/flutter/Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | # flutter currently needs git 23 | # renovate: datasource=github-tags packageName=git/git 24 | RUN install-tool git v2.49.0 25 | 26 | #-------------------------------------- 27 | # Image: base 28 | #-------------------------------------- 29 | FROM ${BASE_IMAGE} AS base 30 | 31 | RUN uname -p | tee | grep aarch64 32 | RUN touch /.dummy 33 | 34 | ARG APT_HTTP_PROXY 35 | ARG CONTAINERBASE_CDN 36 | ARG CONTAINERBASE_DEBUG 37 | ARG CONTAINERBASE_LOG_LEVEL 38 | 39 | #-------------------------------------- 40 | # Image: flutter 41 | #-------------------------------------- 42 | FROM base AS test-flutter 43 | 44 | # renovate: datasource=github-releases packageName=containerbase/flutter-prebuild 45 | RUN install-tool flutter 3.29.3 46 | 47 | #-------------------------------------- 48 | # Image: final 49 | #-------------------------------------- 50 | FROM base 51 | 52 | COPY --from=test-flutter /.dummy /.dummy 53 | -------------------------------------------------------------------------------- /test/flutter/test/.gitignore: -------------------------------------------------------------------------------- 1 | .dart_tool/ 2 | .packages 3 | -------------------------------------------------------------------------------- /test/flutter/test/a/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | characters: 5 | dependency: transitive 6 | description: 7 | name: characters 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "1.1.0-nullsafety.3" 11 | collection: 12 | dependency: "direct main" 13 | description: 14 | name: collection 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.15.0-nullsafety.3" 18 | flutter: 19 | dependency: "direct main" 20 | description: flutter 21 | source: sdk 22 | version: "0.0.0" 23 | meta: 24 | dependency: "direct main" 25 | description: 26 | name: meta 27 | url: "https://pub.dartlang.org" 28 | source: hosted 29 | version: "1.3.0-nullsafety.3" 30 | sky_engine: 31 | dependency: transitive 32 | description: flutter 33 | source: sdk 34 | version: "0.0.99" 35 | typed_data: 36 | dependency: "direct main" 37 | description: 38 | name: typed_data 39 | url: "https://pub.dartlang.org" 40 | source: hosted 41 | version: "1.3.0-nullsafety.3" 42 | vector_math: 43 | dependency: "direct main" 44 | description: 45 | name: vector_math 46 | url: "https://pub.dartlang.org" 47 | source: hosted 48 | version: "2.1.0-nullsafety.3" 49 | sdks: 50 | dart: ">=2.10.0-110 <2.11.0" 51 | -------------------------------------------------------------------------------- /test/flutter/test/a/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_view 2 | description: A new flutter project. 3 | 4 | environment: 5 | # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite. 6 | sdk: '>=2.0.0-dev.68.0 <3.0.0' 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | collection: ^1.14.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 13 | meta: ^1.2.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 14 | typed_data: ^1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 15 | vector_math: ^2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 16 | 17 | flutter: 18 | uses-material-design: true 19 | assets: 20 | - assets/flutter-mark-square-64.png 21 | # PUBSPEC CHECKSUM: 643e 22 | -------------------------------------------------------------------------------- /test/flutter/test/d/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: hello_world 2 | 3 | environment: 4 | sdk: '>=3.0.0 <4.0.0' 5 | 6 | dependencies: 7 | flutter: 8 | sdk: flutter 9 | 10 | collection: ^1.16.0 11 | meta: ^1.7.0 12 | typed_data: ^1.3.2 13 | vector_math: ^2.1.2 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | 19 | archive: ^3.4.2 20 | args: ^2.3.1 21 | async: ^2.8.2 22 | boolean_selector: ^2.1.0 23 | charcode: ^1.3.1 24 | convert: ^3.1.0 25 | crypto: ^3.0.2 26 | image: ^4.1.3 27 | matcher: ^0.12.11 28 | path: ^1.8.1 29 | pedantic: ^1.11.1 30 | petitparser: ^5.0.0 31 | quiver: ^3.2.1 32 | source_span: ^1.8.2 33 | stack_trace: ^1.10.0 34 | stream_channel: ^2.1.0 35 | string_scanner: ^1.1.0 36 | term_glyph: ^1.2.0 37 | test_api: '>=0.6.1' 38 | xml: ^6.1.0 39 | # PUBSPEC CHECKSUM: f789 40 | -------------------------------------------------------------------------------- /test/flux/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | #-------------------------------------- 23 | # Image: base 24 | #-------------------------------------- 25 | FROM ${BASE_IMAGE} AS base 26 | 27 | RUN touch /.dummy 28 | 29 | ARG APT_HTTP_PROXY 30 | ARG CONTAINERBASE_CDN 31 | ARG CONTAINERBASE_DEBUG 32 | ARG CONTAINERBASE_LOG_LEVEL 33 | 34 | # renovate: datasource=github-releases depName=flux packageName=fluxcd/flux2 35 | ARG FLUX_VERSION=v2.5.1 36 | 37 | USER 12021 38 | 39 | #-------------------------------------- 40 | # install flux as root 41 | #-------------------------------------- 42 | FROM base AS testa 43 | 44 | RUN install-tool flux "${FLUX_VERSION}" 45 | 46 | SHELL [ "/bin/sh", "-c" ] 47 | 48 | RUN flux -v | grep "${FLUX_VERSION#v}" 49 | 50 | #-------------------------------------- 51 | # install flux as user 52 | #-------------------------------------- 53 | FROM base AS testb 54 | 55 | USER 1111 56 | 57 | RUN install-tool flux "${FLUX_VERSION}" 58 | 59 | SHELL [ "/bin/sh", "-c" ] 60 | 61 | RUN flux -v | grep "${FLUX_VERSION#v}" 62 | 63 | #-------------------------------------- 64 | # install flux as user when already installed 65 | #-------------------------------------- 66 | FROM base AS testc 67 | 68 | RUN install-tool flux "${FLUX_VERSION}" 69 | 70 | USER 1111 71 | 72 | # do not update 73 | RUN install-tool flux 0.27.2 74 | 75 | SHELL [ "/bin/sh", "-c" ] 76 | 77 | RUN flux -v | grep "0.27.2" 78 | 79 | #-------------------------------------- 80 | # final 81 | #-------------------------------------- 82 | FROM base 83 | 84 | COPY --from=testa /.dummy /.dummy 85 | COPY --from=testb /.dummy /.dummy 86 | COPY --from=testc /.dummy /.dummy 87 | -------------------------------------------------------------------------------- /test/flux/Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | #-------------------------------------- 23 | # Image: base 24 | #-------------------------------------- 25 | FROM ${BASE_IMAGE} AS base 26 | 27 | RUN uname -p | tee | grep aarch64 28 | RUN touch /.dummy 29 | 30 | ARG APT_HTTP_PROXY 31 | ARG CONTAINERBASE_CDN 32 | ARG CONTAINERBASE_DEBUG 33 | ARG CONTAINERBASE_LOG_LEVEL 34 | 35 | #-------------------------------------- 36 | # Image: flux 37 | #-------------------------------------- 38 | FROM base AS test-flux 39 | 40 | # renovate: datasource=github-releases packageName=fluxcd/flux2 41 | RUN install-tool flux v2.5.1 42 | 43 | #-------------------------------------- 44 | # Image: final 45 | #-------------------------------------- 46 | FROM base 47 | 48 | COPY --from=test-flux /.dummy /.dummy 49 | -------------------------------------------------------------------------------- /test/global-setup.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { afterAll, beforeAll, vi } from 'vitest'; 3 | 4 | vi.mock('pino'); 5 | 6 | let rootDir!: string; 7 | let cacheDir!: string; 8 | 9 | beforeAll(async () => { 10 | const fs = 11 | await vi.importActual( 12 | 'node:fs/promises', 13 | ); 14 | const os = await vi.importActual('node:os'); 15 | const path = await vi.importActual('node:path'); 16 | cacheDir = globalThis.cacheDir = await fs.mkdtemp( 17 | path.join(os.tmpdir(), 'containerbase-cache-'), 18 | ); 19 | rootDir = globalThis.rootDir = await fs.mkdtemp( 20 | path.join(os.tmpdir(), 'containerbase-root-'), 21 | ); 22 | 23 | const { env } = 24 | await vi.importActual('node:process'); 25 | 26 | env.CONTAINERBASE_CACHE_DIR = globalThis.cacheDir; 27 | }); 28 | 29 | afterAll(async () => { 30 | const fs = 31 | await vi.importActual( 32 | 'node:fs/promises', 33 | ); 34 | await fs.rm(cacheDir, { recursive: true }); 35 | await fs.rm(rootDir, { recursive: true }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/golang/Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | # currently go doesn't install git 23 | # renovate: datasource=github-tags packageName=git/git 24 | RUN install-tool git v2.49.0 25 | 26 | #-------------------------------------- 27 | # Image: base 28 | #-------------------------------------- 29 | FROM ${BASE_IMAGE} AS base 30 | 31 | RUN uname -p | tee | grep aarch64 32 | RUN touch /.dummy 33 | 34 | ARG APT_HTTP_PROXY 35 | ARG CONTAINERBASE_CDN 36 | ARG CONTAINERBASE_DEBUG 37 | ARG CONTAINERBASE_LOG_LEVEL 38 | 39 | #-------------------------------------- 40 | # Image: golang 41 | #-------------------------------------- 42 | FROM base AS test-golang 43 | 44 | # renovate: datasource=github-releases packageName=containerbase/golang-prebuild 45 | RUN install-tool golang 1.24.3 46 | 47 | #-------------------------------------- 48 | # Image: final 49 | #-------------------------------------- 50 | FROM base 51 | 52 | COPY --from=test-golang /.dummy /.dummy 53 | -------------------------------------------------------------------------------- /test/golang/test/a/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/containerbase/test-golang1 2 | 3 | require github.com/pkg/errors v0.9.1 4 | require github.com/aws/aws-sdk-go v1.15.21 5 | require github.com/davecgh/go-spew v1.0.0 6 | 7 | replace github.com/gocql/gocql => github.com/kiwicom/gocql v0.0.0-20190701110745-b0d035b46104 8 | -------------------------------------------------------------------------------- /test/golang/test/a/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-sdk-go v1.15.21/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= 2 | github.com/davecgh/go-spew v1.0.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 4 | github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 5 | github.com/pkg/errors v0.7.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 6 | -------------------------------------------------------------------------------- /test/golang/test/b/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/containerbase/test-golang1 2 | 3 | require github.com/pkg/errors v0.7.0 4 | require github.com/aws/aws-sdk-go v1.15.21 5 | require github.com/davecgh/go-spew v1.0.0 6 | 7 | -------------------------------------------------------------------------------- /test/golang/test/b/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-sdk-go v1.15.21/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= 2 | github.com/davecgh/go-spew v1.0.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 4 | github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 5 | github.com/pkg/errors v0.7.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 6 | -------------------------------------------------------------------------------- /test/golang/test/c/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/containerbase/test-golang1 2 | 3 | go 1.14 4 | 5 | require github.com/pkg/errors v0.9.0 6 | require launchpad.net/gocheck v0.0.0-20140225173054-000000000087 7 | -------------------------------------------------------------------------------- /test/golang/test/d/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/containerbase/test-golang1 2 | 3 | require github.com/pkg/errors v0.7.0 4 | require github.com/aws/aws-sdk-go v1.15.21 5 | require github.com/davecgh/go-spew v1.0.0 6 | 7 | replace github.com/gocql/gocql => github.com/kiwicom/gocql v0.0.0-20190701110745-b0d035b46104 8 | -------------------------------------------------------------------------------- /test/golang/test/d/go.sum: -------------------------------------------------------------------------------- 1 | github.com/pkg/errors v0.7.1 h1:0XSZhzhcAUrs2vsv1y5jaxWejlCCgvxI/kBpbRFMZ+o= 2 | github.com/pkg/errors v0.7.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -------------------------------------------------------------------------------- /test/golang/test/d/vendor/github.com/pkg/errors/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /test/golang/test/d/vendor/github.com/pkg/errors/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go_import_path: github.com/pkg/errors 3 | go: 4 | - 1.4.3 5 | - 1.5.4 6 | - 1.6.2 7 | - 1.7.1 8 | - tip 9 | 10 | script: 11 | - go test -v ./... 12 | -------------------------------------------------------------------------------- /test/golang/test/d/vendor/github.com/pkg/errors/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Dave Cheney 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /test/golang/test/d/vendor/github.com/pkg/errors/appveyor.yml: -------------------------------------------------------------------------------- 1 | version: build-{build}.{branch} 2 | 3 | clone_folder: C:\gopath\src\github.com\pkg\errors 4 | shallow_clone: true # for startup speed 5 | 6 | environment: 7 | GOPATH: C:\gopath 8 | 9 | platform: 10 | - x64 11 | 12 | # http://www.appveyor.com/docs/installed-software 13 | install: 14 | # some helpful output for debugging builds 15 | - go version 16 | - go env 17 | # pre-installed MinGW at C:\MinGW is 32bit only 18 | # but MSYS2 at C:\msys64 has mingw64 19 | - set PATH=C:\msys64\mingw64\bin;%PATH% 20 | - gcc --version 21 | - g++ --version 22 | 23 | build_script: 24 | - go install -v ./... 25 | 26 | test_script: 27 | - set PATH=C:\gopath\bin;%PATH% 28 | - go test -v ./... 29 | 30 | #artifacts: 31 | # - path: '%GOPATH%\bin\*.exe' 32 | deploy: off 33 | -------------------------------------------------------------------------------- /test/golang/test/d/vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/pkg/errors v0.8.0 2 | github.com/pkg/errors 3 | -------------------------------------------------------------------------------- /test/helm/Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | #-------------------------------------- 23 | # Image: base 24 | #-------------------------------------- 25 | FROM ${BASE_IMAGE} AS base 26 | 27 | RUN uname -p | tee | grep aarch64 28 | RUN touch /.dummy 29 | 30 | ARG APT_HTTP_PROXY 31 | ARG CONTAINERBASE_CDN 32 | ARG CONTAINERBASE_DEBUG 33 | ARG CONTAINERBASE_LOG_LEVEL 34 | 35 | #-------------------------------------- 36 | # Image: helm 37 | #-------------------------------------- 38 | FROM base AS test-helm 39 | 40 | # renovate: datasource=github-releases packageName=helm/helm 41 | RUN install-tool helm v3.18.1 42 | 43 | #-------------------------------------- 44 | # Image: final 45 | #-------------------------------------- 46 | FROM base 47 | 48 | COPY --from=test-helm /.dummy /.dummy 49 | -------------------------------------------------------------------------------- /test/http-mock.ts: -------------------------------------------------------------------------------- 1 | import type { Url } from 'node:url'; 2 | import nock from 'nock'; // eslint-disable-line no-restricted-imports 3 | import { afterAll, afterEach, beforeAll } from 'vitest'; 4 | 5 | type BasePath = string | RegExp | Url; 6 | let missingLog: string[] = []; 7 | 8 | interface TestRequest { 9 | method: string; 10 | href: string; 11 | } 12 | 13 | function onMissing(req: TestRequest, opts?: TestRequest): void { 14 | if (opts) { 15 | missingLog.push(` ${opts.method} ${opts.href}`); 16 | } else { 17 | missingLog.push(` ${req.method} ${req.href}`); 18 | } 19 | } 20 | 21 | /** 22 | * Clear nock state. Will be called in `afterEach` 23 | * @argument throwOnPending Use `false` to simply clear mocks. 24 | */ 25 | export function clear(throwOnPending = true): void { 26 | const isDone = nock.isDone(); 27 | const pending = nock.pendingMocks(); 28 | nock.abortPendingRequests(); 29 | nock.cleanAll(); 30 | const missing = missingLog; 31 | missingLog = []; 32 | if (missing.length && throwOnPending) { 33 | throw new Error(`Missing mocks!\n * ${missing.join('\n * ')}`); 34 | } 35 | if (!isDone && throwOnPending) { 36 | throw new Error(`Pending mocks!\n * ${pending.join('\n * ')}`); 37 | } 38 | } 39 | 40 | export function scope(basePath: BasePath, options?: nock.Options): nock.Scope { 41 | return nock(basePath, options); 42 | } 43 | 44 | // init nock 45 | beforeAll(() => { 46 | nock.emitter.on('no match', onMissing); 47 | nock.disableNetConnect(); 48 | }); 49 | 50 | // clean nock to clear memory leack from http module patching 51 | afterAll(() => { 52 | nock.emitter.removeListener('no match', onMissing); 53 | nock.restore(); 54 | }); 55 | 56 | // clear nock state 57 | afterEach(() => { 58 | clear(); 59 | }); 60 | -------------------------------------------------------------------------------- /test/java/Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | #-------------------------------------- 23 | # Image: base 24 | #-------------------------------------- 25 | FROM ${BASE_IMAGE} AS base 26 | 27 | RUN uname -p | tee | grep aarch64 28 | RUN touch /.dummy 29 | 30 | ARG APT_HTTP_PROXY 31 | ARG CONTAINERBASE_CDN 32 | ARG CONTAINERBASE_DEBUG 33 | ARG CONTAINERBASE_LOG_LEVEL 34 | 35 | #-------------------------------------- 36 | # Image: java 37 | #-------------------------------------- 38 | FROM base AS test-java 39 | 40 | # renovate: datasource=java-version packageName=java-jre 41 | RUN install-tool java 21.0.7+6.0.LTS 42 | 43 | #-------------------------------------- 44 | # Image: java 45 | #-------------------------------------- 46 | FROM test-java AS test-gradle 47 | 48 | # renovate: datasource=gradle-version packageName=gradle versioning=gradle 49 | RUN install-tool gradle 8.14.1 50 | 51 | #-------------------------------------- 52 | # Image: maven 53 | #-------------------------------------- 54 | FROM test-java AS test-maven 55 | 56 | # renovate: datasource=maven packageName=org.apache.maven:maven 57 | RUN install-tool maven 3.9.9 58 | 59 | #-------------------------------------- 60 | # Image: final 61 | #-------------------------------------- 62 | FROM base 63 | 64 | COPY --from=test-java /.dummy /.dummy 65 | COPY --from=test-gradle /.dummy /.dummy 66 | COPY --from=test-maven /.dummy /.dummy 67 | -------------------------------------------------------------------------------- /test/java/test/sbt/build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / scalaVersion := "2.13.13" 2 | ThisBuild / organization := "com.example" 3 | 4 | lazy val hello = (project in file(".")) 5 | .settings( 6 | name := "Hello" 7 | ) 8 | -------------------------------------------------------------------------------- /test/java/test/sbt/src/main/scala/example/Hello.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | object Hello extends App { 4 | println("Hello") 5 | } 6 | -------------------------------------------------------------------------------- /test/jb/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | #-------------------------------------- 23 | # Image: base 24 | #-------------------------------------- 25 | FROM ${BASE_IMAGE} AS base 26 | 27 | RUN touch /.dummy 28 | 29 | ARG APT_HTTP_PROXY 30 | ARG CONTAINERBASE_CDN 31 | ARG CONTAINERBASE_DEBUG 32 | ARG CONTAINERBASE_LOG_LEVEL 33 | 34 | COPY --chown=12021:0 test/jb/test test 35 | WORKDIR /test 36 | 37 | #-------------------------------------- 38 | # build 39 | #-------------------------------------- 40 | FROM base AS build 41 | 42 | USER 12021 43 | 44 | # renovate: datasource=github-releases packageName=jsonnet-bundler/jsonnet-bundler 45 | RUN install-tool jb v0.6.0 46 | 47 | 48 | #-------------------------------------- 49 | # test: jb install 50 | #-------------------------------------- 51 | 52 | FROM build AS testa 53 | 54 | RUN set -ex; \ 55 | jb install; \ 56 | test -d vendor/github.com/prometheus-operator/prometheus-operator; \ 57 | test -d vendor/prometheus-operator 58 | 59 | #-------------------------------------- 60 | # final 61 | #-------------------------------------- 62 | FROM base 63 | 64 | COPY --from=testa /.dummy /.dummy 65 | -------------------------------------------------------------------------------- /test/jb/Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | #-------------------------------------- 23 | # Image: base 24 | #-------------------------------------- 25 | FROM ${BASE_IMAGE} AS base 26 | 27 | RUN uname -p | tee | grep aarch64 28 | RUN touch /.dummy 29 | 30 | ARG APT_HTTP_PROXY 31 | ARG CONTAINERBASE_CDN 32 | ARG CONTAINERBASE_DEBUG 33 | ARG CONTAINERBASE_LOG_LEVEL 34 | 35 | #-------------------------------------- 36 | # Image: jb 37 | #-------------------------------------- 38 | FROM base AS test-jb 39 | 40 | # renovate: datasource=github-releases packageName=jsonnet-bundler/jsonnet-bundler 41 | RUN install-tool jb v0.6.0 42 | 43 | #-------------------------------------- 44 | # Image: final 45 | #-------------------------------------- 46 | FROM base 47 | 48 | COPY --from=test-jb /.dummy /.dummy 49 | -------------------------------------------------------------------------------- /test/jb/test/jsonnetfile.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": [ 4 | { 5 | "source": { 6 | "git": { 7 | "remote": "https://github.com/prometheus-operator/prometheus-operator.git", 8 | "subdir": "jsonnet/prometheus-operator" 9 | } 10 | }, 11 | "version": "v0.50.0" 12 | } 13 | ], 14 | "legacyImports": true 15 | } 16 | -------------------------------------------------------------------------------- /test/jb/test/jsonnetfile.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": [ 4 | { 5 | "source": { 6 | "git": { 7 | "remote": "https://github.com/prometheus-operator/prometheus-operator.git", 8 | "subdir": "jsonnet/prometheus-operator" 9 | } 10 | }, 11 | "version": "cc6cb1ed7e58be6189bab001d239cd1df3ff9146", 12 | "sum": "J1G++A8hrtr3+OZQMmcNeb1w/C30bXqqwpwHL/Xhsd4=" 13 | } 14 | ], 15 | "legacyImports": false 16 | } 17 | -------------------------------------------------------------------------------- /test/latest/src/etc/nginx/sites-enabled/default: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | listen [::]:80 default_server; 4 | 5 | 6 | listen 443 ssl default_server; 7 | listen [::]:443 ssl default_server; 8 | 9 | ssl_certificate /test/renovate-chain.pem; 10 | ssl_certificate_key /test/renovate.key; 11 | 12 | root /var/www/html; 13 | 14 | # Add index.php to the list if you are using PHP 15 | index index.html index.htm index.nginx-debian.html; 16 | 17 | server_name _; 18 | 19 | location /-/ping { 20 | default_type application/json; 21 | return 200 "{}"; 22 | } 23 | 24 | location / { 25 | try_files $uri $uri/ =404; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/latest/src/test/ca.conf: -------------------------------------------------------------------------------- 1 | 2 | [ req ] 3 | default_bits = 2048 4 | default_md = sha256 5 | prompt = no 6 | encrypt_key = no 7 | distinguished_name = dn 8 | 9 | [ dn ] 10 | O = Renovate 11 | CN = Renovate ROOT CA 12 | -------------------------------------------------------------------------------- /test/latest/src/test/ca.srl: -------------------------------------------------------------------------------- 1 | 1234 2 | -------------------------------------------------------------------------------- /test/latest/src/test/cert.conf: -------------------------------------------------------------------------------- 1 | [ req ] 2 | default_bits = 2048 3 | default_md = sha256 4 | prompt = no 5 | encrypt_key = no 6 | distinguished_name = dn 7 | req_extensions = v3_req 8 | 9 | [ dn ] 10 | O = Renovate test 11 | 12 | [ v3_req ] 13 | keyUsage = keyEncipherment, dataEncipherment 14 | extendedKeyUsage = serverAuth 15 | subjectAltName = @alt_names 16 | 17 | [alt_names] 18 | DNS.1 = localhost 19 | DNS.2 = buildkitsandbox 20 | -------------------------------------------------------------------------------- /test/latest/src/test/request.mjs: -------------------------------------------------------------------------------- 1 | import https from 'https'; 2 | 3 | const options = { 4 | hostname: 'localhost', 5 | port: 443, 6 | path: '/', 7 | method: 'GET', 8 | }; 9 | 10 | const req = https.request(options); 11 | 12 | req.on('error', (error) => { 13 | console.error(error); 14 | process.exit(1); 15 | }); 16 | 17 | req.end(); 18 | -------------------------------------------------------------------------------- /test/latest/src/test/request.php: -------------------------------------------------------------------------------- 1 | uri.scheme == 'https') do |http| 7 | request = Net::HTTP::Get.new uri 8 | 9 | response = http.request request # Net::HTTPResponse object 10 | end 11 | -------------------------------------------------------------------------------- /test/nix/Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | #-------------------------------------- 23 | # Image: base 24 | #-------------------------------------- 25 | FROM ${BASE_IMAGE} AS base 26 | 27 | RUN uname -p | tee | grep aarch64 28 | RUN touch /.dummy 29 | 30 | ARG APT_HTTP_PROXY 31 | ARG CONTAINERBASE_CDN 32 | ARG CONTAINERBASE_DEBUG 33 | ARG CONTAINERBASE_LOG_LEVEL 34 | 35 | #-------------------------------------- 36 | # Image: nix 37 | #-------------------------------------- 38 | FROM base AS test-nix 39 | 40 | # renovate: datasource=github-releases packageName=containerbase/nix-prebuild 41 | RUN install-tool nix 2.29.0 42 | 43 | #-------------------------------------- 44 | # Image: final 45 | #-------------------------------------- 46 | FROM base 47 | 48 | COPY --from=test-nix /.dummy /.dummy 49 | -------------------------------------------------------------------------------- /test/nix/test/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1659131907, 6 | "narHash": "sha256-8bz4k18M/FuVC+EVcI4aREN2PsEKT7LGmU2orfjnpCg=", 7 | "owner": "nixos", 8 | "repo": "nixpkgs", 9 | "rev": "8d435fca5c561da8168abb30270788d2da2a7951", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "nixos", 14 | "ref": "nixos-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /test/nix/test/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "update-flake-lock"; 3 | 4 | inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 5 | 6 | outputs = 7 | { self 8 | , nixpkgs 9 | }: 10 | let 11 | nameValuePair = name: value: { inherit name value; }; 12 | genAttrs = names: f: builtins.listToAttrs (map (n: nameValuePair n (f n)) names); 13 | 14 | allSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; 15 | forAllSystems = f: genAttrs allSystems 16 | (system: f { 17 | inherit system; 18 | pkgs = import nixpkgs { inherit system; }; 19 | }); 20 | in 21 | { 22 | devShell = forAllSystems 23 | ({ system, pkgs, ... }: 24 | pkgs.stdenv.mkDerivation { 25 | name = "update-flake-lock-devshell"; 26 | buildInputs = [ pkgs.shellcheck ]; 27 | src = self; 28 | }); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /test/node/test/a/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | enableInlineBuilds: true 3 | -------------------------------------------------------------------------------- /test/node/test/a/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "re2": "1.21.4", 7 | "semver": "7.3.2" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/node/test/b/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "semver": "7.3.2" 7 | }, 8 | "engines": { 9 | "yarn": "=>3.3.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/node/test/b/yarn.lock: -------------------------------------------------------------------------------- 1 | # This file is generated by running "yarn install" inside your project. 2 | # Manual changes might be lost - proceed with caution! 3 | 4 | __metadata: 5 | version: 6 6 | cacheKey: 8 7 | 8 | "semver@npm:7.3.2": 9 | version: 7.3.2 10 | resolution: "semver@npm:7.3.2" 11 | bin: 12 | semver: bin/semver.js 13 | checksum: 692f4900dadb43919614b0df9af23fe05743051cda0d1735b5e4d76f93c9e43a266fae73cfc928f5d1489f022c5c0e65dfd2900fcf5b1839c4e9a239729afa7b 14 | languageName: node 15 | linkType: hard 16 | 17 | "test@workspace:.": 18 | version: 0.0.0-use.local 19 | resolution: "test@workspace:." 20 | dependencies: 21 | semver: 7.3.2 22 | languageName: unknown 23 | linkType: soft 24 | -------------------------------------------------------------------------------- /test/path.ts: -------------------------------------------------------------------------------- 1 | import { join, sep } from 'node:path'; 2 | 3 | export function cachePath(path: string): string { 4 | return `${globalThis.cacheDir}/${path}`.replace(/\/+/g, sep); 5 | } 6 | 7 | export function rootPath(path?: string): string { 8 | if (!path) { 9 | return globalThis.rootDir!.replace(/\/+/g, sep); 10 | } 11 | return join(globalThis.rootDir!, path).replace(/\/+/g, sep); 12 | } 13 | -------------------------------------------------------------------------------- /test/php/Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | #-------------------------------------- 23 | # Image: base 24 | #-------------------------------------- 25 | FROM ${BASE_IMAGE} AS base 26 | 27 | RUN uname -p | tee | grep aarch64 28 | RUN touch /.dummy 29 | 30 | ARG APT_HTTP_PROXY 31 | ARG CONTAINERBASE_CDN 32 | ARG CONTAINERBASE_DEBUG 33 | ARG CONTAINERBASE_LOG_LEVEL 34 | 35 | #-------------------------------------- 36 | # Image: php 37 | #-------------------------------------- 38 | FROM base AS test-php 39 | 40 | # renovate: datasource=github-releases packageName=containerbase/php-prebuild 41 | RUN install-tool php 8.4.7 42 | #-------------------------------------- 43 | # Image: composer 44 | #-------------------------------------- 45 | FROM test-php AS test-composer 46 | 47 | # renovate: datasource=github-releases packageName=containerbase/composer-prebuild 48 | RUN install-tool composer 2.8.9 49 | 50 | #-------------------------------------- 51 | # Image: final 52 | #-------------------------------------- 53 | FROM base 54 | 55 | COPY --from=test-php /.dummy /.dummy 56 | COPY --from=test-composer /.dummy /.dummy 57 | -------------------------------------------------------------------------------- /test/php/test/a/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "monolog/monolog": "1.0.2", 4 | "symfony/console": "4.4.17" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/powershell/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | #-------------------------------------- 23 | # Image: base 24 | #-------------------------------------- 25 | FROM ${BASE_IMAGE} AS base 26 | 27 | RUN touch /.dummy 28 | 29 | ARG APT_HTTP_PROXY 30 | ARG CONTAINERBASE_CDN 31 | ARG CONTAINERBASE_DEBUG 32 | ARG CONTAINERBASE_LOG_LEVEL 33 | 34 | #-------------------------------------- 35 | # test: powershell 7.2 (non-root) 36 | #-------------------------------------- 37 | FROM base AS testa 38 | 39 | RUN prepare-tool powershell 40 | 41 | USER 12021 42 | 43 | # Don't update 44 | RUN install-tool powershell v7.2.8 45 | 46 | 47 | RUN set -ex; \ 48 | pwsh -Version 49 | 50 | RUN set -ex; \ 51 | pwsh -Command Write-Host Hello, World! 52 | 53 | SHELL [ "/bin/sh", "-c" ] 54 | RUN pwsh --version 55 | 56 | #-------------------------------------- 57 | # test: powershell 7.x 58 | #-------------------------------------- 59 | FROM base AS testb 60 | 61 | # renovate: datasource=github-releases packageName=PowerShell/PowerShell 62 | RUN install-tool powershell v7.5.1 63 | 64 | USER 12021 65 | 66 | RUN set -ex; \ 67 | pwsh -Version 68 | 69 | RUN set -ex; \ 70 | pwsh -Command Write-Host Hello, World! 71 | 72 | SHELL [ "/bin/sh", "-c" ] 73 | RUN pwsh --version 74 | 75 | 76 | #-------------------------------------- 77 | FROM base 78 | 79 | COPY --from=testa /.dummy /.dummy 80 | COPY --from=testb /.dummy /.dummy 81 | -------------------------------------------------------------------------------- /test/powershell/Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | #-------------------------------------- 23 | # Image: base 24 | #-------------------------------------- 25 | FROM ${BASE_IMAGE} AS base 26 | 27 | RUN uname -p | tee | grep aarch64 28 | RUN touch /.dummy 29 | 30 | ARG APT_HTTP_PROXY 31 | ARG CONTAINERBASE_CDN 32 | ARG CONTAINERBASE_DEBUG 33 | ARG CONTAINERBASE_LOG_LEVEL 34 | 35 | #-------------------------------------- 36 | # Image: test 37 | #-------------------------------------- 38 | FROM base AS test-powershell 39 | 40 | # renovate: datasource=github-releases packageName=PowerShell/PowerShell 41 | RUN install-tool powershell v7.5.1 42 | 43 | #-------------------------------------- 44 | # Image: final 45 | #-------------------------------------- 46 | FROM base 47 | 48 | COPY --from=test-powershell /.dummy /.dummy 49 | -------------------------------------------------------------------------------- /test/python/test/a/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | numpy = ">=1.18.0" 8 | 9 | [dev-packages] 10 | flake8 = "*" 11 | twine = "*" 12 | bleach = "==3.1.1" 13 | -------------------------------------------------------------------------------- /test/python/test/b-conan/conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | lest/1.35.1 3 | [generators] 4 | CMakeDeps 5 | CMakeToolchain 6 | [layout] 7 | cmake_layout 8 | -------------------------------------------------------------------------------- /test/python/test/c-poetry/pyproject.toml: -------------------------------------------------------------------------------- 1 | 2 | [tool.poetry] 3 | name = "containerbase-test" 4 | version = "0.2.1" 5 | description = "Testing" 6 | authors = [] 7 | license = "MIT" 8 | 9 | [tool.poetry.dependencies] 10 | python = ">3.9" 11 | numpy = "^1.21.3" 12 | PyYAML = "^6.0.1" 13 | pampy = "^0.2.1" 14 | 15 | [tool.poetry.dev-dependencies] 16 | pytest = "^7.1" 17 | mock = "^3.0" 18 | atomicwrites = "^1.3" 19 | 20 | [build-system] 21 | requires = ["poetry>=0.12"] 22 | build-backend = "poetry.masonry.api" 23 | -------------------------------------------------------------------------------- /test/python/test/d-poetry/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "containerbase-test" 3 | version = "0.2.1" 4 | description = "Testing" 5 | authors = [] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = ">3.6" 10 | numpy = "^1.16" 11 | PyYAML = "^5.1" 12 | pampy = { version = "^0.3.0", python = ">3.6" } 13 | 14 | [tool.poetry.dev-dependencies] 15 | pytest = "^4.4" 16 | mock = "^3.0" 17 | 18 | [build-system] 19 | requires = ["poetry>=0.12"] 20 | build-backend = "poetry.masonry.api" 21 | -------------------------------------------------------------------------------- /test/python/test/f/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.* 2 | distribute==0.* 3 | dj-database-url==0.* 4 | psycopg2==2.* 5 | wsgiref==0.* 6 | -------------------------------------------------------------------------------- /test/python/test/pipenv-b/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | numpy = ">=1.18.0" 8 | psycopg2 = "*" 9 | 10 | [dev-packages] 11 | flake8 = "*" 12 | twine = "*" 13 | bleach = "==3.1.1" 14 | 15 | [requires] 16 | python_version = "3.10" 17 | -------------------------------------------------------------------------------- /test/ruby/Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | #-------------------------------------- 23 | # Image: base 24 | #-------------------------------------- 25 | FROM ${BASE_IMAGE} AS base 26 | 27 | RUN uname -p | tee | grep aarch64 28 | RUN touch /.dummy 29 | 30 | ARG APT_HTTP_PROXY 31 | ARG CONTAINERBASE_CDN 32 | ARG CONTAINERBASE_DEBUG 33 | ARG CONTAINERBASE_LOG_LEVEL 34 | 35 | #-------------------------------------- 36 | # Image: ruby 37 | #-------------------------------------- 38 | FROM base AS test-ruby 39 | 40 | # renovate: datasource=github-releases packageName=containerbase/ruby-prebuild versioning=ruby 41 | RUN install-tool ruby 3.4.4 42 | 43 | #-------------------------------------- 44 | # Image: bundler 45 | #-------------------------------------- 46 | FROM test-ruby AS test-bundler 47 | 48 | # renovate: datasource=rubygems versioning=ruby 49 | RUN install-tool bundler 2.6.9 50 | 51 | #-------------------------------------- 52 | # Image: cocoapods 53 | #-------------------------------------- 54 | FROM test-ruby AS test-cocoapods 55 | 56 | # cocoapods need git, but don't install yet 57 | # renovate: datasource=github-tags packageName=git/git 58 | RUN install-tool git v2.49.0 59 | 60 | # renovate: datasource=rubygems versioning=ruby 61 | RUN install-tool cocoapods 1.16.2 62 | 63 | #-------------------------------------- 64 | # Image: final 65 | #-------------------------------------- 66 | FROM base 67 | 68 | COPY --from=test-ruby /.dummy /.dummy 69 | COPY --from=test-bundler /.dummy /.dummy 70 | COPY --from=test-cocoapods /.dummy /.dummy 71 | -------------------------------------------------------------------------------- /test/ruby/test/b/CPDAcknowledgements.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "CPDAcknowledgements" 3 | s.version = "1.0.0" 4 | s.summary = "Show your CocoaPods dependencies in-app." 5 | s.description = <<-DESC 6 | Show your CocoaPods library and contributors in-app with smart defaults, and customisable view controllers. 7 | DESC 8 | s.homepage = "https://github.com/CocoaPods/CPDAcknowledgements" 9 | s.license = 'MIT' 10 | s.author = { "Orta Therox" => "orta.therox@gmail.com", "Fabio Pelosin" => "fabiopelosin@gmail.com" } 11 | s.source = { :git => "https://github.com/CocoaPods/CPDAcknowledgements.git", :tag => s.version.to_s } 12 | s.homepage = "https://github.com/CocoaPods/CPDAcknowledgements" 13 | s.social_media_url = "https://twitter.com/CocoaPods" 14 | s.ios.deployment_target = '8.0' 15 | s.source_files = 'CPDAcknowledgements/**/**' 16 | s.private_header_files = 'CPDAcknowledgements/private/*.h' 17 | s.ios.frameworks = 'UIKit' 18 | end 19 | -------------------------------------------------------------------------------- /test/ruby/test/b/CPDAcknowledgements/private/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containerbase/base/b8411d92e1a7992abe94769b4bfdbacacdcdf17a/test/ruby/test/b/CPDAcknowledgements/private/.gitkeep -------------------------------------------------------------------------------- /test/ruby/test/b/Project/Podfile: -------------------------------------------------------------------------------- 1 | #plugin 'cocoapods-acknowledgements' 2 | 3 | target "Demo Project" do 4 | pod "CPDAcknowledgements", :path => "../CPDAcknowledgements.podspec" 5 | 6 | # These pods are used only for giving us some data 7 | pod "ORStackView" 8 | pod "IRFEmojiCheatSheet" 9 | 10 | target "Demo ProjectTests" do 11 | inherit! :search_paths 12 | 13 | pod 'Specta', '~> 1.0' 14 | pod 'Expecta', '~> 1.0' 15 | pod 'OCMockito', '~> 1.0' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/ruby/test/b/Project/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - CPDAcknowledgements (0.5.0) 3 | - Expecta (1.0.5) 4 | - FLKAutoLayout (0.2.1) 5 | - IRFEmojiCheatSheet (0.3.0) 6 | - OCHamcrest (4.3.0) 7 | - OCMockito (1.4.0): 8 | - OCHamcrest (~> 4.0) 9 | - ORStackView (3.0.1): 10 | - FLKAutoLayout (~> 0.2) 11 | - Specta (1.0.5) 12 | 13 | DEPENDENCIES: 14 | - CPDAcknowledgements (from `../CPDAcknowledgements.podspec`) 15 | - Expecta (~> 1.0) 16 | - IRFEmojiCheatSheet 17 | - OCMockito (~> 1.0) 18 | - ORStackView 19 | - Specta (~> 1.0) 20 | 21 | EXTERNAL SOURCES: 22 | CPDAcknowledgements: 23 | :path: "../CPDAcknowledgements.podspec" 24 | 25 | SPEC CHECKSUMS: 26 | CPDAcknowledgements: 32f6dfcc35eed5d9897f13947024b9d32133e2fa 27 | Expecta: e1c022fcd33910b6be89c291d2775b3fe27a89fe 28 | FLKAutoLayout: 9db6b30c2008d230da608e62c607b11c23b942e6 29 | IRFEmojiCheatSheet: 364b2733c4e37c65ef9f56225246cdf42068370f 30 | OCHamcrest: cd63d27f48a266d4412c0b295b01b8f0940efa81 31 | OCMockito: 4981140c9a9ec06c31af40f636e3c0f25f27e6b2 32 | ORStackView: a1bb52748cd0ae29891c140baf22ff8972fb363c 33 | Specta: ac94d110b865115fe60ff2c6d7281053c6f8e8a2 34 | 35 | PODFILE CHECKSUM: 1c110034e6fc846de3ca560c07d3c7cb43780d13 36 | 37 | COCOAPODS: 1.0.0.beta.6 38 | -------------------------------------------------------------------------------- /test/rust/Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | #-------------------------------------- 23 | # Image: base 24 | #-------------------------------------- 25 | FROM ${BASE_IMAGE} AS base 26 | 27 | RUN uname -p | tee | grep aarch64 28 | RUN touch /.dummy 29 | 30 | ARG APT_HTTP_PROXY 31 | ARG CONTAINERBASE_CDN 32 | ARG CONTAINERBASE_DEBUG 33 | ARG CONTAINERBASE_LOG_LEVEL 34 | 35 | #-------------------------------------- 36 | # Image: rust 37 | #-------------------------------------- 38 | FROM base AS test-rust 39 | 40 | # renovate: datasource=docker versioning=docker 41 | RUN install-tool rust 1.87.0 42 | 43 | #-------------------------------------- 44 | # Image: final 45 | #-------------------------------------- 46 | FROM base 47 | 48 | COPY --from=test-rust /.dummy /.dummy 49 | -------------------------------------------------------------------------------- /test/rust/test/a/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "workspace_example" 3 | version = "0.1.0" 4 | authors = [] 5 | 6 | [workspace] 7 | members = [ 8 | "crate1", 9 | "crate2", 10 | "subdir/crate3" 11 | ] 12 | 13 | [dependencies] 14 | crate4 = { path = "subdir/crate4", version = "0.1.0" } 15 | crate5 = { path = "crate5", version = "0.1.0" } 16 | serde = "1.0.106" 17 | -------------------------------------------------------------------------------- /test/rust/test/a/crate1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crate1" 3 | version = "0.1.0" 4 | authors = [] 5 | 6 | [dependencies] 7 | log = "0.1.0" 8 | -------------------------------------------------------------------------------- /test/rust/test/a/crate1/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /test/rust/test/a/crate2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crate2" 3 | version = "0.1.0" 4 | authors = [] 5 | 6 | [dependencies] 7 | env_logger = "0.3.0" 8 | -------------------------------------------------------------------------------- /test/rust/test/a/crate2/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /test/rust/test/a/crate5/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crate5" 3 | version = "0.1.0" 4 | authors = [] 5 | 6 | [dependencies] 7 | rodio = "0.3.0" 8 | -------------------------------------------------------------------------------- /test/rust/test/a/crate5/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /test/rust/test/a/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /test/rust/test/a/subdir/crate3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crate3" 3 | version = "0.1.0" 4 | authors = [] 5 | 6 | [dependencies] 7 | libc = "0.1.0" 8 | -------------------------------------------------------------------------------- /test/rust/test/a/subdir/crate3/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /test/rust/test/a/subdir/crate4/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crate4" 3 | version = "0.1.0" 4 | authors = [] 5 | 6 | [dependencies] 7 | gfx = "0.10.0" 8 | -------------------------------------------------------------------------------- /test/rust/test/a/subdir/crate4/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /test/swift/Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=containerbase 2 | 3 | #-------------------------------------- 4 | # Image: containerbase 5 | #-------------------------------------- 6 | FROM ghcr.io/containerbase/ubuntu:24.04 AS containerbase 7 | 8 | ENV BASH_ENV=/usr/local/etc/env 9 | SHELL ["/bin/bash" , "-c"] 10 | 11 | ARG TARGETARCH 12 | COPY dist/docker/ / 13 | COPY dist/cli/containerbase-cli-${TARGETARCH} /usr/local/containerbase/bin/containerbase-cli 14 | 15 | ARG APT_HTTP_PROXY 16 | ARG CONTAINERBASE_CDN 17 | ARG CONTAINERBASE_DEBUG 18 | ARG CONTAINERBASE_LOG_LEVEL 19 | 20 | RUN install-containerbase 21 | 22 | #-------------------------------------- 23 | # Image: base 24 | #-------------------------------------- 25 | FROM ${BASE_IMAGE} AS base 26 | 27 | RUN uname -p | tee | grep aarch64 28 | RUN touch /.dummy 29 | 30 | ARG APT_HTTP_PROXY 31 | ARG CONTAINERBASE_CDN 32 | ARG CONTAINERBASE_DEBUG 33 | ARG CONTAINERBASE_LOG_LEVEL 34 | 35 | #-------------------------------------- 36 | # Image: swift 37 | #-------------------------------------- 38 | FROM base AS test-swift 39 | 40 | # renovate: datasource=docker versioning=docker 41 | RUN install-tool swift 6.1.0 42 | 43 | #-------------------------------------- 44 | # Image: final 45 | #-------------------------------------- 46 | FROM base 47 | 48 | COPY --from=test-swift /.dummy /.dummy 49 | -------------------------------------------------------------------------------- /test/swift/test/a/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "swift-argument-parser", 6 | "repositoryURL": "https://github.com/apple/swift-argument-parser.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "3d79b2b5a2e5af52c14e462044702ea7728f5770", 10 | "version": "0.1.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /test/swift/test/a/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "merc", 6 | platforms: [.macOS(.v11)], 7 | products: [.executable(name: "sample", targets: ["sample"])], 8 | dependencies: [ 9 | .package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "0.1.0")), 10 | ], 11 | targets: [ 12 | .target( 13 | name: "sample", 14 | dependencies: [ 15 | .product(name: "ArgumentParser", package: "swift-argument-parser") 16 | ] 17 | ) 18 | ] 19 | ) 20 | -------------------------------------------------------------------------------- /test/swift/test/b/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "swift-argument-parser", 6 | "repositoryURL": "https://github.com/apple/swift-argument-parser.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "3d79b2b5a2e5af52c14e462044702ea7728f5770", 10 | "version": "0.1.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /test/swift/test/b/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "merc", 6 | platforms: [.macOS(.v11)], 7 | products: [.executable(name: "sample", targets: ["sample"])], 8 | dependencies: [ 9 | .package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "0.2.2")), 10 | ], 11 | targets: [ 12 | .target( 13 | name: "sample", 14 | dependencies: [ 15 | .product(name: "ArgumentParser", package: "swift-argument-parser") 16 | ] 17 | ) 18 | ] 19 | ) 20 | -------------------------------------------------------------------------------- /test/swift/test/c/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "swift-argument-parser", 6 | "repositoryURL": "https://github.com/apple/swift-argument-parser.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "3d79b2b5a2e5af52c14e462044702ea7728f5770", 10 | "version": "0.1.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /test/swift/test/c/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "merc", 6 | platforms: [.macOS(.v10_15)], 7 | products: [.executable(name: "sample", targets: ["sample"])], 8 | dependencies: [ 9 | .package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "0.2.2")), 10 | ], 11 | targets: [ 12 | .target( 13 | name: "sample", 14 | dependencies: [ 15 | .product(name: "ArgumentParser", package: "swift-argument-parser") 16 | ] 17 | ) 18 | ] 19 | ) 20 | -------------------------------------------------------------------------------- /test/types.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-var */ 2 | declare var cacheDir: string; 3 | -------------------------------------------------------------------------------- /tools/bats.js: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'node:module'; 2 | import { Command, Option, runExit } from 'clipanion'; 3 | import { execa } from 'execa'; 4 | 5 | const require = createRequire(import.meta.url); 6 | 7 | class BatsCommand extends Command { 8 | args = Option.Proxy(); 9 | 10 | async execute() { 11 | const bats = require.resolve('bats/bin/bats'); 12 | const batsAssert = require.resolve('bats-assert/load.bash'); 13 | const batsSupport = require.resolve('bats-support/load.bash'); 14 | 15 | await execa(bats, this.args, { 16 | env: { 17 | BATS_ASSERT_LOAD_PATH: batsAssert, 18 | BATS_SUPPORT_LOAD_PATH: batsSupport, 19 | }, 20 | stdio: 'inherit', 21 | }); 22 | } 23 | } 24 | 25 | void runExit(BatsCommand); 26 | -------------------------------------------------------------------------------- /tools/containerbase.acl: -------------------------------------------------------------------------------- 1 | ppa.launchpad.net 2 | binaries.erlang-solutions.com 3 | -------------------------------------------------------------------------------- /tools/prepare-proxy.js: -------------------------------------------------------------------------------- 1 | import shell from 'shelljs'; 2 | 3 | shell.config.fatal = true; 4 | 5 | shell.echo(`Preparing squid-deb-proxy`); 6 | 7 | shell.exec('apt-get -qq update'); 8 | shell.exec('apt-get install -y squid-deb-proxy'); 9 | shell 10 | .cat('./tools/containerbase.acl') 11 | .to('/etc/squid-deb-proxy/mirror-dstdomain.acl.d/containerbase.acl'); 12 | shell.exec('systemctl reload squid-deb-proxy'); 13 | -------------------------------------------------------------------------------- /tools/prepare-release.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import { Command, Option, runExit } from 'clipanion'; 3 | import shell from 'shelljs'; 4 | import { hashFile } from './utils.js'; 5 | 6 | shell.config.fatal = true; 7 | 8 | class PrepareCommand extends Command { 9 | release = Option.String('-r,--release', { required: true }); 10 | gitSha = Option.String('--sha'); 11 | dryRun = Option.Boolean('-d,--dry-run'); 12 | 13 | async execute() { 14 | const version = this.release; 15 | 16 | shell.echo(`Preparing version: ${version}`); 17 | 18 | if (this.dryRun) { 19 | shell.echo('DRY-RUN: done.'); 20 | return 0; 21 | } 22 | 23 | process.env.TAG = version; 24 | process.env.CONTAINERBASE_VERSION = version; 25 | 26 | shell.mkdir('-p', 'bin'); 27 | 28 | shell.exec('pnpm build'); 29 | 30 | await fs.writeFile('dist/docker/usr/local/containerbase/version', version); 31 | shell.exec(`tar -cJf ./bin/containerbase.tar.xz -C ./dist/docker .`); 32 | 33 | await hashFile('./bin/containerbase.tar.xz', 'sha512'); 34 | await hashFile('./dist/cli/containerbase-cli-amd64', 'sha512'); 35 | await hashFile('./dist/cli/containerbase-cli-arm64', 'sha512'); 36 | 37 | shell.exec( 38 | 'docker buildx bake --set settings.platform=linux/amd64,linux/arm64 build', 39 | ); 40 | 41 | return 0; 42 | } 43 | } 44 | 45 | void runExit(PrepareCommand); 46 | -------------------------------------------------------------------------------- /tools/publish-release.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import os from 'node:os'; 3 | import path from 'node:path'; 4 | import { Command, Option, runExit } from 'clipanion'; 5 | import shell from 'shelljs'; 6 | 7 | class ReleaseCommand extends Command { 8 | release = Option.String('-r,--release', { required: true }); 9 | gitSha = Option.String('--sha'); 10 | dryRun = Option.Boolean('-d,--dry-run'); 11 | 12 | async execute() { 13 | await Promise.resolve(); 14 | const version = this.release; 15 | const dry = this.dryRun; 16 | /** @type {shell.ShellString} */ 17 | let r; 18 | 19 | shell.echo(`Publish version: ${version}`); 20 | process.env.TAG = version; 21 | process.env.CONTAINERBASE_VERSION = version; 22 | 23 | const tmp = await fs.mkdtemp( 24 | path.join(os.tmpdir(), 'renovate-docker-bake-'), 25 | ); 26 | const metadataFile = path.join(tmp, 'metadata.json'); 27 | 28 | if (dry) { 29 | shell.echo('DRY-RUN: done.'); 30 | return 0; 31 | } 32 | 33 | shell.echo('Pushing docker images'); 34 | 35 | r = shell.exec( 36 | `docker buildx bake --set settings.platform=linux/amd64,linux/arm64 --metadata-file ${metadataFile} push`, 37 | ); 38 | if (r.code) { 39 | return 1; 40 | } 41 | 42 | const meta = JSON.parse(await fs.readFile(metadataFile, 'utf8')); 43 | const digest = meta?.push?.['containerimage.digest']; 44 | 45 | if (!digest) { 46 | shell.echo('Error: missing digest\n' + JSON.stringify(meta, null, 2)); 47 | return 1; 48 | } 49 | 50 | r = shell.exec( 51 | `cosign sign --yes ${process.env.OWNER}/${process.env.FILE}:@${digest}`, 52 | ); 53 | if (r.code) { 54 | return 1; 55 | } 56 | 57 | r = shell.exec( 58 | `cosign sign --yes ghcr.io/${process.env.OWNER}/${process.env.FILE}:@${digest}`, 59 | ); 60 | if (r.code) { 61 | return 1; 62 | } 63 | 64 | return 0; 65 | } 66 | } 67 | 68 | void runExit(ReleaseCommand); 69 | -------------------------------------------------------------------------------- /tools/test.js: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs'; 2 | import { Command, Option, runExit } from 'clipanion'; 3 | import { execa } from 'execa'; 4 | import shell from 'shelljs'; 5 | 6 | shell.config.fatal = true; 7 | 8 | class TestCommand extends Command { 9 | tests = Option.Rest(); 10 | dryRun = Option.Boolean('-d,--dry-run'); 11 | target = Option.String('-t,--target'); 12 | build = Option.Boolean('-b,--build'); 13 | debug = Option.Boolean('-D,--debug'); 14 | logLevel = Option.String('-l,--log-level'); 15 | plain = Option.Boolean('-p,--plain'); 16 | 17 | async execute() { 18 | let tests = this.tests; 19 | let explicit = true; 20 | const env = {}; 21 | 22 | if (this.build) { 23 | shell.echo('Compiling sources'); 24 | shell.exec('pnpm build'); 25 | } 26 | 27 | if (!tests.length) { 28 | tests = shell.ls('test'); 29 | explicit = false; 30 | shell.echo('Running all tests'); 31 | } 32 | 33 | if (this.debug) { 34 | shell.echo('Debug mode enabled'); 35 | env.CONTAINERBASE_DEBUG = '1'; 36 | env.BUILDKIT_PROGRESS = 'plain'; 37 | } 38 | 39 | if (this.logLevel) { 40 | shell.echo('Setting log level to', this.logLevel); 41 | env.CONTAINERBASE_LOG_LEVEL = this.logLevel; 42 | env.BUILDKIT_PROGRESS = 'plain'; 43 | } 44 | 45 | if (this.plain) { 46 | shell.echo('Setting buildx plain progess'); 47 | env.BUILDKIT_PROGRESS = 'plain'; 48 | } 49 | 50 | for (const d of tests) { 51 | if ( 52 | !(await fs.stat(`test/${d}/Dockerfile`).catch(() => null))?.isFile() 53 | ) { 54 | if (explicit) { 55 | shell.echo(`test '${d}' not found!`); 56 | return 1; 57 | } 58 | continue; 59 | } 60 | shell.echo('Processing:', d); 61 | await execa('docker', ['buildx', 'bake', this.target ?? 'default'], { 62 | env: { ...env, TAG: d }, 63 | stdio: 'inherit', 64 | }); 65 | } 66 | return 0; 67 | } 68 | } 69 | 70 | void runExit(TestCommand); 71 | -------------------------------------------------------------------------------- /tools/utils.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import crypto from 'node:crypto'; 3 | import path from 'node:path'; 4 | 5 | /** 6 | * Writes `sum` compatible checksum file. 7 | * @param {string} file 8 | * @param {string} algorithm 9 | */ 10 | export async function hashFile(file, algorithm) { 11 | const data = await fs.readFile(file); 12 | const hash = crypto.createHash(algorithm); 13 | hash.update(data); 14 | await fs.writeFile( 15 | `${file}.${algorithm}`, 16 | `${hash.digest('hex')} ${path.basename(file)}`, 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "files": ["./src/cli/index.ts", "./src/cli/types.d.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@tsconfig/strictest/tsconfig.json", 4 | "@tsconfig/node20/tsconfig.json" 5 | ], 6 | "compilerOptions": { 7 | "baseUrl": ".", 8 | "allowJs": true, 9 | "noEmit": true, 10 | "types": ["node"], 11 | "verbatimModuleSyntax": true, 12 | "noImplicitOverride": true, 13 | "noPropertyAccessFromIndexSignature": false, 14 | "experimentalDecorators": true, 15 | "module": "ES2022", 16 | "moduleResolution": "Bundler", 17 | "paths": { 18 | "~test/*": ["./test/*"] 19 | } 20 | }, 21 | "exclude": ["node_modules", "bin", "dist", "coverage", "html"] 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [".eslintrc.cjs", "**/*.ts", "**/*.js", "**/*.mjs", "**/*.cjs"] 4 | } 5 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'node:process'; 2 | import tsconfigPaths from 'vite-tsconfig-paths'; 3 | import { defineConfig } from 'vitest/config'; 4 | 5 | const ci = !!env.CI; 6 | 7 | export default defineConfig({ 8 | plugins: [tsconfigPaths()], 9 | test: { 10 | coverage: { 11 | provider: 'v8', 12 | reporter: ci ? ['lcovonly', 'text'] : ['html', 'text'], 13 | include: ['src/cli/**/*.ts', '!**/__mocks__/**', '!**/types.ts'], 14 | }, 15 | reporters: ci ? ['default', 'github-actions'] : ['default', 'html'], 16 | restoreMocks: true, 17 | setupFiles: './test/global-setup.ts', 18 | deps: { moduleDirectories: ['node_modules', '.yarn/'] }, 19 | }, 20 | }); 21 | --------------------------------------------------------------------------------