diff --git a/build-devcontainer/action.yml b/build-devcontainer/action.yml new file mode 100644 index 00000000..c6bb155f --- /dev/null +++ b/build-devcontainer/action.yml @@ -0,0 +1,137 @@ +name: "build-devcontainer" + +description: "Build devcontainer image" + +inputs: + tag: {type: string, required: true, description: "Image tag"} + arch: {type: string, required: true, description: "Image arch"} + repo: {type: string, required: true, description: "Image repo"} + push: {type: string, required: true, description: "Whether to push the image"} + version: {type: string, required: true, description: "Repository version number"} + retries: {type: string, default: '3', description: "Number of times to retry the container build"} + workspace-dir: {type: string, default: '.', description: "Devcontainer workspace directory"} + devcontainer-json: {type: string, required: true, description: "Path to the devcontainer.json file"} + +outputs: + hash_amd64: + value: "${{ steps.build.outputs.hash_amd64 }}" + hash_arm64: + value: "${{ steps.build.outputs.hash_arm64 }}" + repo: + value: "${{ steps.build.outputs.repo }}" + tag: + value: "${{ steps.build.outputs.tag }}" + name: + value: "${{ steps.build.outputs.name }}" + name_latest: + value: "${{ steps.build.outputs.name_latest }}" + +runs: + using: composite + steps: + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: '24' + + - if: runner.environment != 'self-hosted' + name: Set up QEMU + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4 + + - name: Create docker context + shell: bash + run: docker context create builder + + - if: runner.environment != 'self-hosted' + name: Setup docker buildx on github-hosted runners + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 + with: + endpoint: builder + + - if: runner.environment == 'self-hosted' + name: Setup docker buildx on self-hosted runners + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 + with: + buildkitd-config: /etc/buildkit/buildkitd.toml + buildkitd-flags: --config /etc/buildkit/buildkitd.toml + endpoint: builder + + - if: runner.environment == 'self-hosted' + name: Setup proxy cache + uses: nv-gha-runners/setup-proxy-cache@c5e8f6fd27b493abfd6af230e6a5d4bf43d1d766 + continue-on-error: true + with: + enable-apt: true + + - name: Install devcontainers CLI + shell: bash + env: + push: "${{ inputs.push }}" + run: | + sudo apt update; + sudo apt install -y --no-install-recommends build-essential; + npm install -g @devcontainers/cli@v0.85.0; + if "${push}"; then + # HACK: remove the `-t` arg from the `docker buildx build` command generated by `devcontainer build` + sed -i 's/,t.map(G=>l.push("-t",G))//g' "$(npm list -g | head -n1)"/node_modules/@devcontainers/cli/dist/spec-node/devContainersSpecCLI.js; + fi + + - id: build + name: Build ${{ inputs.repo }}:${{ inputs.tag }} (${{ inputs.arch }}) + shell: bash + env: + NODE_NO_WARNINGS: 1 + arch: "${{ inputs.arch }}" + push: "${{ inputs.push }}" + repo: "${{ inputs.repo }}" + retries: "${{ inputs.retries }}" + tag: "${{ inputs.tag }}" + version: "${{ inputs.version }}" + workspace_dir: "${{ inputs.workspace-dir }}" + devcontainer_json: "${{ inputs.devcontainer-json }}" + run: | + set -euo pipefail; + + declare i=0; + declare -a outputs=(); + + # Ensure tag and repo are lowercase + tag="${tag,,}" + repo="${repo,,}" + name="${repo}:${version}-${tag}" + name_latest="${repo}:latest-${tag}" + + echo "tag=${tag}" >> "$GITHUB_OUTPUT"; + echo "repo=${repo}" >> "$GITHUB_OUTPUT"; + echo "name=${name}" >> "$GITHUB_OUTPUT"; + echo "name_latest=${name_latest}" >> "$GITHUB_OUTPUT"; + + if "${push}"; then + outputs+=(--output "type=image,compression=zstd,force-compression=true,oci-mediatypes=true,push=true,push-by-digest=true,name=${repo}"); + fi + + until devcontainer build \ + --cache-from "${name}" \ + --image-name "${name}" \ + --platform "linux/${arch}" \ + --config "$devcontainer_json" \ + --workspace-folder "$workspace_dir" \ + "${outputs[@]}" 2>&1 \ + | tee "${{ runner.temp }}/${arch}.log" 1>&2 + do + if test "${i}" -lt "${retries}"; then + j=$((i++)); + t=$((i * i * 5)); + echo "Attempt $j failed! Trying again in $t seconds..."; + sleep $t; + else + echo "Failed to build ${name}"; + exit 1; + fi + done + + if test "${push}" = true; then + echo "hash_${arch}=$(grep 'exporting manifest sha256:' "${{ runner.temp }}/${arch}.log" | tail -n1 | grep -oP 'sha256:\w+')" >> "$GITHUB_OUTPUT"; + else + echo "hash_${arch}=" >> "$GITHUB_OUTPUT"; + fi