diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ce0d77c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +--- +version: 2 +updates: +# Maintain dependencies for GitHub Actions + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + groups: + gha-dependencies: + patterns: + - '*' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..56c5f7f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,79 @@ +--- +name: Build images and upload them to ghcr.io + +env: + BUILDKIT_PROGRESS: plain + +on: + workflow_call: + inputs: + runsOn: + description: GitHub Actions Runner image + required: true + type: string + platforms: + description: Target platforms for the build (linux/amd64 and/or linux/arm64) + required: true + type: string + outputs: + images: + description: Images identified by digests + value: ${{ jobs.build.outputs.images }} + +jobs: + build: + name: ${{ inputs.platforms }} + runs-on: ${{ inputs.runsOn }} + timeout-minutes: 120 + + outputs: + images: ${{ steps.bake_metadata.outputs.images }} + + # Make sure we fail if any command in a piped command sequence fails + defaults: + run: + shell: bash -e -o pipefail {0} + + steps: + + - name: Checkout Repo ⚡️ + uses: actions/checkout@v4 + + - name: Set up QEMU + if: ${{ inputs.platforms != 'linux/amd64' }} + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry 🔑 + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and upload to ghcr.io 📤 + id: build-upload + uses: docker/bake-action@v4 + with: + push: true + # Using provenance to disable default attestation so it will build only desired images: + # https://github.com/orgs/community/discussions/45969 + provenance: false + set: | + *.platform=${{ inputs.platforms }} + *.output=type=registry,push-by-digest=true,name-canonical=true + *.cache-to=type=gha,scope=${{ github.workflow }},mode=max + *.cache-from=type=gha,scope=${{ github.workflow }} + files: | + docker-bake.hcl + build.json + .github/workflows/env.hcl + + - name: Set output variables + id: bake_metadata + run: | + .github/workflows/extract-image-names.sh | tee -a "${GITHUB_OUTPUT}" + env: + BAKE_METADATA: ${{ steps.build-upload.outputs.metadata }} diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml deleted file mode 100644 index 5d13f27..0000000 --- a/.github/workflows/build_and_test.yaml +++ /dev/null @@ -1,44 +0,0 @@ -# Test the Docker image on every pull request. -# -# The steps are: -# 1. Build docker image using cached data. -# 2. Start the docker container with local folder mounted to it. - -name: build-and-test-image-from-pull-request - -on: - [pull_request] - -jobs: - - build-and-test: - - runs-on: ubuntu-latest - timeout-minutes: 30 - - steps: - - - uses: actions/checkout@v2 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - - name: Cache Docker layers - uses: actions/cache@v2 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- - - - name: Build image locally - uses: docker/build-push-action@v2 - with: - load: true - push: false - tags: psp-prerequisites:latest - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache \ No newline at end of file diff --git a/.github/workflows/env.hcl b/.github/workflows/env.hcl new file mode 100644 index 0000000..9a8782a --- /dev/null +++ b/.github/workflows/env.hcl @@ -0,0 +1,2 @@ +# env.hcl +REGISTRY = "ghcr.io" diff --git a/.github/workflows/extract-image-names.sh b/.github/workflows/extract-image-names.sh new file mode 100755 index 0000000..a71b6c1 --- /dev/null +++ b/.github/workflows/extract-image-names.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +set -euo pipefail + +# Extract image names together with their sha256 digests +# from the docker/bake-action metadata output. +# These together uniquely identify newly built images. + +# The input to this script is a JSON string passed via BAKE_METADATA env variable +# Here's example input (trimmed to relevant bits): +# BAKE_METADATA: { +# "qc-base": { +# "containerimage.descriptor": { +# "mediaType": "application/vnd.docker.distribution.manifest.v2+json", +# "digest": "sha256:8e57a52b924b67567314b8ed3c968859cad99ea13521e60bbef40457e16f391d", +# "size": 6170, +# }, +# "containerimage.digest": "sha256:8e57a52b924b67567314b8ed3c968859cad99ea13521e60bbef40457e16f391d", +# "image.name": "ghcr.io/containers4hpc/qc-base" +# }, +# "qc-full-stack": { +# "image.name": "ghcr.io/containers4hpc/qc-full-stack" +# "containerimage.digest": "sha256:85ee91f61be1ea601591c785db038e5899d68d5fb89e07d66d9efbe8f352ee48", +# "...": "" +# } +# } +# +# Example output (real output is on one line): +# +# images={ +# "QC_BASE_IMAGE": "ghcr.io/containers4hpc/base@sha256:8e57a52b924b67567314b8ed3c968859cad99ea13521e60bbef40457e16f391d", +# "QC_FULL_STACK_IMAGE": "ghcr.io/containers4hpc/full-stack@sha256:85ee91f61be1ea601591c785db038e5899d68d5fb89e07d66d9efbe8f352ee48", +# } +# +# This json output is later turned to environment variables using fromJson() GHA builtin +# (e.g. BASE_IMAGE=ghcr.io/containers4hpc/base@sha256:8e57a52b...) +# and these are in turn read in the docker-compose..yml files for tests. + +if [[ -z ${BAKE_METADATA-} ]];then + echo "ERROR: Environment variable BAKE_METADATA is not set!" + exit 1 +fi + +images=$(echo "${BAKE_METADATA}" | jq -c '. as $base |[to_entries[] |{"key": (.key|ascii_upcase|sub("-"; "_"; "g") + "_IMAGE"), "value": [(.value."image.name"|split(",")[0]),.value."containerimage.digest"]|join("@")}] |from_entries') +echo "images=$images" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..6d41636 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,99 @@ +--- +name: Docker + +on: + pull_request: + paths-ignore: + - "**.md" + - ruff.toml + - bumpver.toml + - .pre-commit-config.yaml + push: + branches: + - main + tags: + - "v*" + workflow_dispatch: + +# https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + # only cancel in-progress jobs or runs for the current workflow - matches against branch & tags + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + FORCE_COLOR: 1 + +jobs: + + build-amd64: + uses: ./.github/workflows/build.yml + with: + runsOn: ubuntu-22.04 + platforms: linux/amd64 + + test-amd64: + needs: build-amd64 + strategy: + fail-fast: false + matrix: + target: ["qc-base", "qc-full-stack"] + uses: ./.github/workflows/test.yml + with: + runsOn: ubuntu-22.04 + images: ${{ needs.build-amd64.outputs.images }} + target: ${{ matrix.target }} + integration: false + + build: + needs: test-amd64 + uses: ./.github/workflows/build.yml + with: + runsOn: ubuntu-22.04 + platforms: linux/amd64,linux/arm64 + + # To save arm64 runner resources, we run the tests only on main + # and only for full-stack image (same for integration tests below). + test-arm64: + if: >- + github.repository == 'containers4hpc/quantum-container' + && (github.ref_type == 'tag' || github.ref_name == 'main') + needs: build + uses: ./.github/workflows/test.yml + with: + runsOn: buildjet-4vcpu-ubuntu-2204-arm + images: ${{ needs.build.outputs.images }} + target: qc-full-stack + integration: false + + test-integration: + name: Integration tests + needs: build + strategy: + fail-fast: false + # Trick to exclude arm64 tests from PRs + # https://github.com/orgs/community/discussions/26253 + matrix: + runner: [ubuntu-22.04, buildjet-4vcpu-ubuntu-2204-arm] + isPR: + - ${{ github.event_name == 'pull_request' }} + exclude: + - isPR: true + runner: buildjet-4vcpu-ubuntu-2204-arm + + uses: ./.github/workflows/test.yml + with: + runsOn: ${{ matrix.runner }} + images: ${{ needs.build.outputs.images }} + target: qc-full-stack + integration: true + + publish-ghcr: + needs: [build, test-amd64] + uses: ./.github/workflows/publish.yml + with: + runsOn: ubuntu-22.04 + images: ${{ needs.build.outputs.images }} + registry: ghcr.io + secrets: inherit + diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..b021012 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,90 @@ +--- +name: Publish images to Docker container registries + +env: + # https://github.com/docker/metadata-action?tab=readme-ov-file#environment-variables + DOCKER_METADATA_PR_HEAD_SHA: true + +on: + workflow_call: + inputs: + runsOn: + description: GitHub Actions Runner image + required: true + type: string + images: + description: Images built in build step + required: true + type: string + registry: + description: Docker container registry + required: true + type: string + +jobs: + + release: + runs-on: ${{ inputs.runsOn }} + timeout-minutes: 30 + strategy: + fail-fast: true + matrix: + target: ["qc-base", "qc-full-stack"] + + steps: + - uses: actions/checkout@v4 + + - name: Login to GitHub Container Registry 🔑 + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to DockerHub 🔑 + uses: docker/login-action@v3 + if: inputs.registry == 'docker.io' + with: + registry: docker.io + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Read build variables + id: build_vars + run: | + vars=$(cat build.json | jq -c '[.variable | to_entries[] | {"key": .key, "value": .value.default}] | from_entries') + echo "vars=$vars" | tee -a "${GITHUB_OUTPUT}" + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + env: ${{ fromJSON(steps.build_vars.outputs.vars) }} + with: + # e.g. ghcr.io/containers4hpc/qc-full-stack + # type=raw,value=python-${{ env.PYTHON_VERSION }},enable=${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }} + images: ${{ inputs.registry }}/${{ github.repository_owner }}/${{ matrix.target }} + tags: | + type=ref,event=pr + type=edge,enable={{is_default_branch}} + type=match,pattern=v(\d{4}\.\d{4}(-.+)?),group=1 + + - name: Determine source image + id: images + run: | + src=$(echo '${{ inputs.images }}'| jq -cr '.[("${{ matrix.target }}"|ascii_upcase|sub("-"; "_"; "g")) + "_IMAGE"]') + echo "src=$src" | tee -a "${GITHUB_OUTPUT}" + + - name: Push image + uses: akhilerm/tag-push-action@v2.2.0 + with: + src: ${{ steps.images.outputs.src }} + dst: ${{ steps.meta.outputs.tags }} + + - name: Docker Hub Description + if: inputs.registry == 'docker.io' + uses: peter-evans/dockerhub-description@v4 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + repository: containers4hpc/${{ matrix.target }} + short-description: ${{ github.event.repository.description }} diff --git a/.github/workflows/push_to_dockerhub.yaml b/.github/workflows/push_to_dockerhub.yaml deleted file mode 100644 index 25e88d7..0000000 --- a/.github/workflows/push_to_dockerhub.yaml +++ /dev/null @@ -1,50 +0,0 @@ -# Build the new Docker image on every commit to main branch and on every new tag. -# No caching is involved for the image build. The new image is then pushed to the Docker Hub. - -name: build-and-push-to-dockerhub - -on: - push: - branches: - - main - tags: - - 'v*' - -jobs: - - build-and-push: - - runs-on: ubuntu-latest - timeout-minutes: 30 - - steps: - - - uses: actions/checkout@v2 - - - name: Docker meta - id: meta - uses: docker/metadata-action@v3 - with: - images: ${{ github.repository }} - tags: | - type=ref,event=branch - type=semver,pattern={{version}} - - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - - name: Login to DockerHub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Build and push - id: docker_build - uses: docker/build-push-action@v2 - with: - push: true - tags: ${{ steps.meta.outputs.tags }} \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..b2b424a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,50 @@ +--- +name: Test newly built images + +on: + workflow_call: + inputs: + runsOn: + description: GitHub Actions Runner image + required: true + type: string + images: + description: Images built in build step + required: true + type: string + target: + description: Target image for testing + required: false + type: string + integration: + description: Run integration tests + required: false + type: boolean + +jobs: + + test: + name: ${{ inputs.integration && inputs.runsOn || inputs.target }} + runs-on: ${{ inputs.runsOn }} + timeout-minutes: 20 + + steps: + + - name: Checkout Repo ⚡️ + uses: actions/checkout@v4 + + - name: Login to GitHub Container Registry 🔑 + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Install dependencies 📦 + run: | + pip install -r requirements.txt + pip freeze + + # - name: Run tests + # run: pytest -m "${{ inputs.integration && 'integration' || 'not integration' }}" --target ${{inputs.target}} + # env: ${{ fromJSON(inputs.images) }} diff --git a/README.md b/README.md index 4692a78..24a3314 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,11 @@ -# base image of ABI compatible MPICH 3.1.4 +# The build machine with all toolchains + +We are building two images the `ghcr.io/cnts4sci/openmpi` and `ghcr.io/cnts4sci/mpich`. +The images is used as the base image for other software build. +In the image, we provide as much as possible the regular math libraries as we can: + +- Lapack +- FFTW +- Libxc +- ..?? -The image as base image for container that need to run on ABI compatible HPC machines. -The MPICH 3.1.4 is installed. \ No newline at end of file diff --git a/build.json b/build.json new file mode 100644 index 0000000..5b5634d --- /dev/null +++ b/build.json @@ -0,0 +1,7 @@ +{ + "variable": { + "OPENMPI_VERSION": { + "default": "4.1.6" + } + } +} diff --git a/docker-bake.hcl b/docker-bake.hcl new file mode 100644 index 0000000..38b8122 --- /dev/null +++ b/docker-bake.hcl @@ -0,0 +1,54 @@ +# docker-bake.hcl +variable "VERSION" { +} + +variable "ORGANIZATION" { + default = "cnts4sci" +} + +variable "REGISTRY" { + default = "ghcr.io" +} + +variable "PLATFORMS" { + default = ["linux/amd64"] +} + +variable "BASE_IMAGE" { + default = "ubuntu:20.04" +} + +variable "RUNTIME_BASE_IMAGE" { + default = "ubuntu:20.04" +} + +variable "TARGETS" { + default = ["openmpi"] +} + +# TAGS for softwares +variable "OPENMPI_VERSION" { +} + +function "tags" { + params = [image] + result = [ + "${REGISTRY}/${ORGANIZATION}/${image}" + ] +} + +group "default" { + targets = "${TARGETS}" +} + +target "openmpi" { + tags = tags("openmpi") + context = "openmpi" + platforms = "${PLATFORMS}" + args = { + BASE_IMAGE = "${BASE_IMAGE}" + RUNTIME_BASE_IMAGE = "${RUNTIME_BASE_IMAGE}" + OPENMPI_VERSION = "${OPENMPI_VERSION}" + } +} + diff --git a/Dockerfile b/mpich/Dockerfile similarity index 100% rename from Dockerfile rename to mpich/Dockerfile diff --git a/openmpi/Dockerfile b/openmpi/Dockerfile new file mode 100644 index 0000000..1152368 --- /dev/null +++ b/openmpi/Dockerfile @@ -0,0 +1,87 @@ +# syntax=docker/dockerfile:1 + +ARG BASE_IMAGE +ARG RUNTIME_BASE_IMAGE + +FROM ${BASE_IMAGE} AS toolchain-builder + +WORKDIR /openmpi-build + +# Build toolchains +RUN apt-get update && apt-get install -y \ + build-essential \ + wget \ + curl \ + gcc \ + g++ \ + make \ + libtool \ + autoconf \ + automake \ + gfortran \ + bzip2 \ + tar \ + git && \ + rm -rf /var/lib/apt/lists/* + +RUN wget -c -O openmpi.tar.gz https://download.open-mpi.org/release/open-mpi/v4.1/openmpi-4.1.6.tar.gz && \ + mkdir -p openmpi && \ + tar xf openmpi.tar.gz -C openmpi --strip-components=1 && \ + cd openmpi && \ + ./configure --prefix=/opt/openmpi --enable-static --disable-shared && \ + make -j$(nproc) && \ + make install + +# Compile Lapack +WORKDIR /lapack-build +ARG LAPACK_VERSION="3.10.1" + +RUN wget -c -O lapack.tar.gz https://github.com/Reference-LAPACK/lapack/archive/refs/tags/v${LAPACK_VERSION}.tar.gz && \ + mkdir -p lapack && \ + tar xf lapack.tar.gz -C lapack --strip-components=1 && \ + cd lapack && \ + cp INSTALL/make.inc.gfortran make.inc && \ + make lapacklib blaslib && \ + mkdir -p /usr/local/lapack/lib && \ + cp *.a /usr/local/lapack/lib + +# As build-machine image, it is not actually a runtime +# but I do the runtime multi-stage build to minimize the size +# and for testing the integrity of the openmpi/lapack... static build and move +FROM ${RUNTIME_BASE_IMAGE} + +# Copy build toolchains from the builder stage +COPY --from=toolchain-builder /opt/openmpi /opt/openmpi +COPY --from=toolchain-builder /usr/local/lapack /usr/local/lapack + +RUN apt-get update && apt-get install -y \ + build-essential \ + wget \ + curl \ + gcc \ + g++ \ + make \ + libtool \ + autoconf \ + automake \ + gfortran \ + bzip2 \ + tar \ + git && \ + rm -rf /var/lib/apt/lists/* + +# Set up environment variables for OpenMPI +ENV PATH="/opt/openmpi/bin:$PATH" +ENV LD_LIBRARY_PATH="/opt/openmpi/lib:$LD_LIBRARY_PATH" + +# Compile QE (for test only) +ARG QE_VERSION="7.0" + +RUN wget -c -O qe.tar.gz https://gitlab.com/QEF/q-e/-/archive/qe-${QE_VERSION}/q-e-qe-${QE_VERSION}.tar.gz && \ + mkdir -p qe && \ + tar xf qe.tar.gz -C qe --strip-components=1 && \ + cd qe && \ + LAPACK_LIBS=/usr/local/lapack/lib/liblapack.a BLAS_LIBS=/usr/local/lapack/lib/librefblas.a ./configure -enable-static && \ + make -j8 pw && \ + make install +