Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions .github/workflows/build-devcontainer.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
on:
workflow_call:
inputs:
push:
type: boolean
default: true
description: "Whether to push the image."
repo:
type: string
required: true
description: "Devcontainer image repository."
tag:
type: string
required: true
description: "Devcontainer image tag."
workspace-dir:
type: string
default: '.'
description: "Devcontainer workspace directory."
devcontainer-json:
type: string
required: true
description: "Path to the devcontainer.json file."
timeout-minutes:
type: number
default: 360
description: "Maximum time (in minutes) allowed for a run of this workflow."
retries:
type: string
default: '3'
description: "Number of times to retry the image build"
runs-on:
type: string
default: "ubuntu-latest"
description: "GHA runner label."
outputs:
version:
value: ${{ jobs.build.outputs.version }}


permissions:
actions: none
checks: none
contents: none
deployments: none
discussions: none
issues: none
packages: write
pages: none
pull-requests: none
repository-projects: none
security-events: none
statuses: none

jobs:
build:
timeout-minutes: ${{ inputs.timeout-minutes }}
strategy:
fail-fast: false
matrix:
arch: [amd64, arm64]
runs-on: ${{ fromJSON(github.repository_owner != 'rapidsai' && '"ubuntu-latest"' || format('"${{ inputs.runs-on }}"', matrix.arch)) }}
name: "${{ inputs.tag }} (${{ matrix.arch }})"
outputs:
hash_amd64: ${{ steps.build.outputs.hash_amd64 }}
hash_arm64: ${{ steps.build.outputs.hash_arm64 }}
name: ${{ steps.build.outputs.name }}
repo: ${{ steps.build.outputs.repo }}
tag: ${{ steps.build.outputs.tag }}
version: ${{ steps.setup.outputs.version }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false

- id: setup
name: Setup versions
shell: bash --noprofile --norc -x -eo pipefail {0}
run: |
cat <<EOF | tee -a "$GITHUB_OUTPUT"
version=$(git describe --abbrev=0 --tags | sed 's/[a-zA-Z]//g' | cut -d '.' -f -2)
EOF

- name: Login to ghcr.io
if: inputs.push
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: "ghcr.io"
username: "${{ github.actor }}"
password: "${{ github.token }}"

- id: build
name: Build devcontainer (${{ matrix.arch }})
uses: rapidsai/shared-actions/build-devcontainer@main
with:
arch: "${{ matrix.arch }}"
repo: "ghcr.io/${{ inputs.repo }}"
push: "${{ inputs.push }}"
retries: "${{ inputs.retries }}"
tag: "${{ inputs.tag }}"
version: "${{ steps.setup.outputs.version }}"
workspace-dir: "${{ inputs.workspace-dir }}"
devcontainer-json: "${{ inputs.devcontainer-json }}"

push:
if: inputs.push
name: Push to ghcr.io
needs: [build]
runs-on: ubuntu-latest
steps:
- name: Login to ghcr.io
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: "ghcr.io"
username: "${{ github.actor }}"
password: "${{ github.token }}"

- name: Push manifest to ghcr.io
shell: bash --noprofile --norc -x -eo pipefail {0}
env:
hash_amd64: "${{ needs.build.outputs.hash_amd64 }}"
hash_arm64: "${{ needs.build.outputs.hash_arm64 }}"
name: "${{ needs.build.outputs.name }}"
name_latest: "${{ needs.build.outputs.name_latest }}"
ref_name: "${{ github.ref_name }}"
run: |
# Create the multiarch manifest
docker buildx imagetools create --tag "${name}" "${hash_amd64}" "${hash_arm64}";
if [[ "${ref_name}" == main ]]; then
docker buildx imagetools create --tag "${name_latest}" "${hash_amd64}" "${hash_arm64}";
fi
93 changes: 93 additions & 0 deletions .github/workflows/build-devcontainers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Build devcontainers

on:
workflow_call:
inputs:
cuda:
description: |
Stringified JSON array of CUDA versions to run this workflow for.
This is used to select .devcontainer/ directories local to wherever this workflow is invoked from.
For example, if a repository has directories '.devcontainer/cuda12.9-pip/' and '.devcontainer/cuda13.1-pip/',
'["12.9", "13.2"]' could be passed here to build both of those devcontainers in CI.
type: string
required: true
python_package_manager:
description: |
Stringified JSON array of Python package managers to run devcontainer builds for.
One of: '["conda"]', '["pip"]', '["conda", "pip"]'.
type: string
default: '["conda", "pip"]'
retries:
type: string
default: '3'
description: "Number of times to retry the image build"
push:
type: boolean
default: true

jobs:
build:
uses: ./.github/workflows/build-devcontainer.yaml
permissions:
packages: write
strategy:
fail-fast: false
matrix:
cuda: ${{ fromJSON(inputs.cuda) }}
python_package_manager: ${{ fromJSON(inputs.python_package_manager) }}
with:
runs-on: 'linux-{0}-cpu4'
push: "${{ inputs.push }}"
retries: "${{ inputs.retries }}"
repo: "${{ github.repository }}/devcontainer"
tag: "cuda${{ matrix.cuda }}-${{ matrix.python_package_manager }}"
devcontainer-json: ".devcontainer/cuda${{ matrix.cuda }}-${{ matrix.python_package_manager }}/devcontainer.json"

cleanup:
if: inputs.push
needs: [build]
name: Clean up untagged images
runs-on: ubuntu-latest
steps:
- id: vars
name: Get image name, tags, and digests
shell: bash --noprofile --norc -eo pipefail {0}
env:
CUDA: "${{ inputs.cuda }}"
NAME_PREFIX: "ghcr.io/${{ github.repository_owner }}"
NAME: "ghcr.io/${{ github.repository }}/devcontainer"
PYTHON_PACKAGE_MANAGER: "${{ inputs.python_package_manager }}"
VERSIONS: '["latest", "${{ needs.build.outputs.version }}"]'
run: |
declare -a TAGS="($(jq -cnr \
--argjson vers "${VERSIONS}" \
--argjson cuda "${CUDA}" \
--argjson pkgr "${PYTHON_PACKAGE_MANAGER}" \
'[[$vers, $cuda, $pkgr] | combinations | [.[0], "cuda" + .[1], .[2]] | join("-")] | join(" ")'))"

declare -a DIGESTS=()
for TAG in "${TAGS[@]}"; do
mapfile -O "${#DIGESTS[@]}" -t DIGESTS < <(
docker buildx imagetools inspect --raw "${NAME}:${TAG}" | jq -r '.manifests.[] | .digest'
)
done

NAME="${NAME#${NAME_PREFIX}/}"

# Set values to control ghcr.io cleanup below
cat <<EOF >> "$GITHUB_OUTPUT"
digests=${DIGESTS[*]}
name=${NAME}
tags=${TAGS[*]}
EOF

- name: Clean up untagged images
uses: snok/container-retention-policy@3b0972b2276b171b212f8c4efbca59ebba26eceb # v3.0.1
with:
cut-off: 6hr
tag-selection: untagged
token: "${{ github.token }}"
image-tags: "${{ steps.vars.outputs.tags }}"
image-names: "${{ steps.vars.outputs.name }}"
skip-shas: "${{ steps.vars.outputs.digests }}"
account: "${{ github.repository_owner == 'rapidsai' && 'rapidsai' || 'user' }}"