diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2f88e15c84..932d74782b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -9,6 +9,11 @@ on: type: string required: true default: main + rust_image: + description: "The rust base image tag (e.g. 1.90.0-slim-bookworm). Defaults to latest." + type: string + required: false + default: latest release: types: [published] @@ -19,6 +24,7 @@ defaults: jobs: build: strategy: + fail-fast: false matrix: include: - runs-on: ubuntu-latest @@ -29,42 +35,82 @@ jobs: permissions: contents: read steps: + # The workflow's own ref provides Dockerfile + entrypoint.sh + docker/. + # The binary's source is cloned by the Dockerfile itself based on CLI_REF. - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + # NOTE: switch fallback ref to `main` once this branch lands. + - name: Fall back to docker-custom-image packaging files if missing + run: | + if [[ ! -f Dockerfile ]]; then + echo "::notice::Dockerfile not present at ${GITHUB_REF}; falling back to docker-custom-image" + git fetch --depth=1 origin docker-custom-image + git checkout FETCH_HEAD -- Dockerfile .dockerignore entrypoint.sh docker/ + fi + + - name: Resolve build args + run: | + rust_image="${{ github.event_name == 'workflow_dispatch' && inputs.rust_image || 'latest' }}" + cli_ref="${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref_name }}" + echo "RUST_IMAGE=${rust_image}" >> "$GITHUB_ENV" + echo "CLI_REF=${cli_ref}" >> "$GITHUB_ENV" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 + + - name: Log in to Docker Hub + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 with: - ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Install build dependencies - run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libudev-dev libdbus-1-dev + - id: build + name: Build and push by digest + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 + with: + context: . + platforms: linux/${{ matrix.arch }} + outputs: type=image,name=stellar/stellar-cli,push-by-digest=true,name-canonical=true,push=true + build-args: | + RUST_IMAGE=${{ env.RUST_IMAGE }} + CLI_REF=${{ env.CLI_REF }} - - name: Build binary - run: cargo build --package stellar-cli --release + - name: Export digest + run: | + mkdir -p "${RUNNER_TEMP}/digests" + digest="${{ steps.build.outputs.digest }}" + touch "${RUNNER_TEMP}/digests/${digest#sha256:}" - - name: Upload binary + - name: Upload digest uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: stellar-${{ matrix.arch }} - path: target/release/stellar + name: digest-${{ matrix.arch }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error retention-days: 1 - docker: + manifest: needs: build runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }} - fetch-depth: 0 - - name: Download binaries + # NOTE: switch fallback ref to `main` once this branch lands. + - name: Ensure docker/README.md is available + run: | + if [[ ! -f docker/README.md ]]; then + git fetch --depth=1 origin docker-custom-image + git checkout FETCH_HEAD -- docker/ + fi + + - name: Download digests uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - pattern: stellar-* - merge-multiple: false - - - name: Set up QEMU - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4 + path: ${{ runner.temp }}/digests + pattern: digest-* + merge-multiple: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 @@ -75,31 +121,68 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - # Compute Docker tags from the ref. - # - Version tag (e.g. v1.2.3): push versioned + latest tags. - # - Any other ref: push a tag for the resolved commit SHA. + - name: Resolve rust image + run: | + rust_image="${{ github.event_name == 'workflow_dispatch' && inputs.rust_image || 'latest' }}" + echo "RUST_IMAGE=${rust_image}" >> "$GITHUB_ENV" + + - name: Resolve build commit SHA + run: | + ref="${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref_name }}" + if [[ "$ref" =~ ^[0-9a-f]{40}$ ]]; then + sha="$ref" + else + sha="$(git ls-remote https://github.com/${{ github.repository }}.git "$ref" | awk 'END{print $1}')" + fi + if [[ -z "$sha" ]]; then + echo "::error::Could not resolve SHA for ref: $ref" + exit 1 + fi + echo "BUILD_SHA=${sha}" >> "$GITHUB_ENV" + + # Compute Docker tags from the ref and rust image. + # - Version tag (e.g. v1.2.3) + default rust: push versioned + latest tags. + # - Version tag + custom rust: push versioned + -rust- suffix only (do not update :latest). + # - Any other ref: push a tag for the resolved commit SHA, with the rust suffix when custom. - name: Compute tags run: | ref="${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref_name }}" + suffix="" + if [[ "${RUST_IMAGE}" != "latest" ]]; then + sanitized="${RUST_IMAGE//[^A-Za-z0-9._-]/-}" + suffix="-rust-${sanitized}" + fi + if [[ "$ref" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then version="${ref#v}" - echo "DOCKER_TAGS=stellar/stellar-cli:${version},stellar/stellar-cli:latest" >> $GITHUB_ENV + if [[ -n "$suffix" ]]; then + echo "DOCKER_TAGS=stellar/stellar-cli:${version}${suffix}" >> $GITHUB_ENV + else + echo "DOCKER_TAGS=stellar/stellar-cli:${version},stellar/stellar-cli:latest" >> $GITHUB_ENV + fi elif [[ "${{ github.event_name }}" == "release" ]]; then echo "::error::Release tag '${ref}' is not a valid version tag (expected vX.Y.Z)." exit 1 else - commit="$(git rev-parse HEAD)" - echo "DOCKER_TAGS=stellar/stellar-cli:${commit}" >> $GITHUB_ENV + echo "DOCKER_TAGS=stellar/stellar-cli:${BUILD_SHA}${suffix}" >> $GITHUB_ENV fi - - name: Build and push - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 - with: - context: . - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ env.DOCKER_TAGS }} + - name: Create and push manifest list + working-directory: ${{ runner.temp }}/digests + run: | + IFS=',' read -ra TAGS <<< "${DOCKER_TAGS}" + tag_args=() + for tag in "${TAGS[@]}"; do + tag_args+=(-t "$tag") + done + + digest_refs=() + for f in *; do + digest_refs+=("stellar/stellar-cli@sha256:${f}") + done + + docker buildx imagetools create "${tag_args[@]}" "${digest_refs[@]}" - name: Update Docker Hub description run: | diff --git a/Dockerfile b/Dockerfile index f4d34ce8c1..24a3d70b5f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,31 @@ -FROM rust:latest +ARG RUST_IMAGE=latest + +FROM rust:${RUST_IMAGE} AS builder + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + build-essential cmake pkg-config git \ + libssl-dev libudev-dev libdbus-1-dev && \ + rm -rf /var/lib/apt/lists/* + +ARG CLI_REPO=https://github.com/stellar/stellar-cli +ARG CLI_REF=main + +WORKDIR /src +RUN git clone "${CLI_REPO}" . && \ + git checkout "${CLI_REF}" + +RUN cargo build --package stellar-cli --release + +FROM rust:${RUST_IMAGE} RUN rustup target add wasm32v1-none RUN apt-get update && \ - apt-get install -y --no-install-recommends libudev1 libssl3 && \ + apt-get install -y --no-install-recommends libudev1 libssl3 libdbus-1-3 && \ rm -rf /var/lib/apt/lists/* -ARG TARGETARCH -COPY stellar-${TARGETARCH}/stellar /usr/local/bin/stellar +COPY --from=builder /src/target/release/stellar /usr/local/bin/stellar RUN chmod +x /usr/local/bin/stellar ENV STELLAR_CONFIG_HOME=/config