From 27178b15f6cdf74850bb25d17ad2c493203de8fe Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Thu, 8 Oct 2026 03:01:11 +0000 Subject: [PATCH 1/4] ci(images): publish validated master-fpm channel Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/nextcloud-development.yml | 109 +++++++++++++++++++- docs/images.md | 18 +++- 2 files changed, 121 insertions(+), 6 deletions(-) diff --git a/.github/workflows/nextcloud-development.yml b/.github/workflows/nextcloud-development.yml index 137628f..b2cc655 100644 --- a/.github/workflows/nextcloud-development.yml +++ b/.github/workflows/nextcloud-development.yml @@ -1,4 +1,4 @@ -name: Validate Nextcloud master image +name: Validate and publish Nextcloud master image on: pull_request: @@ -19,19 +19,29 @@ on: - 'scripts/scan-images.sh' - 'trivy.yaml' - '.github/workflows/nextcloud-development.yml' + schedule: + - cron: '17 3 * * *' workflow_dispatch: permissions: contents: read concurrency: - group: nextcloud-master-validation-${{ github.ref }} + group: nextcloud-master-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: true +env: + APP_IMAGE: ghcr.io/librecodecoop/nextcloud-docker-app + NEXTCLOUD_DAILY_URL: https://download.nextcloud.com/server/daily/latest-master.tar.bz2 + NEXTCLOUD_BASE_TAG: stable-fpm + jobs: validate: name: master-fpm / linux/${{ matrix.arch }} runs-on: ubuntu-latest + permissions: + contents: read + packages: write strategy: fail-fast: false @@ -62,6 +72,34 @@ jobs: detik-install: false file-install: false + - name: Resolve upstream inputs + id: upstream + shell: bash + run: | + set -euo pipefail + + base_digest="$(docker buildx imagetools inspect "nextcloud:${NEXTCLOUD_BASE_TAG}" | awk '/^Digest:/ { print $2; exit }')" + if [[ ! "${base_digest}" =~ ^sha256:[0-9a-f]{64}$ ]]; then + echo "Could not resolve nextcloud:${NEXTCLOUD_BASE_TAG} digest" >&2 + exit 1 + fi + + archive_name="$(basename "${NEXTCLOUD_DAILY_URL}")" + checksum_file="$(mktemp)" + curl -fsSL "${NEXTCLOUD_DAILY_URL}.sha512" -o "${checksum_file}" + source_sha512="$(awk -v archive="${archive_name}" '$2 == archive { print $1; exit }' "${checksum_file}")" + if [[ ! "${source_sha512}" =~ ^[0-9a-fA-F]{128}$ ]]; then + echo "Could not resolve SHA-512 for ${archive_name}" >&2 + exit 1 + fi + + created="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" + + echo "base_image=nextcloud@${base_digest}" >> "${GITHUB_OUTPUT}" + echo "base_digest=${base_digest}" >> "${GITHUB_OUTPUT}" + echo "source_sha512=${source_sha512,,}" >> "${GITHUB_OUTPUT}" + echo "created=${created}" >> "${GITHUB_OUTPUT}" + - name: Build Nextcloud master app image uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: @@ -69,9 +107,19 @@ jobs: platforms: linux/${{ matrix.arch }} load: true build-args: | - NEXTCLOUD_BASE_IMAGE=nextcloud:stable-fpm + NEXTCLOUD_BASE_IMAGE=${{ steps.upstream.outputs.base_image }} NEXTCLOUD_SOURCE=daily - NEXTCLOUD_DAILY_URL=https://download.nextcloud.com/server/daily/latest-master.tar.bz2 + NEXTCLOUD_DAILY_URL=${{ env.NEXTCLOUD_DAILY_URL }} + labels: | + org.opencontainers.image.source=https://github.com/${{ github.repository }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.created=${{ steps.upstream.outputs.created }} + org.opencontainers.image.version=master-fpm + coop.librecode.nextcloud.channel=master + coop.librecode.nextcloud.source=${{ env.NEXTCLOUD_DAILY_URL }} + coop.librecode.nextcloud.source.sha512=${{ steps.upstream.outputs.source_sha512 }} + coop.librecode.nextcloud.base.digest=${{ steps.upstream.outputs.base_digest }} + coop.librecode.runtime.variant=fpm tags: scan/master:${{ matrix.arch }} cache-from: type=gha,scope=master-${{ matrix.arch }} cache-to: type=gha,mode=max,scope=master-${{ matrix.arch }} @@ -95,3 +143,56 @@ jobs: env: APP_IMAGE: scan/master:${{ matrix.arch }} run: bats tests/app-image.bats + + - name: Log in to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push validated architecture image + if: github.event_name != 'pull_request' + shell: bash + env: + ARCH: ${{ matrix.arch }} + run: | + set -euo pipefail + staging_tag="master-fpm-${GITHUB_SHA}-${ARCH}" + docker tag "scan/master:${ARCH}" "${APP_IMAGE}:${staging_tag}" + docker push "${APP_IMAGE}:${staging_tag}" + + publish: + name: Publish master-fpm manifest + if: github.event_name != 'pull_request' + needs: + - validate + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish multi-platform master-fpm + shell: bash + run: | + set -euo pipefail + docker buildx imagetools create \ + --tag "${APP_IMAGE}:master-fpm" \ + "${APP_IMAGE}:master-fpm-${GITHUB_SHA}-amd64" \ + "${APP_IMAGE}:master-fpm-${GITHUB_SHA}-arm64" + + - name: Inspect published manifest + shell: bash + run: docker buildx imagetools inspect "${APP_IMAGE}:master-fpm" diff --git a/docs/images.md b/docs/images.md index 8dfb323..7a47144 100644 --- a/docs/images.md +++ b/docs/images.md @@ -166,6 +166,20 @@ The repository Compose environment keeps `NEXTCLOUD_VERSION` as its user-facing The app image remains LibreSign-ready while being generic across Nextcloud versions. It keeps repository-level runtime requirements such as Poppler, UTF-8 locale support, and the PHP bz2 extension, but does not reinstall extensions already supplied by the official Nextcloud base image such as Imagick. Dependabot owns Docker and GitHub Actions updates in this repository. Renovate is deliberately restricted to custom regex-managed values that Dependabot cannot see, currently the explicit Trivy binary version used by CI. The two bots must not manage the same dependency. +## Development publication + +The rolling Nextcloud Server `master` image is published as: + +``` +ghcr.io/librecodecoop/nextcloud-docker-app:master-fpm +``` + +Pull requests build, scan, and run runtime acceptance for both amd64 and arm64 without publishing. Pushes to `main`, the daily scheduled run, and manual workflow dispatches may publish only after both architectures pass those gates. + +The workflow resolves the official `nextcloud:stable-fpm` base to an OCI digest and records that digest in image metadata. It also records the SHA-512 of the exact upstream `latest-master.tar.bz2` artifact used for the build. This provides exact upstream artifact traceability without inventing a Nextcloud Git commit that the daily archive does not expose. + +Architecture-specific staging tags are implementation details used to assemble the multi-platform manifest. The public development-channel contract is `:master-fpm`. + ## Runtime acceptance The app image has a runtime acceptance test based on the same behavioral checks used by the official Nextcloud container projects. @@ -192,8 +206,8 @@ This document defines the target contract and the generic app-image foundation n Remaining work in #47 must continue incrementally. In particular: -- publish development images from the generic foundation using the documented `:master-fpm` contract; -- add the remaining OCI traceability metadata to published images; +- keep the `:master-fpm` publication workflow aligned with the generic app-image foundation; +- extend traceability metadata when upstream exposes stronger revision identifiers; - keep LibreSign-specific behavior out of the generic runtime; - preserve scan and runtime-acceptance gates before publication; - preserve the existing Compose environment until a replacement is explicitly validated. From 5637fca5a7ffe7521968704e697dab489447cac5 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Thu, 8 Oct 2026 03:02:51 +0000 Subject: [PATCH 2/4] fix(ci): resolve Nextcloud base digest through registry API Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/nextcloud-development.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nextcloud-development.yml b/.github/workflows/nextcloud-development.yml index b2cc655..99f2883 100644 --- a/.github/workflows/nextcloud-development.yml +++ b/.github/workflows/nextcloud-development.yml @@ -78,7 +78,18 @@ jobs: run: | set -euo pipefail - base_digest="$(docker buildx imagetools inspect "nextcloud:${NEXTCLOUD_BASE_TAG}" | awk '/^Digest:/ { print $2; exit }')" + token="$(curl -fsSL "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/nextcloud:pull" | jq -r '.token')" + test -n "${token}" + + headers="$(mktemp)" + curl -fsSLI \ + -H "Authorization: Bearer ${token}" \ + -H 'Accept: application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json' \ + -D "${headers}" \ + -o /dev/null \ + "https://registry-1.docker.io/v2/library/nextcloud/manifests/${NEXTCLOUD_BASE_TAG}" + + base_digest="$(awk 'BEGIN { IGNORECASE=1 } /^docker-content-digest:/ { gsub("\\r", "", $2); print $2; exit }' "${headers}")" if [[ ! "${base_digest}" =~ ^sha256:[0-9a-f]{64}$ ]]; then echo "Could not resolve nextcloud:${NEXTCLOUD_BASE_TAG} digest" >&2 exit 1 From 394c6d9c8837185278212842e59658a7135c3de4 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Thu, 8 Oct 2026 03:08:08 +0000 Subject: [PATCH 3/4] refactor(ci): move image publication logic into tested scripts Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/nextcloud-development.yml | 63 ++--------- scripts/publish-master-manifest.sh | 13 +++ scripts/push-master-architecture.sh | 20 ++++ scripts/resolve-nextcloud-upstream.sh | 43 ++++++++ tests/nextcloud-development-image.bats | 115 ++++++++++++++++++++ 5 files changed, 202 insertions(+), 52 deletions(-) create mode 100644 scripts/publish-master-manifest.sh create mode 100644 scripts/push-master-architecture.sh create mode 100644 scripts/resolve-nextcloud-upstream.sh create mode 100644 tests/nextcloud-development-image.bats diff --git a/.github/workflows/nextcloud-development.yml b/.github/workflows/nextcloud-development.yml index 99f2883..b332d66 100644 --- a/.github/workflows/nextcloud-development.yml +++ b/.github/workflows/nextcloud-development.yml @@ -10,6 +10,10 @@ on: - 'scripts/scan-images.sh' - 'trivy.yaml' - '.github/workflows/nextcloud-development.yml' + - 'scripts/resolve-nextcloud-upstream.sh' + - 'scripts/push-master-architecture.sh' + - 'scripts/publish-master-manifest.sh' + - 'tests/nextcloud-development-image.bats' push: branches: - main @@ -19,6 +23,10 @@ on: - 'scripts/scan-images.sh' - 'trivy.yaml' - '.github/workflows/nextcloud-development.yml' + - 'scripts/resolve-nextcloud-upstream.sh' + - 'scripts/push-master-architecture.sh' + - 'scripts/publish-master-manifest.sh' + - 'tests/nextcloud-development-image.bats' schedule: - cron: '17 3 * * *' workflow_dispatch: @@ -75,41 +83,7 @@ jobs: - name: Resolve upstream inputs id: upstream shell: bash - run: | - set -euo pipefail - - token="$(curl -fsSL "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/nextcloud:pull" | jq -r '.token')" - test -n "${token}" - - headers="$(mktemp)" - curl -fsSLI \ - -H "Authorization: Bearer ${token}" \ - -H 'Accept: application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json' \ - -D "${headers}" \ - -o /dev/null \ - "https://registry-1.docker.io/v2/library/nextcloud/manifests/${NEXTCLOUD_BASE_TAG}" - - base_digest="$(awk 'BEGIN { IGNORECASE=1 } /^docker-content-digest:/ { gsub("\\r", "", $2); print $2; exit }' "${headers}")" - if [[ ! "${base_digest}" =~ ^sha256:[0-9a-f]{64}$ ]]; then - echo "Could not resolve nextcloud:${NEXTCLOUD_BASE_TAG} digest" >&2 - exit 1 - fi - - archive_name="$(basename "${NEXTCLOUD_DAILY_URL}")" - checksum_file="$(mktemp)" - curl -fsSL "${NEXTCLOUD_DAILY_URL}.sha512" -o "${checksum_file}" - source_sha512="$(awk -v archive="${archive_name}" '$2 == archive { print $1; exit }' "${checksum_file}")" - if [[ ! "${source_sha512}" =~ ^[0-9a-fA-F]{128}$ ]]; then - echo "Could not resolve SHA-512 for ${archive_name}" >&2 - exit 1 - fi - - created="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" - - echo "base_image=nextcloud@${base_digest}" >> "${GITHUB_OUTPUT}" - echo "base_digest=${base_digest}" >> "${GITHUB_OUTPUT}" - echo "source_sha512=${source_sha512,,}" >> "${GITHUB_OUTPUT}" - echo "created=${created}" >> "${GITHUB_OUTPUT}" + run: scripts/resolve-nextcloud-upstream.sh "${NEXTCLOUD_BASE_TAG}" "${NEXTCLOUD_DAILY_URL}" >> "${GITHUB_OUTPUT}" - name: Build Nextcloud master app image uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 @@ -166,13 +140,7 @@ jobs: - name: Push validated architecture image if: github.event_name != 'pull_request' shell: bash - env: - ARCH: ${{ matrix.arch }} - run: | - set -euo pipefail - staging_tag="master-fpm-${GITHUB_SHA}-${ARCH}" - docker tag "scan/master:${ARCH}" "${APP_IMAGE}:${staging_tag}" - docker push "${APP_IMAGE}:${staging_tag}" + run: scripts/push-master-architecture.sh "${APP_IMAGE}" "scan/master:${{ matrix.arch }}" "${GITHUB_SHA}" "${{ matrix.arch }}" publish: name: Publish master-fpm manifest @@ -197,13 +165,4 @@ jobs: - name: Publish multi-platform master-fpm shell: bash - run: | - set -euo pipefail - docker buildx imagetools create \ - --tag "${APP_IMAGE}:master-fpm" \ - "${APP_IMAGE}:master-fpm-${GITHUB_SHA}-amd64" \ - "${APP_IMAGE}:master-fpm-${GITHUB_SHA}-arm64" - - - name: Inspect published manifest - shell: bash - run: docker buildx imagetools inspect "${APP_IMAGE}:master-fpm" + run: scripts/publish-master-manifest.sh "${APP_IMAGE}" "${GITHUB_SHA}" diff --git a/scripts/publish-master-manifest.sh b/scripts/publish-master-manifest.sh new file mode 100644 index 0000000..47f3464 --- /dev/null +++ b/scripts/publish-master-manifest.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -euo pipefail + +app_image=${1:?Usage: publish-master-manifest.sh } +revision=${2:?Usage: publish-master-manifest.sh } + +docker buildx imagetools create \ + --tag "${app_image}:master-fpm" \ + "${app_image}:master-fpm-${revision}-amd64" \ + "${app_image}:master-fpm-${revision}-arm64" + +docker buildx imagetools inspect "${app_image}:master-fpm" diff --git a/scripts/push-master-architecture.sh b/scripts/push-master-architecture.sh new file mode 100644 index 0000000..6268c2c --- /dev/null +++ b/scripts/push-master-architecture.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -euo pipefail + +app_image=${1:?Usage: push-master-architecture.sh } +source_tag=${2:?Usage: push-master-architecture.sh } +revision=${3:?Usage: push-master-architecture.sh } +arch=${4:?Usage: push-master-architecture.sh } + +case "${arch}" in + amd64|arm64) ;; + *) + echo "Unsupported architecture: ${arch}" >&2 + exit 2 + ;; +esac + +staging_tag="master-fpm-${revision}-${arch}" +docker tag "${source_tag}" "${app_image}:${staging_tag}" +docker push "${app_image}:${staging_tag}" diff --git a/scripts/resolve-nextcloud-upstream.sh b/scripts/resolve-nextcloud-upstream.sh new file mode 100644 index 0000000..5707a58 --- /dev/null +++ b/scripts/resolve-nextcloud-upstream.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +set -euo pipefail + +base_tag=${1:?Usage: resolve-nextcloud-upstream.sh } +daily_url=${2:?Usage: resolve-nextcloud-upstream.sh } + +token_response="$(curl -fsSL "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/nextcloud:pull")" +token="$(printf '%s' "${token_response}" | sed -n 's/.*"token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" +if [ -z "${token}" ]; then + echo "Could not obtain Docker Hub token" >&2 + exit 1 +fi + +headers="$(mktemp)" +checksum_file="$(mktemp)" +trap 'rm -f "$headers" "$checksum_file"' EXIT + +curl -fsSLI \ + -H "Authorization: Bearer ${token}" \ + -H 'Accept: application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json' \ + -D "${headers}" \ + -o /dev/null \ + "https://registry-1.docker.io/v2/library/nextcloud/manifests/${base_tag}" + +base_digest="$(awk 'BEGIN { IGNORECASE=1 } /^docker-content-digest:/ { gsub("\r", "", $2); print $2; exit }' "${headers}")" +if [[ ! "${base_digest}" =~ ^sha256:[0-9a-f]{64}$ ]]; then + echo "Could not resolve nextcloud:${base_tag} digest" >&2 + exit 1 +fi + +archive_name="$(basename "${daily_url}")" +curl -fsSL "${daily_url}.sha512" -o "${checksum_file}" +source_sha512="$(awk -v archive="${archive_name}" '$2 == archive { print $1; exit }' "${checksum_file}")" +if [[ ! "${source_sha512}" =~ ^[0-9a-fA-F]{128}$ ]]; then + echo "Could not resolve SHA-512 for ${archive_name}" >&2 + exit 1 +fi + +printf 'base_image=nextcloud@%s\n' "${base_digest}" +printf 'base_digest=%s\n' "${base_digest}" +printf 'source_sha512=%s\n' "${source_sha512,,}" +printf 'created=%s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" diff --git a/tests/nextcloud-development-image.bats b/tests/nextcloud-development-image.bats new file mode 100644 index 0000000..6b0da4a --- /dev/null +++ b/tests/nextcloud-development-image.bats @@ -0,0 +1,115 @@ +#!/usr/bin/env bats + +setup() { + TEST_ROOT="$(mktemp -d)" + BIN_DIR="$TEST_ROOT/bin" + mkdir -p "$BIN_DIR" + export PATH="$BIN_DIR:$PATH" +} + +teardown() { + rm -rf "$TEST_ROOT" +} + +@test "resolve upstream emits validated metadata" { + cat > "$BIN_DIR/curl" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +args="$*" +if [[ "$args" == *"auth.docker.io/token"* ]]; then + printf '{"token":"test-token"}' +elif [[ "$args" == *"registry-1.docker.io"* ]]; then + headers="" + while [ "$#" -gt 0 ]; do + if [ "$1" = "-D" ]; then + headers=$2 + shift 2 + else + shift + fi + done + printf 'docker-content-digest: sha256:%064d\r\n' 0 > "$headers" +elif [[ "$args" == *".sha512"* ]]; then + output="" + while [ "$#" -gt 0 ]; do + if [ "$1" = "-o" ]; then + output=$2 + shift 2 + else + shift + fi + done + printf '%0128d latest-master.tar.bz2\n' 0 > "$output" +else + exit 1 +fi +EOF + chmod +x "$BIN_DIR/curl" + + run scripts/resolve-nextcloud-upstream.sh stable-fpm https://download.nextcloud.com/server/daily/latest-master.tar.bz2 + + [ "$status" -eq 0 ] + [[ "$output" == *"base_image=nextcloud@sha256:"* ]] + [[ "$output" == *"source_sha512="* ]] + [[ "$output" == *"created="* ]] +} + +@test "resolve upstream rejects an invalid base digest" { + cat > "$BIN_DIR/curl" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +args="$*" +if [[ "$args" == *"auth.docker.io/token"* ]]; then + printf '{"token":"test-token"}' +elif [[ "$args" == *"registry-1.docker.io"* ]]; then + while [ "$#" -gt 0 ]; do + if [ "$1" = "-D" ]; then + printf 'docker-content-digest: invalid\r\n' > "$2" + exit 0 + fi + shift + done +fi +EOF + chmod +x "$BIN_DIR/curl" + + run scripts/resolve-nextcloud-upstream.sh stable-fpm https://download.nextcloud.com/server/daily/latest-master.tar.bz2 + + [ "$status" -ne 0 ] + [[ "$output" == *"Could not resolve nextcloud:stable-fpm digest"* ]] +} + +@test "architecture publication uses only the requested staging tag" { + cat > "$BIN_DIR/docker" <> "$TEST_ROOT/docker.log" +EOF + chmod +x "$BIN_DIR/docker" + + run scripts/push-master-architecture.sh ghcr.io/example/app scan/master:amd64 abc123 amd64 + + [ "$status" -eq 0 ] + grep -Fx "tag scan/master:amd64 ghcr.io/example/app:master-fpm-abc123-amd64" "$TEST_ROOT/docker.log" + grep -Fx "push ghcr.io/example/app:master-fpm-abc123-amd64" "$TEST_ROOT/docker.log" +} + +@test "architecture publication rejects unsupported architectures" { + run scripts/push-master-architecture.sh ghcr.io/example/app scan/master:s390x abc123 s390x + + [ "$status" -eq 2 ] + [[ "$output" == *"Unsupported architecture: s390x"* ]] +} + +@test "manifest publication combines exactly amd64 and arm64" { + cat > "$BIN_DIR/docker" <> "$TEST_ROOT/docker.log" +EOF + chmod +x "$BIN_DIR/docker" + + run scripts/publish-master-manifest.sh ghcr.io/example/app abc123 + + [ "$status" -eq 0 ] + grep -Fx "buildx imagetools create --tag ghcr.io/example/app:master-fpm ghcr.io/example/app:master-fpm-abc123-amd64 ghcr.io/example/app:master-fpm-abc123-arm64" "$TEST_ROOT/docker.log" + grep -Fx "buildx imagetools inspect ghcr.io/example/app:master-fpm" "$TEST_ROOT/docker.log" +} From 39770d0b6d82582aa286ba5413b311f32dff4cb6 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Thu, 8 Oct 2026 03:08:44 +0000 Subject: [PATCH 4/4] test(ci): cover development image helper scripts Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/nextcloud-development.yml | 29 ++++++++++++++++++--- Makefile | 5 +++- tests/nextcloud-development-image.bats | 10 +++---- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/.github/workflows/nextcloud-development.yml b/.github/workflows/nextcloud-development.yml index b332d66..e0fbe4e 100644 --- a/.github/workflows/nextcloud-development.yml +++ b/.github/workflows/nextcloud-development.yml @@ -44,7 +44,30 @@ env: NEXTCLOUD_BASE_TAG: stable-fpm jobs: + script-tests: + name: Test development image scripts + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Setup Bats + uses: bats-core/bats-action@77d6fb60505b4d0d1d73e48bd035b55074bbfb43 # 4.0.0 + with: + support-install: false + assert-install: false + detik-install: false + file-install: false + + - name: Test development image scripts + run: bats tests/nextcloud-development-image.bats + validate: + needs: + - script-tests name: master-fpm / linux/${{ matrix.arch }} runs-on: ubuntu-latest permissions: @@ -83,7 +106,7 @@ jobs: - name: Resolve upstream inputs id: upstream shell: bash - run: scripts/resolve-nextcloud-upstream.sh "${NEXTCLOUD_BASE_TAG}" "${NEXTCLOUD_DAILY_URL}" >> "${GITHUB_OUTPUT}" + run: bash scripts/resolve-nextcloud-upstream.sh "${NEXTCLOUD_BASE_TAG}" "${NEXTCLOUD_DAILY_URL}" >> "${GITHUB_OUTPUT}" - name: Build Nextcloud master app image uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 @@ -140,7 +163,7 @@ jobs: - name: Push validated architecture image if: github.event_name != 'pull_request' shell: bash - run: scripts/push-master-architecture.sh "${APP_IMAGE}" "scan/master:${{ matrix.arch }}" "${GITHUB_SHA}" "${{ matrix.arch }}" + run: bash scripts/push-master-architecture.sh "${APP_IMAGE}" "scan/master:${{ matrix.arch }}" "${GITHUB_SHA}" "${{ matrix.arch }}" publish: name: Publish master-fpm manifest @@ -165,4 +188,4 @@ jobs: - name: Publish multi-platform master-fpm shell: bash - run: scripts/publish-master-manifest.sh "${APP_IMAGE}" "${GITHUB_SHA}" + run: bash scripts/publish-master-manifest.sh "${APP_IMAGE}" "${GITHUB_SHA}" diff --git a/Makefile b/Makefile index 62dae15..95454a2 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ COMPOSE ?= docker compose GARAGES3_COMPOSE_FILE ?= docker-compose-garages3.yml APP_TEST_IMAGE ?= nextcloud-app:acceptance -.PHONY: up-garages3 down-garages3 bootstrap-garages3 garage-status-garages3 start-garages3 wait-nextcloud-garages3 setup-garages3 test-scan-images test-ncdd test-app-image test-current-app-image scan-images +.PHONY: up-garages3 down-garages3 bootstrap-garages3 garage-status-garages3 start-garages3 wait-nextcloud-garages3 setup-garages3 test-scan-images test-development-image-scripts test-ncdd test-app-image test-current-app-image scan-images up-garages3: $(COMPOSE) -f $(GARAGES3_COMPOSE_FILE) up -d garage @@ -30,6 +30,9 @@ setup-garages3: test-scan-images: bash tests/test-scan-images.sh +test-development-image-scripts: + bats tests/nextcloud-development-image.bats + test-ncdd: bats tests/ncdd.bats diff --git a/tests/nextcloud-development-image.bats b/tests/nextcloud-development-image.bats index 6b0da4a..970c53c 100644 --- a/tests/nextcloud-development-image.bats +++ b/tests/nextcloud-development-image.bats @@ -46,7 +46,7 @@ fi EOF chmod +x "$BIN_DIR/curl" - run scripts/resolve-nextcloud-upstream.sh stable-fpm https://download.nextcloud.com/server/daily/latest-master.tar.bz2 + run bash scripts/resolve-nextcloud-upstream.sh stable-fpm https://download.nextcloud.com/server/daily/latest-master.tar.bz2 [ "$status" -eq 0 ] [[ "$output" == *"base_image=nextcloud@sha256:"* ]] @@ -73,7 +73,7 @@ fi EOF chmod +x "$BIN_DIR/curl" - run scripts/resolve-nextcloud-upstream.sh stable-fpm https://download.nextcloud.com/server/daily/latest-master.tar.bz2 + run bash scripts/resolve-nextcloud-upstream.sh stable-fpm https://download.nextcloud.com/server/daily/latest-master.tar.bz2 [ "$status" -ne 0 ] [[ "$output" == *"Could not resolve nextcloud:stable-fpm digest"* ]] @@ -86,7 +86,7 @@ printf '%s\n' "\$*" >> "$TEST_ROOT/docker.log" EOF chmod +x "$BIN_DIR/docker" - run scripts/push-master-architecture.sh ghcr.io/example/app scan/master:amd64 abc123 amd64 + run bash scripts/push-master-architecture.sh ghcr.io/example/app scan/master:amd64 abc123 amd64 [ "$status" -eq 0 ] grep -Fx "tag scan/master:amd64 ghcr.io/example/app:master-fpm-abc123-amd64" "$TEST_ROOT/docker.log" @@ -94,7 +94,7 @@ EOF } @test "architecture publication rejects unsupported architectures" { - run scripts/push-master-architecture.sh ghcr.io/example/app scan/master:s390x abc123 s390x + run bash scripts/push-master-architecture.sh ghcr.io/example/app scan/master:s390x abc123 s390x [ "$status" -eq 2 ] [[ "$output" == *"Unsupported architecture: s390x"* ]] @@ -107,7 +107,7 @@ printf '%s\n' "\$*" >> "$TEST_ROOT/docker.log" EOF chmod +x "$BIN_DIR/docker" - run scripts/publish-master-manifest.sh ghcr.io/example/app abc123 + run bash scripts/publish-master-manifest.sh ghcr.io/example/app abc123 [ "$status" -eq 0 ] grep -Fx "buildx imagetools create --tag ghcr.io/example/app:master-fpm ghcr.io/example/app:master-fpm-abc123-amd64 ghcr.io/example/app:master-fpm-abc123-arm64" "$TEST_ROOT/docker.log"