diff --git a/.dockerignore b/.dockerignore index 4f75c951..43a0d0c5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,3 +3,4 @@ node_modules .gitignore *.md dist +rust/target diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 00000000..129e2a6c --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,28 @@ +# Workflows + +Only the release related workflows are described here. + +## Release artifacts + +`release.yaml` runs on `v*.*.*` tags and uploads archives to the GitHub release. The Rust CLI is the primary implementation and the JavaScript CLI is published under an `adc_js_` prefix. + +| Implementation | Archive name | Binary inside | +|---|---|---| +| Rust | `adc___.{tar.gz,zip}` | `adc` | +| Rust debug info | `adc____debuginfo.{tar.gz,zip}` | `adc.debug` (Linux), `adc.dSYM` (macOS), `adc.pdb` (Windows) | +| JavaScript | `adc_js___.{tar.gz,zip}` | `adc` | + +- `` is `linux`, `darwin` or `windows`, `` is `amd64` or `arm64`. +- The version comes from the tag. The Linux job fails the release when the tag does not match `version` under `[workspace.package]` in `rust/Cargo.toml`, so bump that first. +- Pull requests with the `test/build` label build only the Rust archives and upload them as workflow artifacts. + +### Linux glibc floor + +- The Linux job builds inside `almalinux:8` (glibc 2.28) so the binary runs on any glibc 2.28 or newer system. The `Check glibc floor` step fails when the binary references a newer `GLIBC_*` symbol version, which can happen after a toolchain or dependency change. +- `adc-linux-smoke` runs the built binary in Debian 11, Debian 12, Ubuntu 22.04 and Ubuntu 24.04 containers for both architectures. It runs after the archives are uploaded, so a failure does not block the upload. +- The binary is not static. Alpine and other musl systems need a glibc compatibility layer, which is not a supported target. +- The Linux job uses a container, so it can only use the JavaScript actions and shell tools available in that image. Install extra tools in the `Install build dependencies` step. + +## Docker images + +`docker.yaml` builds the images described in [libs/tools/README.md](../../libs/tools/README.md#docker-images). It runs on pushes to `main`, tags and pull requests, but only pushes images on `push` events. diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index e54b1a9d..2576b760 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -24,6 +24,7 @@ jobs: push: ${{ github.event_name == 'push' }} platforms: linux/amd64,linux/arm64 file: libs/tools/src/docker/Dockerfile + job-name-prefix: rust meta-images: | docker.io/api7/adc ghcr.io/api7/adc @@ -38,3 +39,31 @@ jobs: - registry: ghcr.io username: '${{ github.actor }}' password: '${{ secrets.GITHUB_TOKEN }}' + + build-js: + uses: docker/github-builder/.github/workflows/build.yml@c4a1b216d96a8c85b45a9974b37857828274c808 # v1.13.0 + permissions: + contents: read + packages: write + id-token: write + with: + output: image + push: ${{ github.event_name == 'push' }} + platforms: linux/amd64,linux/arm64 + file: libs/tools/src/docker/Dockerfile.js + job-name-prefix: js + meta-images: | + docker.io/api7/adc + ghcr.io/api7/adc + meta-tags: | + type=semver,pattern={{version}}-js + type=raw,value=dev-js,enable={{is_default_branch}} + meta-flavor: latest=false + secrets: + registry-auths: | + - registry: docker.io + username: '${{ secrets.DOCKER_USERNAME }}' + password: '${{ secrets.DOCKER_PASSWORD }}' + - registry: ghcr.io + username: '${{ github.actor }}' + password: '${{ secrets.GITHUB_TOKEN }}' diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d9ba909e..d525fc68 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -10,7 +10,7 @@ on: env: NODE_VERSION: "24.16.0" jobs: - adc: + adc-js: if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest permissions: @@ -43,25 +43,25 @@ jobs: run: | mkdir -p release mv ./node-binary/linux-amd64 ./adc - tar -czf release/adc_${{ env.RELEASE_VERSION }}_linux_amd64.tar.gz ./adc && rm -rf ./adc + tar -czf release/adc_js_${{ env.RELEASE_VERSION }}_linux_amd64.tar.gz ./adc && rm -rf ./adc mv ./node-binary/linux-arm64 ./adc - tar -czf release/adc_${{ env.RELEASE_VERSION }}_linux_arm64.tar.gz ./adc && rm -rf ./adc + tar -czf release/adc_js_${{ env.RELEASE_VERSION }}_linux_arm64.tar.gz ./adc && rm -rf ./adc mv ./node-binary/win-x64.exe ./adc.exe - zip -r release/adc_${{ env.RELEASE_VERSION }}_windows_amd64.zip ./adc.exe && rm -rf ./adc.exe + zip -r release/adc_js_${{ env.RELEASE_VERSION }}_windows_amd64.zip ./adc.exe && rm -rf ./adc.exe mv ./node-binary/win-arm64.exe ./adc.exe - zip -r release/adc_${{ env.RELEASE_VERSION }}_windows_arm64.zip ./adc.exe && rm -rf ./adc.exe + zip -r release/adc_js_${{ env.RELEASE_VERSION }}_windows_arm64.zip ./adc.exe && rm -rf ./adc.exe - name: Upload ADC to GitHub release uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 if: startsWith(github.ref, 'refs/tags/') with: files: | - release/adc_${{ env.RELEASE_VERSION }}_linux_amd64.tar.gz - release/adc_${{ env.RELEASE_VERSION }}_linux_arm64.tar.gz - release/adc_${{ env.RELEASE_VERSION }}_windows_amd64.zip - release/adc_${{ env.RELEASE_VERSION }}_windows_arm64.zip + release/adc_js_${{ env.RELEASE_VERSION }}_linux_amd64.tar.gz + release/adc_js_${{ env.RELEASE_VERSION }}_linux_arm64.tar.gz + release/adc_js_${{ env.RELEASE_VERSION }}_windows_amd64.zip + release/adc_js_${{ env.RELEASE_VERSION }}_windows_arm64.zip - adc-macos: + adc-js-macos: if: startsWith(github.ref, 'refs/tags/') runs-on: macos-26 permissions: @@ -95,19 +95,19 @@ jobs: run: | mkdir release mv ./node-binary/darwin-arm64 ./adc - tar -czf release/adc_${{ env.RELEASE_VERSION }}_darwin_arm64.tar.gz ./adc && rm -rf ./adc + tar -czf release/adc_js_${{ env.RELEASE_VERSION }}_darwin_arm64.tar.gz ./adc && rm -rf ./adc mv ./node-binary/darwin-x64 ./adc - tar -czf release/adc_${{ env.RELEASE_VERSION }}_darwin_amd64.tar.gz ./adc && rm -rf ./adc + tar -czf release/adc_js_${{ env.RELEASE_VERSION }}_darwin_amd64.tar.gz ./adc && rm -rf ./adc - name: Upload ADC to GitHub release uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 if: startsWith(github.ref, 'refs/tags/') with: files: | - release/adc_${{ env.RELEASE_VERSION }}_darwin_arm64.tar.gz - release/adc_${{ env.RELEASE_VERSION }}_darwin_amd64.tar.gz + release/adc_js_${{ env.RELEASE_VERSION }}_darwin_arm64.tar.gz + release/adc_js_${{ env.RELEASE_VERSION }}_darwin_amd64.tar.gz - adc-rust-linux: + adc-linux: if: startsWith(github.ref, 'refs/tags/') || contains(github.event.pull_request.labels.*.name, 'test/build') strategy: fail-fast: false @@ -118,9 +118,15 @@ jobs: - os: ubuntu-24.04-arm arch: arm64 runs-on: ${{ matrix.os }} + # Built on EL8 so the binary only needs glibc 2.28 or newer at runtime. + container: almalinux:8 permissions: contents: write steps: + - name: Install build dependencies + run: | + dnf install -y gcc-toolset-12-gcc gcc-toolset-12-binutils make cmake perl git tar gzip curl + echo "/opt/rh/gcc-toolset-12/root/usr/bin" >> $GITHUB_PATH - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -132,10 +138,19 @@ jobs: echo "RELEASE_VERSION=pr-${{ github.event.pull_request.number }}" >> $GITHUB_ENV fi + - name: Verify tag matches Cargo version + if: startsWith(github.ref, 'refs/tags/') + run: | + CARGO_VERSION=$(sed -n '/^\[workspace.package\]/,/^\[/s/^version = "\(.*\)"/\1/p' rust/Cargo.toml) + if [ "$CARGO_VERSION" != "$RELEASE_VERSION" ]; then + echo "Tag version $RELEASE_VERSION does not match rust/Cargo.toml version $CARGO_VERSION" + exit 1 + fi + - name: Setup Rust toolchain run: | - rustup update stable - rustup default stable + curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable + echo "$HOME/.cargo/bin" >> $GITHUB_PATH - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 with: workspaces: rust -> target @@ -144,6 +159,15 @@ jobs: working-directory: ./rust run: cargo build --release -p adc-cli + - name: Check glibc floor + run: | + MAX=$(objdump -T rust/target/release/adc | grep -o 'GLIBC_[0-9.]*' | sort -uV | tail -1) + echo "Highest required glibc symbol version: $MAX" + if [ "$(printf '%s\n' "$MAX" GLIBC_2.28 | sort -V | tail -1)" != "GLIBC_2.28" ]; then + echo "Binary requires $MAX, which is newer than the supported GLIBC_2.28" + exit 1 + fi + - name: Split debug info run: | BIN_DIR=rust/target/release @@ -155,28 +179,53 @@ jobs: run: | mkdir -p release BIN_DIR=rust/target/release - tar -czf release/adc_rust_${{ env.RELEASE_VERSION }}_linux_${{ matrix.arch }}.tar.gz -C "$BIN_DIR" adc - tar -czf release/adc_rust_${{ env.RELEASE_VERSION }}_linux_${{ matrix.arch }}_debuginfo.tar.gz -C "$BIN_DIR" adc.debug + tar -czf release/adc_${{ env.RELEASE_VERSION }}_linux_${{ matrix.arch }}.tar.gz -C "$BIN_DIR" adc + tar -czf release/adc_${{ env.RELEASE_VERSION }}_linux_${{ matrix.arch }}_debuginfo.tar.gz -C "$BIN_DIR" adc.debug - name: Upload adc to GitHub release uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 if: startsWith(github.ref, 'refs/tags/') with: files: | - release/adc_rust_${{ env.RELEASE_VERSION }}_linux_${{ matrix.arch }}.tar.gz - release/adc_rust_${{ env.RELEASE_VERSION }}_linux_${{ matrix.arch }}_debuginfo.tar.gz + release/adc_${{ env.RELEASE_VERSION }}_linux_${{ matrix.arch }}.tar.gz + release/adc_${{ env.RELEASE_VERSION }}_linux_${{ matrix.arch }}_debuginfo.tar.gz - name: Upload adc artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - if: github.event_name == 'pull_request' with: - name: adc_rust_linux_${{ matrix.arch }} + name: adc_linux_${{ matrix.arch }} path: | - release/adc_rust_${{ env.RELEASE_VERSION }}_linux_${{ matrix.arch }}.tar.gz - release/adc_rust_${{ env.RELEASE_VERSION }}_linux_${{ matrix.arch }}_debuginfo.tar.gz + release/adc_${{ env.RELEASE_VERSION }}_linux_${{ matrix.arch }}.tar.gz + release/adc_${{ env.RELEASE_VERSION }}_linux_${{ matrix.arch }}_debuginfo.tar.gz if-no-files-found: error - adc-rust-windows: + adc-linux-smoke: + needs: adc-linux + strategy: + fail-fast: false + matrix: + arch: [amd64, arm64] + image: ["debian:11", "debian:12", "ubuntu:22.04", "ubuntu:24.04"] + include: + - arch: amd64 + os: ubuntu-latest + - arch: arm64 + os: ubuntu-24.04-arm + runs-on: ${{ matrix.os }} + permissions: + contents: read + steps: + - name: Download adc artifact + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: adc_linux_${{ matrix.arch }} + - name: Run adc in ${{ matrix.image }} + run: | + mkdir bin + tar -xzf adc_*_linux_${{ matrix.arch }}.tar.gz -C bin + docker run --rm -v "$PWD/bin:/opt/adc:ro" ${{ matrix.image }} sh -c '/opt/adc/adc --version && /opt/adc/adc --help > /dev/null' + + adc-windows: if: startsWith(github.ref, 'refs/tags/') || contains(github.event.pull_request.labels.*.name, 'test/build') strategy: fail-fast: false @@ -220,28 +269,28 @@ jobs: run: | New-Item -ItemType Directory -Force -Path release | Out-Null $binDir = "rust/target/release" - Compress-Archive -Path "$binDir/adc.exe" -DestinationPath "release/adc_rust_${{ env.RELEASE_VERSION }}_windows_${{ matrix.arch }}.zip" - Compress-Archive -Path "$binDir/adc.pdb" -DestinationPath "release/adc_rust_${{ env.RELEASE_VERSION }}_windows_${{ matrix.arch }}_debuginfo.zip" + Compress-Archive -Path "$binDir/adc.exe" -DestinationPath "release/adc_${{ env.RELEASE_VERSION }}_windows_${{ matrix.arch }}.zip" + Compress-Archive -Path "$binDir/adc.pdb" -DestinationPath "release/adc_${{ env.RELEASE_VERSION }}_windows_${{ matrix.arch }}_debuginfo.zip" - name: Upload adc to GitHub release uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 if: startsWith(github.ref, 'refs/tags/') with: files: | - release/adc_rust_${{ env.RELEASE_VERSION }}_windows_${{ matrix.arch }}.zip - release/adc_rust_${{ env.RELEASE_VERSION }}_windows_${{ matrix.arch }}_debuginfo.zip + release/adc_${{ env.RELEASE_VERSION }}_windows_${{ matrix.arch }}.zip + release/adc_${{ env.RELEASE_VERSION }}_windows_${{ matrix.arch }}_debuginfo.zip - name: Upload adc artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: github.event_name == 'pull_request' with: - name: adc_rust_windows_${{ matrix.arch }} + name: adc_windows_${{ matrix.arch }} path: | - release/adc_rust_${{ env.RELEASE_VERSION }}_windows_${{ matrix.arch }}.zip - release/adc_rust_${{ env.RELEASE_VERSION }}_windows_${{ matrix.arch }}_debuginfo.zip + release/adc_${{ env.RELEASE_VERSION }}_windows_${{ matrix.arch }}.zip + release/adc_${{ env.RELEASE_VERSION }}_windows_${{ matrix.arch }}_debuginfo.zip if-no-files-found: error - adc-rust-macos: + adc-macos: if: startsWith(github.ref, 'refs/tags/') || contains(github.event.pull_request.labels.*.name, 'test/build') strategy: fail-fast: false @@ -288,23 +337,23 @@ jobs: run: | mkdir -p release BIN_DIR=rust/target/${{ matrix.target }}/release - tar -czf release/adc_rust_${{ env.RELEASE_VERSION }}_darwin_${{ matrix.arch }}.tar.gz -C "$BIN_DIR" adc - tar -czf release/adc_rust_${{ env.RELEASE_VERSION }}_darwin_${{ matrix.arch }}_debuginfo.tar.gz -C "$BIN_DIR" adc.dSYM + tar -czf release/adc_${{ env.RELEASE_VERSION }}_darwin_${{ matrix.arch }}.tar.gz -C "$BIN_DIR" adc + tar -czf release/adc_${{ env.RELEASE_VERSION }}_darwin_${{ matrix.arch }}_debuginfo.tar.gz -C "$BIN_DIR" adc.dSYM - name: Upload adc to GitHub release uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 if: startsWith(github.ref, 'refs/tags/') with: files: | - release/adc_rust_${{ env.RELEASE_VERSION }}_darwin_${{ matrix.arch }}.tar.gz - release/adc_rust_${{ env.RELEASE_VERSION }}_darwin_${{ matrix.arch }}_debuginfo.tar.gz + release/adc_${{ env.RELEASE_VERSION }}_darwin_${{ matrix.arch }}.tar.gz + release/adc_${{ env.RELEASE_VERSION }}_darwin_${{ matrix.arch }}_debuginfo.tar.gz - name: Upload adc artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: github.event_name == 'pull_request' with: - name: adc_rust_darwin_${{ matrix.arch }} + name: adc_darwin_${{ matrix.arch }} path: | - release/adc_rust_${{ env.RELEASE_VERSION }}_darwin_${{ matrix.arch }}.tar.gz - release/adc_rust_${{ env.RELEASE_VERSION }}_darwin_${{ matrix.arch }}_debuginfo.tar.gz + release/adc_${{ env.RELEASE_VERSION }}_darwin_${{ matrix.arch }}.tar.gz + release/adc_${{ env.RELEASE_VERSION }}_darwin_${{ matrix.arch }}_debuginfo.tar.gz if-no-files-found: error diff --git a/README.md b/README.md index 738636d1..cdccce89 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ Or, you can download the appropriate binary from the [releases page](https://git Pre-built binaries for `amd64` and `arm64` on Linux, Windows, and macOS are available now. +The binaries and the Docker images (`api7/adc:`, also published to `ghcr.io/api7/adc`) are the Rust implementation. The JavaScript implementation is published as `adc_js___` archives and `api7/adc:-js` images. The Linux binaries require glibc 2.28 or newer. See [Workflows](.github/workflows/README.md) and [tools](libs/tools/README.md) for the release details. + ## Configure ADC You can configure ADC through environment variables or command line flags. Run `adc help [command]` to see the available configuration options for a command. @@ -106,7 +108,19 @@ See the [`docs/`](docs/README.md) folder for the CLI command reference, configur ## Development -To build ADC from source, [install Nx](https://nx.dev/getting-started/installation) and run: +### Rust CLI + +The default `adc` binary is built with Cargo: + +```bash +cd rust +cargo build --release -p adc-cli +./target/release/adc -h +``` + +### JavaScript CLI + +To build the JavaScript implementation, [install Nx](https://nx.dev/getting-started/installation) and run: ```bash pnpm install diff --git a/libs/tools/README.md b/libs/tools/README.md new file mode 100644 index 00000000..f6097d15 --- /dev/null +++ b/libs/tools/README.md @@ -0,0 +1,23 @@ +# tools + +Distribution assets that are not part of the CLI itself. + +## Docker images + +Both images are published to `docker.io/api7/adc` and `ghcr.io/api7/adc` by `.github/workflows/docker.yaml`, for `linux/amd64` and `linux/arm64`. + +| Dockerfile | Implementation | Tags | +|---|---|---| +| `src/docker/Dockerfile` | Rust | ``, `dev` | +| `src/docker/Dockerfile.js` | JavaScript | `-js`, `dev-js` | + +- The Rust image builds `adc-cli` in `rust:1-trixie` and runs it on `gcr.io/distroless/cc-debian13:nonroot`. The runtime image provides glibc, libgcc and CA certificates, so the binary must stay glibc based. Moving to a static musl binary would require changing the base image as well. +- The Docker build context is the repository root. `.dockerignore` excludes `rust/target` and `node_modules`, keep them excluded or the context becomes very large. +- The entrypoint of each image is the CLI binary itself, so arguments are passed straight to `adc`. + +## Install script + +`src/s3/install` is what `https://run.api7.ai/adc/install` serves. `.github/workflows/update-s3-and-cloudfront.yaml` syncs it to S3 on every merge to `main`, independent of any release tag. + +- It downloads `adc___.tar.gz` from the GitHub release, which is the Rust build. The file names are defined in [.github/workflows/README.md](../../.github/workflows/README.md#release-artifacts), keep both in step. +- The architecture mapping only converts `x86_64` to `amd64`. On Linux arm64 `uname -m` reports `aarch64`, which is not mapped. diff --git a/libs/tools/src/docker/Dockerfile b/libs/tools/src/docker/Dockerfile index b51f2ed6..8990000a 100644 --- a/libs/tools/src/docker/Dockerfile +++ b/libs/tools/src/docker/Dockerfile @@ -1,20 +1,13 @@ -FROM node:lts-bookworm-slim AS builder - -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME/bin:$PATH" -ENV NX_DAEMON="false" -RUN corepack enable +FROM rust:1-trixie AS builder WORKDIR /build -COPY . . +COPY rust ./rust -RUN pnpm install nx -g \ - && pnpm install \ - && NODE_ENV=production nx build cli +RUN cd rust && cargo build --release --locked -p adc-cli -FROM gcr.io/distroless/nodejs24-debian13:nonroot +FROM gcr.io/distroless/cc-debian13:nonroot -COPY --from=builder /build/dist/apps/cli/main.cjs . +COPY --from=builder /build/rust/target/release/adc /adc -ENTRYPOINT [ "/nodejs/bin/node", "main.cjs" ] +ENTRYPOINT [ "/adc" ] diff --git a/libs/tools/src/docker/Dockerfile.js b/libs/tools/src/docker/Dockerfile.js new file mode 100644 index 00000000..b51f2ed6 --- /dev/null +++ b/libs/tools/src/docker/Dockerfile.js @@ -0,0 +1,20 @@ +FROM node:lts-bookworm-slim AS builder + +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME/bin:$PATH" +ENV NX_DAEMON="false" +RUN corepack enable + +WORKDIR /build + +COPY . . + +RUN pnpm install nx -g \ + && pnpm install \ + && NODE_ENV=production nx build cli + +FROM gcr.io/distroless/nodejs24-debian13:nonroot + +COPY --from=builder /build/dist/apps/cli/main.cjs . + +ENTRYPOINT [ "/nodejs/bin/node", "main.cjs" ]