From ed43dca7b6c2dc27916f7eaba0445e461e733403 Mon Sep 17 00:00:00 2001 From: Mauro Ezequiel Moltrasio Date: Wed, 16 Sep 2026 12:58:31 +0200 Subject: [PATCH 1/3] ROX-36983: version containers used in tests (#1789) After a change in the Vi/Vim versioned used by Fedora broke our integration tests, we have decided to tag and version all images used in our tests. This patch adds a CI job for building and pushing the images when `tests/containers/QA_TAG` is updated, much like we do in stackrox/collector. It also modifies the existing tests from always building the images to: * Try to find the image locally. * Attempt to pull it if it is not found. * If unable to pulled and allowed to build locally, do that. --- .github/workflows/ci.yml | 10 ++- .github/workflows/integration-tests.yml | 5 ++ .github/workflows/konflux-tests.yml | 6 ++ .github/workflows/test-containers-build.yml | 84 +++++++++++++++++++++ ansible/run-tests.yml | 2 + tests/conftest.py | 5 ++ tests/containers.py | 54 +++++++++++++ tests/containers/Makefile | 10 +++ tests/containers/QA_TAG | 1 + tests/containers/constants.mk | 13 ++++ tests/containers/editors/Containerfile | 2 +- tests/containers/editors/Makefile | 19 +++++ tests/containers/fedora/Containerfile | 1 + tests/containers/fedora/Makefile | 19 +++++ tests/containers/self-deleter/Makefile | 19 +++++ tests/test_editors/commons.py | 43 +++++++++-- tests/test_editors/test_sed.py | 26 ++++--- tests/test_editors/test_vi.py | 32 ++++---- tests/test_misc.py | 14 ++-- 19 files changed, 323 insertions(+), 42 deletions(-) create mode 100644 .github/workflows/test-containers-build.yml create mode 100644 tests/containers.py create mode 100644 tests/containers/Makefile create mode 100644 tests/containers/QA_TAG create mode 100644 tests/containers/constants.mk create mode 100644 tests/containers/editors/Makefile create mode 100644 tests/containers/fedora/Containerfile create mode 100644 tests/containers/fedora/Makefile create mode 100644 tests/containers/self-deleter/Makefile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73b4f247..5332c6e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,6 +72,10 @@ jobs: rustup component add rustfmt make format-check + unit-tests: + uses: ./.github/workflows/unit-tests.yml + secrets: inherit + vars: runs-on: ubuntu-24.04 outputs: @@ -180,8 +184,8 @@ jobs: podman manifest inspect ${{ needs.vars.outputs.rhacs-eng-image }} podman manifest push ${{ needs.vars.outputs.rhacs-eng-image }} - unit-tests: - uses: ./.github/workflows/unit-tests.yml + test-containers-build: + uses: ./.github/workflows/test-containers-build.yml secrets: inherit integration-tests: @@ -189,9 +193,11 @@ jobs: - vars - manifest-stackrox-io - manifest-rhacs-eng + - test-containers-build uses: ./.github/workflows/integration-tests.yml with: tag: ${{ needs.vars.outputs.tag }} + qa-tag: ${{ needs.test-containers-build.outputs.qa-tag }} secrets: inherit performance-tests: diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index f32e77f8..49c4c483 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -13,6 +13,9 @@ on: tag: description: The container image tag to test type: string + qa-tag: + description: The test containers tag to use + type: string job-tag: description: Additional tag to prevent collision on GCP VM naming type: string @@ -68,6 +71,7 @@ jobs: env: FACT_VERSION: ${{ inputs.version }} FACT_REGISTRY: ${{ inputs.registry }} + FACT_QA_TAG: ${{ inputs.qa-tag }} FACT_TAG: ${{ inputs.tag }} run: | FACT_IMAGE_NAME="$(make -sC "${GITHUB_WORKSPACE}/fact" image-name)" @@ -77,6 +81,7 @@ jobs: fact: image: ${FACT_IMAGE_NAME} version: ${FACT_VERSION} + qa_tag: ${FACT_QA_TAG} quay: username: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} password: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} diff --git a/.github/workflows/konflux-tests.yml b/.github/workflows/konflux-tests.yml index f3415578..a42a9784 100644 --- a/.github/workflows/konflux-tests.yml +++ b/.github/workflows/konflux-tests.yml @@ -40,14 +40,20 @@ jobs: image: rhacs-eng/release-fact:${{ needs.init.outputs.fact-tag }} limit: 2400 # 40m + test-containers-build: + uses: ./.github/workflows/test-containers-build.yml + secrets: inherit + run-konflux-tests: uses: ./.github/workflows/integration-tests.yml needs: - init - wait-for-images + - test-containers-build with: version: ${{ github.head_ref || github.ref_name }} registry: quay.io/rhacs-eng/release-fact tag: ${{ needs.init.outputs.fact-tag }} + qa-tag: ${{ needs.test-containers-build.outputs.qa-tag }} job-tag: konf secrets: inherit diff --git a/.github/workflows/test-containers-build.yml b/.github/workflows/test-containers-build.yml new file mode 100644 index 00000000..1ae27a3a --- /dev/null +++ b/.github/workflows/test-containers-build.yml @@ -0,0 +1,84 @@ +name: Build integration test containers + +on: + workflow_call: + outputs: + qa-tag: + value: ${{ jobs.determine-qa-tag.outputs.qa-tag }} + +jobs: + should-build-test-containers: + name: Determine if tests image needs to be built + runs-on: ubuntu-24.04 + outputs: + build-image: ${{ (steps.changed.outputs.qa-tag == 'true' || + contains(github.event.pull_request.labels.*.name, 'rebuild-test-container')) }} + + steps: + - uses: actions/checkout@v7 + + - uses: dorny/paths-filter@v4 + id: changed + with: + list-files: shell + base: ${{ github.ref }} + + # Only trigger a rebuild when the QA tag has changed + filters: | + qa-tag: + - tests/containers/QA_TAG + + determine-qa-tag: + name: Determine QA tag + runs-on: ubuntu-24.04 + needs: + - should-build-test-containers + outputs: + qa-tag: ${{ steps.qa-tag.outputs.qa-tag }} + steps: + - uses: actions/checkout@v7 + with: + submodules: true + fetch-depth: 0 + persist-credentials: false + - name: Determine QA tag + id: qa-tag + run: | + QA_TAG="$(cat "${GITHUB_WORKSPACE}/tests/containers/QA_TAG")" + if [[ "${GITHUB_EVENT_NAME}" == "pull_request" && "${{ needs.should-build-test-containers.outputs.build-image }}" == "true" ]]; then + QA_TAG="${QA_TAG}-$(make tag)" + fi + echo "::notice::Using QA tag: ${QA_TAG}" + echo "qa-tag=${QA_TAG}" >> "$GITHUB_OUTPUT" + + build-test-containers: + name: Build and push test containers + runs-on: ubuntu-24.04 + needs: + - determine-qa-tag + - should-build-test-containers + if: needs.should-build-test-containers.outputs.build-image == 'true' || + contains(github.event.pull_request.labels.*.name, 'rebuild-test-container') + + steps: + - uses: actions/checkout@v7 + + - name: Setup QEMU + uses: docker/setup-qemu-action@v4 + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v4 + + - name: Login to quay.io/rhacs-eng + uses: docker/login-action@v3 + with: + registry: quay.io + username: ${{ secrets.QUAY_RHACS_ENG_RW_USERNAME }} + password: ${{ secrets.QUAY_RHACS_ENG_RW_PASSWORD }} + + - name: Build and Push containers + env: + FACT_QA_TAG: ${{ needs.determine-qa-tag.outputs.qa-tag }} + PLATFORM: linux/amd64,linux/arm64 + run: | + make -C "${GITHUB_WORKSPACE}/tests/containers" build-and-push diff --git a/ansible/run-tests.yml b/ansible/run-tests.yml index 20578442..c2e3a02f 100644 --- a/ansible/run-tests.yml +++ b/ansible/run-tests.yml @@ -37,6 +37,8 @@ - block: - name: Run tests + environment: + FACT_QA_TAG: "{{ qa_tag | default(None) }}" ansible.builtin.shell: cmd: | cd "${HOME}/fact" diff --git a/tests/conftest.py b/tests/conftest.py index cce6a03f..7e4f1ab8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -228,3 +228,8 @@ def fact(request, docker_client, fact_config, server, logs_dir, test_file): def pytest_addoption(parser): parser.addoption('--image', action='store', default='quay.io/stackrox-io/fact:latest', help='The image to be used for testing') + parser.addoption( + '--no-local-builds', + action='store_true', + help='Do not build test containers locally', + ) diff --git a/tests/containers.py b/tests/containers.py new file mode 100644 index 00000000..af515dc2 --- /dev/null +++ b/tests/containers.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +import os + +import docker +import docker.errors +from docker.models.images import Image + + +def read_qa_tag() -> str: + tag = os.environ.get('FACT_QA_TAG') + if tag is not None: + return tag + + with open('containers/QA_TAG') as f: + return f.read().strip() + + +QA_TAG = read_qa_tag() +QA_REPOSITORY = os.environ.get( + 'FACT_QA_REPOSITORY', 'quay.io/rhacs-eng/qa-multi-arch' +) + + +def pull_or_build( + docker_client: docker.DockerClient, + tag: str, + path: str | None, + no_local_builds: bool, + containerfile: str = 'Containerfile', +) -> Image: + tag = f'{tag}-{QA_TAG}' + image = f'{QA_REPOSITORY}:{tag}' + + try: + return docker_client.images.get(image) + except docker.errors.ImageNotFound: + print(f'{image} not found locally, attempting to pull') + + try: + return docker_client.images.pull(QA_REPOSITORY, tag=tag) + except docker.errors.APIError as e: + if no_local_builds: + raise e + + if e.status_code != 401 and e.status_code != 404: + raise e + print(f'Failed to pull image: {e}') + print('Attempting to build image from source') + + image, _ = docker_client.images.build( + path=path, tag=image, dockerfile=containerfile + ) + return image diff --git a/tests/containers/Makefile b/tests/containers/Makefile new file mode 100644 index 00000000..4eb28d59 --- /dev/null +++ b/tests/containers/Makefile @@ -0,0 +1,10 @@ +include constants.mk + +TARGETS := build build-and-push +DIRS := $(wildcard ./*/) + +$(TARGETS): $(DIRS) +$(DIRS): + $(MAKE) -C $@ $(MAKECMDGOALS) + +.PHONY: $(TARGETS) $(DIRS) diff --git a/tests/containers/QA_TAG b/tests/containers/QA_TAG new file mode 100644 index 00000000..6e8bf73a --- /dev/null +++ b/tests/containers/QA_TAG @@ -0,0 +1 @@ +0.1.0 diff --git a/tests/containers/constants.mk b/tests/containers/constants.mk new file mode 100644 index 00000000..01b5cb2a --- /dev/null +++ b/tests/containers/constants.mk @@ -0,0 +1,13 @@ +REPO_ROOT := $(shell git rev-parse --show-toplevel) + +include $(REPO_ROOT)/constants.mk + +# PLATFORM can be a comma separated list of platforms of the form linux/ +# it is passed to buildx, which will build for each one (this is important +# for pushing multi-arch images because `buildx --push` will push a manifest list +# containing all the relevant platforms) +PLATFORM ?= linux/amd64 + +# Path needs to be absolute so subdirectories don't fail when they include this +# file. +FACT_QA_TAG ?= $(shell cat "$(REPO_ROOT)/tests/containers/QA_TAG") diff --git a/tests/containers/editors/Containerfile b/tests/containers/editors/Containerfile index 47c91b43..3357f249 100644 --- a/tests/containers/editors/Containerfile +++ b/tests/containers/editors/Containerfile @@ -1,4 +1,4 @@ -FROM quay.io/fedora/fedora:43 +FROM quay.io/fedora/fedora:44 RUN dnf install -y \ neovim \ diff --git a/tests/containers/editors/Makefile b/tests/containers/editors/Makefile new file mode 100644 index 00000000..df1f655e --- /dev/null +++ b/tests/containers/editors/Makefile @@ -0,0 +1,19 @@ +include ../constants.mk + +FACT_QA_EDITORS_TAG := fact-editors-$(FACT_QA_TAG) + +all: build + +run: + $(DOCKER) buildx build $(BUILD_ARGS) --platform $(PLATFORM) \ + -t quay.io/rhacs-eng/qa-multi-arch:$(FACT_QA_EDITORS_TAG) \ + -f Containerfile \ + $(CURDIR) + +build: BUILD_ARGS = --load +build: run + +build-and-push: BUILD_ARGS = --push +build-and-push: run + +.PHONY: build build-and-push all run diff --git a/tests/containers/fedora/Containerfile b/tests/containers/fedora/Containerfile new file mode 100644 index 00000000..3ebbf5b2 --- /dev/null +++ b/tests/containers/fedora/Containerfile @@ -0,0 +1 @@ +FROM quay.io/fedora/fedora:44 diff --git a/tests/containers/fedora/Makefile b/tests/containers/fedora/Makefile new file mode 100644 index 00000000..c3eabd41 --- /dev/null +++ b/tests/containers/fedora/Makefile @@ -0,0 +1,19 @@ +include ../constants.mk + +FACT_QA_FEDORA_TAG := fact-fedora-$(FACT_QA_TAG) + +all: build + +run: + $(DOCKER) buildx build $(BUILD_ARGS) --platform $(PLATFORM) \ + -t quay.io/rhacs-eng/qa-multi-arch:$(FACT_QA_FEDORA_TAG) \ + -f Containerfile \ + $(CURDIR) + +build: BUILD_ARGS = --load +build: run + +build-and-push: BUILD_ARGS = --push +build-and-push: run + +.PHONY: build build-and-push all run diff --git a/tests/containers/self-deleter/Makefile b/tests/containers/self-deleter/Makefile new file mode 100644 index 00000000..74ed5009 --- /dev/null +++ b/tests/containers/self-deleter/Makefile @@ -0,0 +1,19 @@ +include ../constants.mk + +FACT_QA_SELF_DELETER_TAG := fact-self-deleter-$(FACT_QA_TAG) + +all: build + +run: + $(DOCKER) buildx build $(BUILD_ARGS) --platform $(PLATFORM) \ + -t quay.io/rhacs-eng/qa-multi-arch:$(FACT_QA_SELF_DELETER_TAG) \ + -f Containerfile \ + $(CURDIR) + +build: BUILD_ARGS = --load +build: run + +build-and-push: BUILD_ARGS = --push +build-and-push: run + +.PHONY: build build-and-push all run diff --git a/tests/test_editors/commons.py b/tests/test_editors/commons.py index 150e662d..0e6895fc 100644 --- a/tests/test_editors/commons.py +++ b/tests/test_editors/commons.py @@ -1,20 +1,27 @@ import os +import docker +import docker.models.images import pytest +from containers import pull_or_build + def get_vi_test_file(dir): return os.path.join(dir, '4913') @pytest.fixture(scope='session') -def build_editor_image(docker_client): - image, _ = docker_client.images.build( +def build_editor_image( + request: pytest.FixtureRequest, docker_client: docker.DockerClient +): + no_local_builds = bool(request.config.getoption('--no-local-builds')) + return pull_or_build( + docker_client, + tag='fact-editors', path='containers/editors', - tag='editors:latest', - dockerfile='Containerfile', + no_local_builds=no_local_builds, ) - return image def run_editor_container(image, docker_client, ignored_dir): @@ -38,9 +45,31 @@ def run_editor_container(image, docker_client, ignored_dir): container.remove() +@pytest.fixture(scope='session') +def build_fedora_image( + request: pytest.FixtureRequest, docker_client: docker.DockerClient +) -> docker.models.images.Image: + no_local_builds = bool(request.config.getoption('--no-local-builds')) + return pull_or_build( + docker_client, + tag='fact-fedora', + path='containers/fedora', + no_local_builds=no_local_builds, + ) + + @pytest.fixture -def vi_container(docker_client, ignored_dir): - yield from run_editor_container('quay.io/fedora/fedora:43', docker_client, ignored_dir) +def fedora_container( + docker_client: docker.DockerClient, + build_fedora_image: docker.models.images.Image, + ignored_dir: str, +): + image = build_fedora_image.tags[0] + yield from run_editor_container( + image, + docker_client, + ignored_dir, + ) @pytest.fixture diff --git a/tests/test_editors/test_sed.py b/tests/test_editors/test_sed.py index 122d1527..1ba75add 100644 --- a/tests/test_editors/test_sed.py +++ b/tests/test_editors/test_sed.py @@ -2,15 +2,16 @@ from event import Event, EventType, Process -def test_sed(vi_container, server): +def test_sed(fedora_container, server): + assert fedora_container.id is not None # File Under Test fut = '/mounted/test.txt' - create_cmd = f"sh -c \"echo 'This is a test' > {fut}\"" - sed_cmd = fr'sed -i -e "s/a test/not \\0/" {fut}' - container_id = vi_container.id[:12] + create_cmd = f'sh -c "echo \'This is a test\' > {fut}"' + sed_cmd = rf'sed -i -e "s/a test/not \\0/" {fut}' + container_id = fedora_container.id[:12] - vi_container.exec_run(create_cmd) - vi_container.exec_run(sed_cmd) + fedora_container.exec_run(create_cmd) + fedora_container.exec_run(sed_cmd) shell = Process.in_container( exe_path='/usr/bin/bash', @@ -41,15 +42,16 @@ def test_sed(vi_container, server): server.wait_events(events, strict=True) -def test_sed_ovfs(vi_container, server): +def test_sed_ovfs(fedora_container, server): + assert fedora_container.id is not None # File Under Test fut = '/container-dir/test.txt' - create_cmd = f"sh -c \"echo 'This is a test' > {fut}\"" - sed_cmd = fr'sed -i -e "s/a test/not \\0/" {fut}' - container_id = vi_container.id[:12] + create_cmd = f'sh -c "echo \'This is a test\' > {fut}"' + sed_cmd = rf'sed -i -e "s/a test/not \\0/" {fut}' + container_id = fedora_container.id[:12] - vi_container.exec_run(create_cmd) - vi_container.exec_run(sed_cmd) + fedora_container.exec_run(create_cmd) + fedora_container.exec_run(sed_cmd) shell = Process.in_container( exe_path='/usr/bin/bash', diff --git a/tests/test_editors/test_vi.py b/tests/test_editors/test_vi.py index 6d1d4434..552dceb9 100644 --- a/tests/test_editors/test_vi.py +++ b/tests/test_editors/test_vi.py @@ -2,7 +2,8 @@ from test_editors.commons import get_vi_test_file -def test_new_file(vi_container, server): +def test_new_file(fedora_container, server): + assert fedora_container.id is not None fut = '/mounted/test.txt' swap_file = '/mounted/.test.txt.swp' swx_file = '/mounted/.test.txt.swx' @@ -10,13 +11,13 @@ def test_new_file(vi_container, server): cmd = f"{exe} {fut} '+:normal iThis is a test' -c x" - vi_container.exec_run(cmd) + fedora_container.exec_run(cmd) process = Process.in_container( exe_path=exe, args=cmd, name='vi', - container_id=vi_container.id[:12], + container_id=fedora_container.id[:12], ) events = [ @@ -39,7 +40,8 @@ def test_new_file(vi_container, server): server.wait_events(events, strict=True) -def test_new_file_ovfs(vi_container, server): +def test_new_file_ovfs(fedora_container, server): + assert fedora_container.id is not None fut = '/container-dir/test.txt' swap_file = '/container-dir/.test.txt.swp' swx_file = '/container-dir/.test.txt.swx' @@ -47,13 +49,13 @@ def test_new_file_ovfs(vi_container, server): cmd = f"{exe} {fut} '+:normal iThis is a test' -c x" - vi_container.exec_run(cmd) + fedora_container.exec_run(cmd) process = Process.in_container( exe_path=exe, args=cmd, name='vi', - container_id=vi_container.id[:12], + container_id=fedora_container.id[:12], ) events = [ @@ -76,20 +78,21 @@ def test_new_file_ovfs(vi_container, server): server.wait_events(events, strict=True) -def test_open_file(vi_container, server): +def test_open_file(fedora_container, server): + assert fedora_container.id is not None fut = '/mounted/test.txt' fut_backup = f'{fut}~' swap_file = '/mounted/.test.txt.swp' swx_file = '/mounted/.test.txt.swx' vi_test_file = get_vi_test_file('/mounted') exe = '/usr/bin/vi' - container_id = vi_container.id[:12] + container_id = fedora_container.id[:12] cmd = f"{exe} {fut} '+:normal iThis is a test' -c x" # We ensure the file exists before editing. - vi_container.exec_run(f'touch {fut}') - vi_container.exec_run(cmd) + fedora_container.exec_run(f'touch {fut}') + fedora_container.exec_run(cmd) touch_process = Process.in_container( exe_path='/usr/bin/touch', @@ -142,20 +145,21 @@ def test_open_file(vi_container, server): server.wait_events(events, strict=True) -def test_open_file_ovfs(vi_container, server): +def test_open_file_ovfs(fedora_container, server): + assert fedora_container.id is not None fut = '/container-dir/test.txt' fut_backup = f'{fut}~' swap_file = '/container-dir/.test.txt.swp' swx_file = '/container-dir/.test.txt.swx' vi_test_file = get_vi_test_file('/container-dir') exe = '/usr/bin/vi' - container_id = vi_container.id[:12] + container_id = fedora_container.id[:12] cmd = f"{exe} {fut} '+:normal iThis is a test' -c x" # We ensure the file exists before editing. - vi_container.exec_run(f'touch {fut}') - vi_container.exec_run(cmd) + fedora_container.exec_run(f'touch {fut}') + fedora_container.exec_run(cmd) touch_process = Process.in_container( exe_path='/usr/bin/touch', diff --git a/tests/test_misc.py b/tests/test_misc.py index a18814a4..815e46e1 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1,19 +1,21 @@ import os from conftest import dump_logs +from containers import pull_or_build from event import Event, EventType, Process import pytest -@pytest.fixture -def build_self_deleter(docker_client): - image, _ = docker_client.images.build( +@pytest.fixture(scope='session') +def build_self_deleter(request: pytest.FixtureRequest, docker_client): + no_local_builds = bool(request.config.getoption('--no-local-builds')) + return pull_or_build( + docker_client, + tag='fact-self-deleter', path='containers/self-deleter', - tag='self-deleter:latest', - dockerfile='Containerfile' + no_local_builds=no_local_builds, ) - return image @pytest.fixture From 6923b26142431ef520a5ac13306c94e6c856fab9 Mon Sep 17 00:00:00 2001 From: ovalenti Date: Fri, 23 Oct 2026 10:45:12 +0200 Subject: [PATCH 2/3] ROX-37670: test container rebuilds happen during nightlies (#2209) * Never rebuild test containers on scheduled builds * Bump test container version * Use debian:trixie as the base distro for 'editors' * Use centos images + pin image versions and packages used This leads to a closer and (hopefully) more stable version of the existing fedora images we were previously using. --------- Co-authored-by: Mauro Ezequiel Moltrasio --- .github/workflows/test-containers-build.yml | 5 ++- tests/containers/QA_TAG | 2 +- tests/containers/base/Containerfile | 2 + tests/containers/{fedora => base}/Makefile | 4 +- tests/containers/editors/Containerfile | 12 ++++-- tests/containers/fedora/Containerfile | 1 - tests/test_editors/commons.py | 12 +++--- tests/test_editors/test_nvim.py | 10 +++-- tests/test_editors/test_sed.py | 20 ++++----- tests/test_editors/test_vi.py | 46 +++++++++++---------- tests/test_editors/test_vim.py | 10 +++-- 11 files changed, 68 insertions(+), 56 deletions(-) create mode 100644 tests/containers/base/Containerfile rename tests/containers/{fedora => base}/Makefile (71%) delete mode 100644 tests/containers/fedora/Containerfile diff --git a/.github/workflows/test-containers-build.yml b/.github/workflows/test-containers-build.yml index 1ae27a3a..87aa5a4b 100644 --- a/.github/workflows/test-containers-build.yml +++ b/.github/workflows/test-containers-build.yml @@ -11,8 +11,9 @@ jobs: name: Determine if tests image needs to be built runs-on: ubuntu-24.04 outputs: - build-image: ${{ (steps.changed.outputs.qa-tag == 'true' || - contains(github.event.pull_request.labels.*.name, 'rebuild-test-container')) }} + build-image: ${{ (github.event_name != 'schedule' && + (steps.changed.outputs.qa-tag == 'true' || + contains(github.event.pull_request.labels.*.name, 'rebuild-test-container'))) }} steps: - uses: actions/checkout@v7 diff --git a/tests/containers/QA_TAG b/tests/containers/QA_TAG index 6e8bf73a..0ea3a944 100644 --- a/tests/containers/QA_TAG +++ b/tests/containers/QA_TAG @@ -1 +1 @@ -0.1.0 +0.2.0 diff --git a/tests/containers/base/Containerfile b/tests/containers/base/Containerfile new file mode 100644 index 00000000..102da78d --- /dev/null +++ b/tests/containers/base/Containerfile @@ -0,0 +1,2 @@ +# CentOS Stream 10 +FROM quay.io/centos/centos@sha256:6f8d618844ed5af4365bad24bea19fb0435fd5ec808201af9aeb19d7a28eb515 diff --git a/tests/containers/fedora/Makefile b/tests/containers/base/Makefile similarity index 71% rename from tests/containers/fedora/Makefile rename to tests/containers/base/Makefile index c3eabd41..f29828ed 100644 --- a/tests/containers/fedora/Makefile +++ b/tests/containers/base/Makefile @@ -1,12 +1,12 @@ include ../constants.mk -FACT_QA_FEDORA_TAG := fact-fedora-$(FACT_QA_TAG) +FACT_QA_BASE_TAG := fact-base-$(FACT_QA_TAG) all: build run: $(DOCKER) buildx build $(BUILD_ARGS) --platform $(PLATFORM) \ - -t quay.io/rhacs-eng/qa-multi-arch:$(FACT_QA_FEDORA_TAG) \ + -t quay.io/rhacs-eng/qa-multi-arch:$(FACT_QA_BASE_TAG) \ -f Containerfile \ $(CURDIR) diff --git a/tests/containers/editors/Containerfile b/tests/containers/editors/Containerfile index 3357f249..0c8529e1 100644 --- a/tests/containers/editors/Containerfile +++ b/tests/containers/editors/Containerfile @@ -1,5 +1,9 @@ -FROM quay.io/fedora/fedora:44 +# CentOS Stream 10 +FROM quay.io/centos/centos@sha256:6f8d618844ed5af4365bad24bea19fb0435fd5ec808201af9aeb19d7a28eb515 -RUN dnf install -y \ - neovim \ - vim +RUN dnf config-manager --set-enabled crb && \ + dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm && \ + dnf install -y \ + neovim-0.10.1-4.el10_0 \ + vim-enhanced-2:9.1.083-25.el10 && \ + dnf clean all diff --git a/tests/containers/fedora/Containerfile b/tests/containers/fedora/Containerfile deleted file mode 100644 index 3ebbf5b2..00000000 --- a/tests/containers/fedora/Containerfile +++ /dev/null @@ -1 +0,0 @@ -FROM quay.io/fedora/fedora:44 diff --git a/tests/test_editors/commons.py b/tests/test_editors/commons.py index 0e6895fc..4639b5df 100644 --- a/tests/test_editors/commons.py +++ b/tests/test_editors/commons.py @@ -46,25 +46,25 @@ def run_editor_container(image, docker_client, ignored_dir): @pytest.fixture(scope='session') -def build_fedora_image( +def build_base_image( request: pytest.FixtureRequest, docker_client: docker.DockerClient ) -> docker.models.images.Image: no_local_builds = bool(request.config.getoption('--no-local-builds')) return pull_or_build( docker_client, - tag='fact-fedora', - path='containers/fedora', + tag='fact-base', + path='containers/base', no_local_builds=no_local_builds, ) @pytest.fixture -def fedora_container( +def base_container( docker_client: docker.DockerClient, - build_fedora_image: docker.models.images.Image, + build_base_image: docker.models.images.Image, ignored_dir: str, ): - image = build_fedora_image.tags[0] + image = build_base_image.tags[0] yield from run_editor_container( image, docker_client, diff --git a/tests/test_editors/test_nvim.py b/tests/test_editors/test_nvim.py index 51ffc26b..1f265958 100644 --- a/tests/test_editors/test_nvim.py +++ b/tests/test_editors/test_nvim.py @@ -34,8 +34,9 @@ def test_open_file(editor_container, server): editor_container.exec_run(cmd) touch = Process.in_container( - exe_path='/usr/bin/touch', - args=f'touch {fut}', + exe_path='/usr/bin/coreutils', + args="/usr/bin/coreutils '--coreutils-prog-shebang=touch' " + + f'/usr/bin/touch {fut}', name='touch', container_id=container_id, ) @@ -101,8 +102,9 @@ def test_open_file_ovfs(editor_container, server): editor_container.exec_run(cmd) touch = Process.in_container( - exe_path='/usr/bin/touch', - args=f'touch {fut}', + exe_path='/usr/bin/coreutils', + args="/usr/bin/coreutils '--coreutils-prog-shebang=touch' " + + f'/usr/bin/touch {fut}', name='touch', container_id=container_id, ) diff --git a/tests/test_editors/test_sed.py b/tests/test_editors/test_sed.py index 1ba75add..c45aca6d 100644 --- a/tests/test_editors/test_sed.py +++ b/tests/test_editors/test_sed.py @@ -2,16 +2,16 @@ from event import Event, EventType, Process -def test_sed(fedora_container, server): - assert fedora_container.id is not None +def test_sed(base_container, server): + assert base_container.id is not None # File Under Test fut = '/mounted/test.txt' create_cmd = f'sh -c "echo \'This is a test\' > {fut}"' sed_cmd = rf'sed -i -e "s/a test/not \\0/" {fut}' - container_id = fedora_container.id[:12] + container_id = base_container.id[:12] - fedora_container.exec_run(create_cmd) - fedora_container.exec_run(sed_cmd) + base_container.exec_run(create_cmd) + base_container.exec_run(sed_cmd) shell = Process.in_container( exe_path='/usr/bin/bash', @@ -42,16 +42,16 @@ def test_sed(fedora_container, server): server.wait_events(events, strict=True) -def test_sed_ovfs(fedora_container, server): - assert fedora_container.id is not None +def test_sed_ovfs(base_container, server): + assert base_container.id is not None # File Under Test fut = '/container-dir/test.txt' create_cmd = f'sh -c "echo \'This is a test\' > {fut}"' sed_cmd = rf'sed -i -e "s/a test/not \\0/" {fut}' - container_id = fedora_container.id[:12] + container_id = base_container.id[:12] - fedora_container.exec_run(create_cmd) - fedora_container.exec_run(sed_cmd) + base_container.exec_run(create_cmd) + base_container.exec_run(sed_cmd) shell = Process.in_container( exe_path='/usr/bin/bash', diff --git a/tests/test_editors/test_vi.py b/tests/test_editors/test_vi.py index 552dceb9..10b6002b 100644 --- a/tests/test_editors/test_vi.py +++ b/tests/test_editors/test_vi.py @@ -2,8 +2,8 @@ from test_editors.commons import get_vi_test_file -def test_new_file(fedora_container, server): - assert fedora_container.id is not None +def test_new_file(base_container, server): + assert base_container.id is not None fut = '/mounted/test.txt' swap_file = '/mounted/.test.txt.swp' swx_file = '/mounted/.test.txt.swx' @@ -11,13 +11,13 @@ def test_new_file(fedora_container, server): cmd = f"{exe} {fut} '+:normal iThis is a test' -c x" - fedora_container.exec_run(cmd) + base_container.exec_run(cmd) process = Process.in_container( exe_path=exe, args=cmd, name='vi', - container_id=fedora_container.id[:12], + container_id=base_container.id[:12], ) events = [ @@ -40,8 +40,8 @@ def test_new_file(fedora_container, server): server.wait_events(events, strict=True) -def test_new_file_ovfs(fedora_container, server): - assert fedora_container.id is not None +def test_new_file_ovfs(base_container, server): + assert base_container.id is not None fut = '/container-dir/test.txt' swap_file = '/container-dir/.test.txt.swp' swx_file = '/container-dir/.test.txt.swx' @@ -49,13 +49,13 @@ def test_new_file_ovfs(fedora_container, server): cmd = f"{exe} {fut} '+:normal iThis is a test' -c x" - fedora_container.exec_run(cmd) + base_container.exec_run(cmd) process = Process.in_container( exe_path=exe, args=cmd, name='vi', - container_id=fedora_container.id[:12], + container_id=base_container.id[:12], ) events = [ @@ -78,25 +78,26 @@ def test_new_file_ovfs(fedora_container, server): server.wait_events(events, strict=True) -def test_open_file(fedora_container, server): - assert fedora_container.id is not None +def test_open_file(base_container, server): + assert base_container.id is not None fut = '/mounted/test.txt' fut_backup = f'{fut}~' swap_file = '/mounted/.test.txt.swp' swx_file = '/mounted/.test.txt.swx' vi_test_file = get_vi_test_file('/mounted') exe = '/usr/bin/vi' - container_id = fedora_container.id[:12] + container_id = base_container.id[:12] cmd = f"{exe} {fut} '+:normal iThis is a test' -c x" # We ensure the file exists before editing. - fedora_container.exec_run(f'touch {fut}') - fedora_container.exec_run(cmd) + base_container.exec_run(f'touch {fut}') + base_container.exec_run(cmd) touch_process = Process.in_container( - exe_path='/usr/bin/touch', - args=f'touch {fut}', + exe_path='/usr/bin/coreutils', + args="/usr/bin/coreutils '--coreutils-prog-shebang=touch' " + + f'/usr/bin/touch {fut}', name='touch', container_id=container_id, ) @@ -145,25 +146,26 @@ def test_open_file(fedora_container, server): server.wait_events(events, strict=True) -def test_open_file_ovfs(fedora_container, server): - assert fedora_container.id is not None +def test_open_file_ovfs(base_container, server): + assert base_container.id is not None fut = '/container-dir/test.txt' fut_backup = f'{fut}~' swap_file = '/container-dir/.test.txt.swp' swx_file = '/container-dir/.test.txt.swx' vi_test_file = get_vi_test_file('/container-dir') exe = '/usr/bin/vi' - container_id = fedora_container.id[:12] + container_id = base_container.id[:12] cmd = f"{exe} {fut} '+:normal iThis is a test' -c x" # We ensure the file exists before editing. - fedora_container.exec_run(f'touch {fut}') - fedora_container.exec_run(cmd) + base_container.exec_run(f'touch {fut}') + base_container.exec_run(cmd) touch_process = Process.in_container( - exe_path='/usr/bin/touch', - args=f'touch {fut}', + exe_path='/usr/bin/coreutils', + args="/usr/bin/coreutils '--coreutils-prog-shebang=touch' " + + f'/usr/bin/touch {fut}', name='touch', container_id=container_id, ) diff --git a/tests/test_editors/test_vim.py b/tests/test_editors/test_vim.py index 20505b54..a315daef 100644 --- a/tests/test_editors/test_vim.py +++ b/tests/test_editors/test_vim.py @@ -87,8 +87,9 @@ def test_open_file(editor_container, server): editor_container.exec_run(cmd) touch_process = Process.in_container( - exe_path='/usr/bin/touch', - args=f'touch {fut}', + exe_path='/usr/bin/coreutils', + args="/usr/bin/coreutils '--coreutils-prog-shebang=touch' " + + f'/usr/bin/touch {fut}', name='touch', container_id=container_id, ) @@ -152,8 +153,9 @@ def test_open_file_ovfs(editor_container, server): editor_container.exec_run(cmd) touch_process = Process.in_container( - exe_path='/usr/bin/touch', - args=f'touch {fut}', + exe_path='/usr/bin/coreutils', + args="/usr/bin/coreutils '--coreutils-prog-shebang=touch' " + + f'/usr/bin/touch {fut}', name='touch', container_id=container_id, ) From aec534d6a4ba5269842c2031ce95584d0ff6911a Mon Sep 17 00:00:00 2001 From: Mauro Ezequiel Moltrasio Date: Mon, 26 Oct 2026 11:43:57 +0100 Subject: [PATCH 3/3] chore: validate test images don't exist on remote before building (#2214) * chore: validate test images don't exist on remote before building This is a follow up to #2209, we need to backport those changes and we need to ensure release branches will not accidentally overwrite images built in the main branch. A new `exists` make target is added to all test containers and a global check ensures at least one of the images is missing before triggering the rebuild. On normal operation, new versions of the images will be built by the main branch and be available once the QA_TAG bump is backported to release branches. In this case the paths-filter action will determine a rebuild is needed, but the new check will skip building the images. If the images are in the process of being built on the main branch and some of them are not yet pushed, the backported commit will push the images again but they will have been built in such a small span of time the resulting images will pretty much be the same. On PRs, if the containers need to be rebuilt a unique tag is generated and the step will always return the images don't exist. * Global FACT_QA_TAG and portable redirections * Don't conflate network/registry errors with manifest does not exist errors --- .github/workflows/test-containers-build.yml | 43 ++++++++++++++++++++- tests/containers/Makefile | 2 +- tests/containers/base/Makefile | 5 ++- tests/containers/editors/Makefile | 5 ++- tests/containers/self-deleter/Makefile | 5 ++- 5 files changed, 55 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-containers-build.yml b/.github/workflows/test-containers-build.yml index 87aa5a4b..e5ebb128 100644 --- a/.github/workflows/test-containers-build.yml +++ b/.github/workflows/test-containers-build.yml @@ -60,6 +60,8 @@ jobs: - should-build-test-containers if: needs.should-build-test-containers.outputs.build-image == 'true' || contains(github.event.pull_request.labels.*.name, 'rebuild-test-container') + env: + FACT_QA_TAG: ${{ needs.determine-qa-tag.outputs.qa-tag }} steps: - uses: actions/checkout@v7 @@ -77,9 +79,48 @@ jobs: username: ${{ secrets.QUAY_RHACS_ENG_RW_USERNAME }} password: ${{ secrets.QUAY_RHACS_ENG_RW_PASSWORD }} + - name: Check images already exist + id: images-exist + run: | + manifest_exists() { + local image=$1 output + if output=$(docker manifest inspect "${image}" 2>&1); then + return 0 + elif echo "${output}" | grep -Eiq 'no such manifest:|manifest unknown'; then + return 1 + else + printf '%s: %s\n' "${image}" "${output}" >&2 + return 2 + fi + } + + image_names=$(make --no-print-directory -s \ + -C "${GITHUB_WORKSPACE}/tests/containers" image-names) || exit $? + + EXIST=true + while IFS= read -r image_name; do + [[ -z "${image_name}" ]] && continue + if manifest_exists "${image_name}"; then + continue + else + status=$? + fi + + if [[ "${status}" -eq 1 ]]; then + EXIST=false + else + exit "${status}" + fi + done <<< "${image_names}" + + if [[ "${EXIST}" == false ]]; then + echo "::notice::At least one test container manifest is absent" + fi + echo "exist=${EXIST}" >> "${GITHUB_OUTPUT}" + - name: Build and Push containers + if: steps.images-exist.outputs.exist == 'false' || contains(github.event.pull_request.labels.*.name, 'rebuild-test-container') env: - FACT_QA_TAG: ${{ needs.determine-qa-tag.outputs.qa-tag }} PLATFORM: linux/amd64,linux/arm64 run: | make -C "${GITHUB_WORKSPACE}/tests/containers" build-and-push diff --git a/tests/containers/Makefile b/tests/containers/Makefile index 4eb28d59..3998bc20 100644 --- a/tests/containers/Makefile +++ b/tests/containers/Makefile @@ -1,6 +1,6 @@ include constants.mk -TARGETS := build build-and-push +TARGETS := build build-and-push image-names DIRS := $(wildcard ./*/) $(TARGETS): $(DIRS) diff --git a/tests/containers/base/Makefile b/tests/containers/base/Makefile index f29828ed..9065f26a 100644 --- a/tests/containers/base/Makefile +++ b/tests/containers/base/Makefile @@ -16,4 +16,7 @@ build: run build-and-push: BUILD_ARGS = --push build-and-push: run -.PHONY: build build-and-push all run +image-names: + @echo quay.io/rhacs-eng/qa-multi-arch:$(FACT_QA_BASE_TAG) + +.PHONY: build build-and-push all run image-names diff --git a/tests/containers/editors/Makefile b/tests/containers/editors/Makefile index df1f655e..384d30d6 100644 --- a/tests/containers/editors/Makefile +++ b/tests/containers/editors/Makefile @@ -16,4 +16,7 @@ build: run build-and-push: BUILD_ARGS = --push build-and-push: run -.PHONY: build build-and-push all run +image-names: + @echo quay.io/rhacs-eng/qa-multi-arch:$(FACT_QA_EDITORS_TAG) + +.PHONY: build build-and-push all run image-names diff --git a/tests/containers/self-deleter/Makefile b/tests/containers/self-deleter/Makefile index 74ed5009..0a0fd6c7 100644 --- a/tests/containers/self-deleter/Makefile +++ b/tests/containers/self-deleter/Makefile @@ -16,4 +16,7 @@ build: run build-and-push: BUILD_ARGS = --push build-and-push: run -.PHONY: build build-and-push all run +image-names: + @echo quay.io/rhacs-eng/qa-multi-arch:$(FACT_QA_SELF_DELETER_TAG) + +.PHONY: build build-and-push all run image-names