diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index f32e77f8..1f85c944 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -27,6 +27,9 @@ jobs: vm: - fedora-coreos - fcarm + - rhel + - rhel-arm64 + - rhcos steps: - uses: actions/checkout@v4 @@ -80,11 +83,25 @@ jobs: quay: username: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} password: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} + excluded_vms: + # RHEL 8 doesn't handle file creation properly, + # need more investigation + - rhel-8 + - rhcos-412-86-202402272018-0-gcp-x86-64 + - rhcos-414-92-202407091253-0-gcp-x86-64 + # BPF trampolines are only implemented starting with RHEL 10 + - rhel-9-arm64 EOF - name: Create Test VMs + env: + ANSIBLE_CONFIG: "${{ github.workspace }}/collector/ansible/ansible.cfg" run: | - make -C "./collector/ansible" create-ci-vms + ansible-playbook \ + -i "${GITHUB_WORKSPACE}/collector/ansible/ci" \ + -e @vars.yml \ + --tags setup,provision \ + "${GITHUB_WORKSPACE}/collector/ansible/integration-tests.yml" - name: Run the tests env: @@ -104,10 +121,10 @@ jobs: if: always() run: | cd "${GITHUB_WORKSPACE}/fact/tests" - if [[ -f "logs.tar.gz" ]]; then - tar xzf "logs.tar.gz" - rm -f "logs.tar.gz" - fi + for file in logs/*.tar.gz; do + tar xzf "$file" + rm -f "$file" + done - name: Test summary uses: test-summary/action@v2 diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml index cfc86685..746de296 100644 --- a/ansible/group_vars/all.yml +++ b/ansible/group_vars/all.yml @@ -1,2 +1,3 @@ --- runtime_command: docker +runtime_host: 'unix:///var/run/docker.sock' diff --git a/ansible/group_vars/platform_rhcos.yml b/ansible/group_vars/platform_rhcos.yml index 792d9227..fd7510c0 100644 --- a/ansible/group_vars/platform_rhcos.yml +++ b/ansible/group_vars/platform_rhcos.yml @@ -1,2 +1,3 @@ --- ansible_user: core +runtime_host: "unix:///run/podman/podman.sock" diff --git a/ansible/group_vars/platform_rhcos_arm64.yml b/ansible/group_vars/platform_rhcos_arm64.yml index 792d9227..fd7510c0 100644 --- a/ansible/group_vars/platform_rhcos_arm64.yml +++ b/ansible/group_vars/platform_rhcos_arm64.yml @@ -1,2 +1,3 @@ --- ansible_user: core +runtime_host: "unix:///run/podman/podman.sock" diff --git a/ansible/group_vars/platform_rhel.yml b/ansible/group_vars/platform_rhel.yml new file mode 100644 index 00000000..945e3538 --- /dev/null +++ b/ansible/group_vars/platform_rhel.yml @@ -0,0 +1,2 @@ +--- +runtime_host: "unix:///run/podman/podman.sock" diff --git a/ansible/group_vars/platform_rhel_arm64.yml b/ansible/group_vars/platform_rhel_arm64.yml new file mode 100644 index 00000000..945e3538 --- /dev/null +++ b/ansible/group_vars/platform_rhel_arm64.yml @@ -0,0 +1,2 @@ +--- +runtime_host: "unix:///run/podman/podman.sock" diff --git a/ansible/run-tests.yml b/ansible/run-tests.yml index 20578442..91115519 100644 --- a/ansible/run-tests.yml +++ b/ansible/run-tests.yml @@ -5,57 +5,88 @@ FACT_IMAGE_NAME: "{{ fact.image | default(None) }}" tasks: - - name: Install dependencies - become: true - community.general.rpm_ostree_pkg: - apply_live: true - name: - - make - - python3-packaging - - python3-requests - state: present - - - name: Login to quay.io - community.docker.docker_login: - registry_url: quay.io - username: "{{ quay.username }}" - password: "{{ quay.password }}" - - name: Clone the repo ansible.builtin.git: repo: https://github.com/stackrox/fact - dest: ./fact + dest: /tmp/fact version: "{{ fact.version }}" update: false - - name: Install python packages - ansible.builtin.pip: - requirements: ./fact/tests/requirements.txt - chdir: "{{ ansible_env.HOME }}" - virtualenv: ./fact/.venv - virtualenv_command: python3 -m venv + - name: Log into quay.io + become: "{{ runtime_command == 'podman' }}" + shell: + cmd: "{{ runtime_command }} login -u {{ quay.username }} --password-stdin quay.io" + stdin: "{{ quay.password }}" + + - name: Copy podman auth + become: true + shell: + cmd: | + mkdir -p ~/.docker/ + if [[ -f "${XDG_RUNTIME_DIR:-}/containers/auth.json" ]]; then + AUTH_FILE="${XDG_RUNTIME_DIR:-}/containers/auth.json" + elif [[ -f "/run/containers/0/auth.json" ]]; then + AUTH_FILE="/run/containers/0/auth.json" + else + echo &>2 "No valid auth.json file found" + exit 1 + fi + cp "${AUTH_FILE}" ~/.docker/config.json + creates: ~/.docker/config.json + when: runtime_command == "podman" - block: + # There are some ansible modules that we could use to modularize + # this next task, however they required some Python modules to be + # installed on the managed VM that may not be available (like + # python3-packaging), so we stick to a shell as ugly as it is. - name: Run tests + become: true + environment: + DOCKER_HOST: "{{ runtime_host }}" ansible.builtin.shell: cmd: | - cd "${HOME}/fact" - source ".venv/bin/activate" - make integration-tests + set -euo pipefail + cd "/tmp/fact/tests" + + # Setup the virtual environment + python3 -m venv .venv + source .venv/bin/activate + pip install -r requirements.txt + + # Generate gRPC files + python3 -m grpc_tools.protoc \ + -I../third_party/stackrox/proto \ + --python_out=. \ + --pyi_out=. \ + --grpc_python_out=. \ + ../third_party/stackrox/proto/internalapi/sensor/collector.proto \ + ../third_party/stackrox/proto/internalapi/sensor/sfa.proto \ + ../third_party/stackrox/proto/internalapi/sensor/sfa_iservice.proto + + # Run the tests + pytest --image="${FACT_IMAGE_NAME}" --junit-xml=results.xml + always: + - name: Make logs directories + file: + state: directory + path: "../tests/logs" + delegate_to: localhost + - name: Retrieve results ansible.builtin.fetch: - src: "{{ ansible_env.HOME }}/fact/tests/results.xml" + src: "/tmp/fact/tests/results.xml" dest: ../tests/ flat: true - name: Compress log files community.general.archive: - path: "{{ ansible_env.HOME }}/fact/tests/logs" - dest: "{{ ansible_env.HOME }}/fact/tests/logs.tar.gz" + path: "/tmp/fact/tests/logs" + dest: "/tmp/fact/tests/{{ vm_config }}.tar.gz" - name: Fetch log files ansible.builtin.fetch: - src: "{{ ansible_env.HOME }}/fact/tests/logs.tar.gz" - dest: ../tests/logs.tar.gz + src: "/tmp/fact/tests/logs.tar.gz" + dest: ../tests/logs flat: true diff --git a/tests/event.py b/tests/event.py index 11bca769..811e19a4 100644 --- a/tests/event.py +++ b/tests/event.py @@ -1,8 +1,18 @@ +from __future__ import annotations + import os import string from enum import Enum from re import Pattern -from typing import Any, override +from typing import Any + +try: + from typing import override # type: ignore[reportAssignmentType] +except ImportError: + + def override(func): # type: ignore[reportMissingParameterType] + return func + import utils from internalapi.sensor.collector_pb2 import ProcessSignal diff --git a/tests/test_config_hotreload.py b/tests/test_config_hotreload.py index 996f4b0f..f9aa3d91 100644 --- a/tests/test_config_hotreload.py +++ b/tests/test_config_hotreload.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +from concurrent.futures import TimeoutError as FuturesTimeoutError from time import sleep import docker.models.containers @@ -219,7 +220,7 @@ def test_no_paths_then_add( e = Event(process=p, event_type=EventType.OPEN, file=fut, host_path=fut) - with pytest.raises(TimeoutError): + with pytest.raises((TimeoutError, FuturesTimeoutError)): server.wait_events([e]) # Add paths back @@ -264,7 +265,7 @@ def test_paths_then_remove( f.write('This should be ignored') sleep(1) - with pytest.raises(TimeoutError): + with pytest.raises((TimeoutError, FuturesTimeoutError)): server.wait_events([e]) diff --git a/tests/test_path_rename.py b/tests/test_path_rename.py index 21fd263f..5445c76e 100644 --- a/tests/test_path_rename.py +++ b/tests/test_path_rename.py @@ -451,20 +451,27 @@ def test_cross_mountpoints( container_id=test_container.id[:12], ) - server.wait_events( - [ - Event( - process=touch, - event_type=EventType.OPEN, - file=mounted_file, - host_path=host_path, - ), - Event( - process=first_rename, - event_type=EventType.CREATION, - file=ovfs_file, - host_path='', - ), + events = [ + Event( + process=touch, + event_type=EventType.OPEN, + file=mounted_file, + host_path=host_path, + ), + Event( + process=first_rename, + event_type=EventType.CREATION, + file=ovfs_file, + host_path='', + ), + ] + + # If the id for the current process and the process in the container (root) + # match, the ownership events are skipped + curr_id = (os.getuid(), os.getgid()) + root_id = (0, 0) + if curr_id != root_id: + events.append( Event( process=first_rename, event_type=EventType.OWNERSHIP, @@ -472,7 +479,11 @@ def test_cross_mountpoints( host_path='', owner_uid=owner_uid, owner_gid=owner_gid, - ), + ) + ) + + events.extend( + [ Event( process=first_rename, event_type=EventType.PERMISSION, @@ -492,6 +503,11 @@ def test_cross_mountpoints( file=mounted_file, host_path=host_path, ), + ] + ) + + if curr_id != root_id: + events.append( Event( process=second_rename, event_type=EventType.OWNERSHIP, @@ -499,7 +515,11 @@ def test_cross_mountpoints( host_path=host_path, owner_uid=owner_uid, owner_gid=owner_gid, - ), + ) + ) + + events.extend( + [ Event( process=second_rename, event_type=EventType.PERMISSION, @@ -513,5 +533,7 @@ def test_cross_mountpoints( file=ovfs_file, host_path='', ), - ], + ] ) + + server.wait_events(events) diff --git a/tests/test_path_rmdir.py b/tests/test_path_rmdir.py index 669699d4..0dba5780 100644 --- a/tests/test_path_rmdir.py +++ b/tests/test_path_rmdir.py @@ -2,6 +2,7 @@ import os import shutil +import sys import pytest @@ -154,6 +155,10 @@ def test_rmdir_empty( ) +@pytest.mark.skipif( + sys.version_info < (3, 10), + reason='shutil.rmtree behavior changes between interpreter versions', +) def test_rmdir_recursive( monitored_dir: str, server: FileActivityService,