From 769e6f6d68a7edcc285fe3b739c1167129d040fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:17:23 +0000 Subject: [PATCH 1/4] Initial plan From 9184f92cbfae6823fb6e450c52af8f046273f896 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:32:43 +0000 Subject: [PATCH 2/4] feat(docker): add setup-ccbr-actions composite action and docker build workflow - Add Dockerfile to build a Python 3.11-slim image with ccbr_actions pre-installed - Add .github/workflows/build-docker-ccbr-actions.yml to automatically build and push nciccbr/ccbr_actions:{tag} to DockerHub on release - Add setup-ccbr-actions composite action that checks if nciccbr/ccbr_actions:{version} exists on DockerHub and uses it (docker pull) if available, otherwise installs ccbr_actions via pip - Update build-docker to use setup-ccbr-actions instead of directly installing via pip, with full support for running Python steps via docker or directly Closes #186" --- .../workflows/build-docker-ccbr-actions.yml | 60 ++++++++++ CHANGELOG.md | 4 + Dockerfile | 6 + build-docker/action.yml | 103 +++++++++++++++--- setup-ccbr-actions/action.yml | 72 ++++++++++++ 5 files changed, 227 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/build-docker-ccbr-actions.yml create mode 100644 Dockerfile create mode 100644 setup-ccbr-actions/action.yml diff --git a/.github/workflows/build-docker-ccbr-actions.yml b/.github/workflows/build-docker-ccbr-actions.yml new file mode 100644 index 0000000..56ea223 --- /dev/null +++ b/.github/workflows/build-docker-ccbr-actions.yml @@ -0,0 +1,60 @@ +name: build-docker-ccbr-actions + +on: + release: + types: + - published + workflow_dispatch: + inputs: + push: + description: "Push image to DockerHub" + type: boolean + default: false + ccbr-actions-version: + description: "Version/tag of ccbr_actions to install in the image (defaults to the release tag or 'main')" + required: false + default: "main" + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Determine version and push flag + id: setup + shell: bash + run: | + if [ "${{ github.event_name }}" = "release" ]; then + version="${{ github.ref_name }}" + push="true" + else + version="${{ inputs.ccbr-actions-version }}" + push="${{ inputs.push }}" + fi + echo "version=${version}" >> "$GITHUB_OUTPUT" + echo "push=${push}" >> "$GITHUB_OUTPUT" + + - name: Login to DockerHub + if: ${{ steps.setup.outputs.push == 'true' }} + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME_VK }} + password: ${{ secrets.DOCKERHUBRW_TOKEN_VK }} + + - name: Build and push Docker image + uses: docker/build-push-action@v7 + with: + context: . + file: Dockerfile + push: ${{ steps.setup.outputs.push == 'true' }} + tags: nciccbr/ccbr_actions:${{ steps.setup.outputs.version }} + build-args: | + CCBR_ACTIONS_VERSION=${{ steps.setup.outputs.version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 53c5750..643ab46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## actions development version +- Add `setup-ccbr-actions` composite action that checks whether a Docker image exists at `nciccbr/ccbr_actions:{version}` on DockerHub and uses it if available, otherwise installs `ccbr_actions` via pip. (#186, @copilot) +- Add `Dockerfile` and `.github/workflows/build-docker-ccbr-actions.yml` to build and push `nciccbr/ccbr_actions` Docker images on release. (#186, @copilot) +- Update `build-docker` to use `setup-ccbr-actions` for installing `ccbr_actions`. (#186, @copilot) + ## actions 0.7.1 - Fix `draft-release` to preserve custom keys in `CITATION.cff` in R packages. (#180, @kelly-sovacool) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..37c1f56 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.11-slim + +ARG CCBR_ACTIONS_VERSION=main + +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir "git+https://github.com/CCBR/actions@${CCBR_ACTIONS_VERSION}" diff --git a/build-docker/action.yml b/build-docker/action.yml index 0cf94ea..ef780fc 100644 --- a/build-docker/action.yml +++ b/build-docker/action.yml @@ -75,13 +75,12 @@ outputs: runs: using: "composite" steps: - - uses: actions/setup-python@v6 + - name: Set up ccbr_actions + id: setup-ccbr-actions + uses: ./setup-ccbr-actions with: - python-version: "${{ inputs.python-version }}" - - - name: Install CCBR/actions - shell: bash - run: pip install --upgrade pip git+https://github.com/CCBR/actions.git@${{ inputs.ccbr-actions-version }} + ccbr-actions-version: ${{ inputs.ccbr-actions-version }} + python-version: ${{ inputs.python-version }} - name: Resolve push mode id: resolve_push @@ -110,15 +109,35 @@ runs: password: ${{ inputs.dockerhub-token }} - name: Prepare build-time variables - shell: python + shell: bash id: prepare_vars + env: + CCBR_INSTALL_METHOD: ${{ steps.setup-ccbr-actions.outputs.install-method }} + CCBR_DOCKER_IMAGE: ${{ steps.setup-ccbr-actions.outputs.docker-image }} run: | + script=$(mktemp /tmp/ccbr_XXXXXX.py) + cat > "$script" << 'PYEOF' from ccbr_actions.docker import prepare_docker_build_variables prepare_docker_build_variables( dockerfile="${{ inputs.dockerfile }}", suffix="${{ inputs.suffix }}", dockerhub_account="${{ inputs.dockerhub-namespace }}" ) + PYEOF + if [ "${CCBR_INSTALL_METHOD}" = "docker" ]; then + docker run --rm \ + -v "${GITHUB_ENV}:${GITHUB_ENV}" \ + -e GITHUB_ENV \ + -v "${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}" \ + -e GITHUB_WORKSPACE \ + -w "${GITHUB_WORKSPACE}" \ + -v "${script}:/tmp/ccbr_script.py:ro" \ + "${CCBR_DOCKER_IMAGE}" \ + python /tmp/ccbr_script.py + else + python "$script" + fi + rm -f "$script" - name: Check variables and create README shell: bash @@ -142,23 +161,46 @@ runs: - name: Check whether image tag is stale id: check_tag_staleness - shell: python + shell: bash + env: + CCBR_INSTALL_METHOD: ${{ steps.setup-ccbr-actions.outputs.install-method }} + CCBR_DOCKER_IMAGE: ${{ steps.setup-ccbr-actions.outputs.docker-image }} + INPUT_FORCE_BUILD: ${{ inputs.force_build }} run: | + script=$(mktemp /tmp/ccbr_XXXXXX.py) + cat > "$script" << 'PYEOF' + import os from ccbr_actions.actions import set_output from ccbr_actions.docker import evaluate_docker_build_staleness_and_set_outputs - force_build = "${{ inputs.force_build }}".lower() == "true" + force_build = os.environ.get("INPUT_FORCE_BUILD", "").lower() == "true" if force_build: set_output("should_build", "true") set_output("reason", "force_build_requested") print("::notice::Force build requested. Skipping Docker Hub staleness check.") else: evaluate_docker_build_staleness_and_set_outputs( - dockerfile_path="${{ env.DOCKERFILE_PATH }}", - image_name="${{ env.IMAGENAME }}", + dockerfile_path=os.environ.get("DOCKERFILE_PATH", ""), + image_name=os.environ.get("IMAGENAME", ""), dockerhub_namespace="${{ inputs.dockerhub-namespace }}", - repo_name="${{ env.REPONAME }}", + repo_name=os.environ.get("REPONAME", ""), ) + PYEOF + if [ "${CCBR_INSTALL_METHOD}" = "docker" ]; then + docker run --rm \ + -v "${GITHUB_OUTPUT}:${GITHUB_OUTPUT}" \ + -e GITHUB_OUTPUT \ + -e INPUT_FORCE_BUILD \ + -e DOCKERFILE_PATH \ + -e IMAGENAME \ + -e REPONAME \ + -v "${script}:/tmp/ccbr_script.py:ro" \ + "${CCBR_DOCKER_IMAGE}" \ + python /tmp/ccbr_script.py + else + python "$script" + fi + rm -f "$script" - name: Build and push Docker image id: build_and_push @@ -181,15 +223,40 @@ runs: shell: bash id: run_script_in_container if: ${{ (inputs.print-versions == 'true') && (steps.check_tag_staleness.outputs.should_build == 'true') }} + env: + CCBR_INSTALL_METHOD: ${{ steps.setup-ccbr-actions.outputs.install-method }} + CCBR_DOCKER_IMAGE: ${{ steps.setup-ccbr-actions.outputs.docker-image }} run: | - script_src=$(command -v print_versions.sh) EXTRA_MOUNTS="" - if [ -f "${{ inputs.config-file }}" ]; then - CONFIG_FILE="/ws/${{ inputs.config-file }}" + if [ "${CCBR_INSTALL_METHOD}" = "docker" ]; then + # Extract print_versions.sh from the ccbr_actions Docker image + docker run --rm --entrypoint sh \ + -v /tmp:/tmp \ + "${CCBR_DOCKER_IMAGE}" \ + -c "cp \$(command -v print_versions.sh) /tmp/print_versions.sh" + script_src=/tmp/print_versions.sh + if [ -f "${{ inputs.config-file }}" ]; then + CONFIG_FILE="/ws/${{ inputs.config-file }}" + else + config_src=$(docker run --rm --entrypoint python \ + "${CCBR_DOCKER_IMAGE}" \ + -c 'import ccbr_actions.data; print(ccbr_actions.data.get_file_path("tool_version_commands.txt"))') + docker run --rm --entrypoint sh \ + -v /tmp:/tmp \ + "${CCBR_DOCKER_IMAGE}" \ + -c "cp ${config_src} /tmp/tool_version_commands.txt" + CONFIG_FILE="/ws/tool_version_commands.txt" + EXTRA_MOUNTS="-v /tmp/tool_version_commands.txt:/ws/tool_version_commands.txt:ro" + fi else - config_src=$(python -c 'import ccbr_actions.data; print(ccbr_actions.data.get_file_path("tool_version_commands.txt"))') - CONFIG_FILE="/ws/tool_version_commands.txt" - EXTRA_MOUNTS="-v ${config_src}:/ws/tool_version_commands.txt:ro" + script_src=$(command -v print_versions.sh) + if [ -f "${{ inputs.config-file }}" ]; then + CONFIG_FILE="/ws/${{ inputs.config-file }}" + else + config_src=$(python -c 'import ccbr_actions.data; print(ccbr_actions.data.get_file_path("tool_version_commands.txt"))') + CONFIG_FILE="/ws/tool_version_commands.txt" + EXTRA_MOUNTS="-v ${config_src}:/ws/tool_version_commands.txt:ro" + fi fi docker run --rm --entrypoint /bin/bash \ -v "${{ github.workspace }}:/ws" \ diff --git a/setup-ccbr-actions/action.yml b/setup-ccbr-actions/action.yml new file mode 100644 index 0000000..fd1f444 --- /dev/null +++ b/setup-ccbr-actions/action.yml @@ -0,0 +1,72 @@ +name: setup-ccbr-actions +description: "Set up ccbr_actions by pulling a Docker image if available on DockerHub, otherwise installing via pip" +author: "Kelly Sovacool" + +inputs: + ccbr-actions-version: + description: | + Version/tag of ccbr_actions to use (e.g. 'v0.7.0', 'main', 'latest'). + When a matching Docker image exists at nciccbr/ccbr_actions:{version}, it is + pulled instead of running pip install. Otherwise ccbr_actions is installed via pip. + required: true + default: "main" + python-version: + description: | + The version of Python to install when falling back to pip install. + required: true + default: "3.11" + dockerhub-namespace: + description: | + DockerHub namespace to check for the ccbr_actions Docker image. + required: false + default: "nciccbr" + +outputs: + install-method: + description: "Method used to make ccbr_actions available: 'docker' or 'pip'." + value: ${{ steps.check-docker.outputs.install-method }} + docker-image: + description: "Full Docker image reference used (e.g. nciccbr/ccbr_actions:v0.7.0), or empty when pip was used." + value: ${{ steps.check-docker.outputs.docker-image }} + +runs: + using: "composite" + steps: + - name: Check if Docker image exists on DockerHub + id: check-docker + shell: bash + run: | + version="${{ inputs.ccbr-actions-version }}" + namespace="${{ inputs.dockerhub-namespace }}" + repo_name="ccbr_actions" + + http_code=$(curl -s -o /dev/null -w "%{http_code}" \ + "https://hub.docker.com/v2/namespaces/${namespace}/repositories/${repo_name}/tags/${version}" \ + --max-time 20) + + if [ "${http_code}" = "200" ]; then + docker_image="${namespace}/${repo_name}:${version}" + echo "docker-image=${docker_image}" >> "$GITHUB_OUTPUT" + echo "install-method=docker" >> "$GITHUB_OUTPUT" + echo "::notice::ccbr_actions Docker image found: ${docker_image}" + else + echo "docker-image=" >> "$GITHUB_OUTPUT" + echo "install-method=pip" >> "$GITHUB_OUTPUT" + echo "::notice::No Docker image found for '${namespace}/${repo_name}:${version}'. Installing ccbr_actions via pip." + fi + + - name: Pull Docker image + if: ${{ steps.check-docker.outputs.install-method == 'docker' }} + shell: bash + run: docker pull ${{ steps.check-docker.outputs.docker-image }} + + - name: Set up Python + if: ${{ steps.check-docker.outputs.install-method == 'pip' }} + uses: actions/setup-python@v6 + with: + python-version: "${{ inputs.python-version }}" + + - name: Install ccbr_actions via pip + if: ${{ steps.check-docker.outputs.install-method == 'pip' }} + shell: bash + run: pip install --upgrade pip git+https://github.com/CCBR/actions.git@${{ inputs.ccbr-actions-version }} From 54646841f63a5303779289906a63089d8535dc67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:34:00 +0000 Subject: [PATCH 3/4] fix(build-docker): use unique container paths and stronger mktemp entropy - Mount temp script at its actual host path (not a fixed container path) to avoid conflicts when multiple runs use the same container - Increase mktemp randomness from 6 to 10 X's for better uniqueness" --- build-docker/action.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build-docker/action.yml b/build-docker/action.yml index ef780fc..3dfb0e2 100644 --- a/build-docker/action.yml +++ b/build-docker/action.yml @@ -115,7 +115,7 @@ runs: CCBR_INSTALL_METHOD: ${{ steps.setup-ccbr-actions.outputs.install-method }} CCBR_DOCKER_IMAGE: ${{ steps.setup-ccbr-actions.outputs.docker-image }} run: | - script=$(mktemp /tmp/ccbr_XXXXXX.py) + script=$(mktemp /tmp/ccbr_XXXXXXXXXX.py) cat > "$script" << 'PYEOF' from ccbr_actions.docker import prepare_docker_build_variables prepare_docker_build_variables( @@ -131,9 +131,9 @@ runs: -v "${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}" \ -e GITHUB_WORKSPACE \ -w "${GITHUB_WORKSPACE}" \ - -v "${script}:/tmp/ccbr_script.py:ro" \ + -v "${script}:${script}:ro" \ "${CCBR_DOCKER_IMAGE}" \ - python /tmp/ccbr_script.py + python "${script}" else python "$script" fi @@ -167,7 +167,7 @@ runs: CCBR_DOCKER_IMAGE: ${{ steps.setup-ccbr-actions.outputs.docker-image }} INPUT_FORCE_BUILD: ${{ inputs.force_build }} run: | - script=$(mktemp /tmp/ccbr_XXXXXX.py) + script=$(mktemp /tmp/ccbr_XXXXXXXXXX.py) cat > "$script" << 'PYEOF' import os from ccbr_actions.actions import set_output @@ -194,9 +194,9 @@ runs: -e DOCKERFILE_PATH \ -e IMAGENAME \ -e REPONAME \ - -v "${script}:/tmp/ccbr_script.py:ro" \ + -v "${script}:${script}:ro" \ "${CCBR_DOCKER_IMAGE}" \ - python /tmp/ccbr_script.py + python "${script}" else python "$script" fi From c8f08427f07169e7fcb239078c1a345100776af5 Mon Sep 17 00:00:00 2001 From: CCBR-bot <258092125+ccbr-bot@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:43:21 +0000 Subject: [PATCH 4/4] =?UTF-8?q?ci:=20=F0=9F=A4=96=20render=20readme=20&=20?= =?UTF-8?q?format=20everything=20with=20pre-commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 48e6281..7b95ffd 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,9 @@ Custom actions used in our github workflows. using mkdocs + mike - [post-release](post-release) - Post-release cleanup chores, intended to be triggered by publishing a release +- [setup-ccbr-actions](setup-ccbr-actions) - Set up ccbr_actions by + pulling a Docker image if available on DockerHub, otherwise installing + via pip - [sync-copilot-instructions](sync-copilot-instructions) - Sync Copilot instructions from a source repository to a target repository and open a PR