Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate config map and fix bad Fedora IoT test config #1156

Merged
merged 9 commits into from
Jan 22, 2025
7 changes: 6 additions & 1 deletion .github/workflows/gitlab-helper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ on: # yamllint disable-line rule:truthy

jobs:
prepare:
name: "🔍 Check source preparation"
name: "🔍 Check source preparation and test configs"
runs-on: ubuntu-latest
steps:

Expand Down Expand Up @@ -46,6 +46,11 @@ jobs:
exit "0"
fi
- name: Check that the config-map is valid
run: |
./test/scripts/validate-config-map
gitlab-ci-helper:
name: "Gitlab CI trigger helper"
runs-on: ubuntu-latest
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,21 +160,21 @@ jobs:
SHELLCHECK_OPTS: -e SC1091 -e SC2002 -e SC2317

python-test:
name: "🐍 pytest (imgtestlib)"
name: "🐍 pytest (imgtestlib and test scripts)"
runs-on: ubuntu-latest
container:
image: registry.fedoraproject.org/fedora:latest
steps:

- name: Install build and test dependencies
run: dnf -y install python3-pytest podman skopeo
run: dnf -y install python3-pytest podman skopeo go btrfs-progs-devel device-mapper-devel gpgme-devel

- name: Check out code into the Go module directory
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}

- name: Testing imgtestlib
- name: Testing imgtestlib and test scripts
run: |
python3 -m pytest -v
Expand Down
2 changes: 1 addition & 1 deletion test/config-map.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@
},
"./configs/iot-ostree-pull-user.json": {
"image-types": [
"iot-ami"
"iot-qcow2-image"
]
},
"./configs/kernel-debug.json": {
Expand Down
10 changes: 9 additions & 1 deletion test/configs/edge-ostree-pull-user.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@
"wheel"
],
"key": "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNebAh6SjpAn8wB53K4695cGnHGuCtl4RdaX3futZgJUultHyzeYHnzMO7d4++qnRL+Rworew62LKP560uvtncc= github.com/osbuild/images",
"name": "osbuild"
"name": "osbuild",
"uid": 1001,
"gid": 1002
}
],
"group": [
{
"name": "osbuild",
"gid": 1002
}
]
}
Expand Down
190 changes: 190 additions & 0 deletions test/scripts/test_validate_config_map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import importlib
from types import ModuleType

import pytest


def import_validator() -> ModuleType:
name = "validate_config_map"
path = "test/scripts/validate-config-map"
loader = importlib.machinery.SourceFileLoader(name, path)
spec = importlib.util.spec_from_loader(loader.name, loader)
if spec is None:
raise ImportError(f"cannot import {name} from {path}, got None as the spec")
mod = importlib.util.module_from_spec(spec)
loader.exec_module(mod)
return mod


def write_configs(files, root):
for config_file in files:
config_path = root / config_file
config_path.parent.mkdir(exist_ok=True)
config_path.touch()


@pytest.mark.parametrize("config_map,files,missing_files,invalid_cfgs", (
# valid
(
{
"everything.json": {
"distros": [
"fedora*",
],
},
"empty.json": {
"image-types": ["qcow2"],
},
},
["everything.json", "empty.json"],
[],
[],
),
(
{
"configs/cfg-1.json": {},
"configs/cfg-2.json": {
"distros": ["centos*"],
"arches": ["s390x"],
"image-types": ["qcow2"],
},
},
["configs/cfg-1.json", "configs/cfg-2.json"],
[],
[],
),
(
{
"configs/cfg-3.json": {
"distros": ["fedora*"],
},
"configs/cfg-4.json": {
"image-types": ["qcow2"],
},
},
["configs/cfg-3.json", "configs/cfg-4.json"],
[],
[],
),

# missing files
(
{
"everything.json": {
"distros": [
"fedora*",
],
},
"empty.json": {
"image-types": ["qcow2"],
},
},
["everything.json"],
["empty.json"],
[],
),
(
{
"configs/cfg-1.json": {},
"configs/cfg-2.json": {
"distros": ["centos*"],
"arches": ["s390x"],
"image-types": ["qcow2"],
},
},
[],
["configs/cfg-1.json", "configs/cfg-2.json"],
[],
),
(
{
"configs/cfg-3.json": {
"distros": ["fedora*"],
},
"configs/cfg-4.json": {
"image-types": ["qcow2"],
},
},
["configs/cfg-4.json"],
["configs/cfg-3.json"],
[],
),

# bad config
(
{
"everything.json": {
"distros": [
"fedora*",
],
},
"empty.json": {
"image-types": ["not-qcow2"],
},
},
["everything.json", "empty.json"],
[],
[
(
"empty.json",
{
"image-types": ["not-qcow2"],
},
)
],
),
(
{
"configs/cfg-1.json": {},
"configs/cfg-2.json": {
"distros": ["centos*"],
"arches": ["noarch"],
"image-types": ["qcow2"],
},
},
["configs/cfg-1.json", "configs/cfg-2.json"],
[],
[
(
"configs/cfg-2.json",
{
"distros": ["centos*"],
"arches": ["noarch"],
"image-types": ["qcow2"],
},
)
],
),
(
{
"configs/cfg-3.json": {
"distros": ["archlinux"],
},
"configs/cfg-4.json": {
"distros": ["ubuntu*"],
},
},
["configs/cfg-3.json", "configs/cfg-4.json"],
[],
[
(
"configs/cfg-3.json",
{
"distros": ["archlinux"],
},
),
(
"configs/cfg-4.json",
{
"distros": ["ubuntu*"],
},
),
],
),
))
def test_valid_config_map(config_map, files, missing_files, invalid_cfgs, tmp_path):
validator = import_validator()
write_configs(files, tmp_path)

assert validator.validate_config_file_paths(config_map, tmp_path) == [tmp_path / mf for mf in missing_files]
assert validator.validate_build_config(config_map) == invalid_cfgs
98 changes: 98 additions & 0 deletions test/scripts/validate-config-map
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""
Reads the config map and verifies that: 1. All listed config files exist, and 2. The combination of distros, arches, and
image-types produces at least one valid build configuration for each config file.

The config map is read as test/config-map.json relative to the repository root.
"""
import argparse
import json
import pathlib
import sys

import imgtestlib as testlib


def read_config_map(root):
config_map_path = root / "test/config-map.json"
if not config_map_path.exists():
print(f"config map not found at {config_map_path}", file=sys.stderr)
sys.exit(1)

print(f"Reading config map: {config_map_path}")
with config_map_path.open(encoding="utf-8") as config_map_fp:
return json.load(config_map_fp), config_map_path.parent


def validate_config_file_paths(config_map, config_map_dir):
"""
Validate that all paths used as keys in the config map exist. Paths must be relative to config_map_dir (the parent
directory of the config map).

Returns a list of paths found in the config map that were not found in the directory.
"""
not_found = []
for path in config_map.keys():
config_path = config_map_dir / path
if not config_path.exists():
not_found.append(config_path)

return not_found


def validate_build_config(config_map):
"""
Validate that all build configurations (distros, arches, image types) match at least one valid, known configuration.

Returns a list of 2-tuples, each consisting of the config file path and the build configuration.
"""
no_matches = []
for config, build_config in config_map.items():
distros = build_config.get("distros", ["*"])
arches = build_config.get("arches", ["*"])
image_types = build_config.get("image-types", ["*"])

matches = testlib.list_images(distros=distros, arches=arches, images=image_types)
if not matches:
no_matches.append((config, build_config))

return no_matches


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("path", default=".", nargs="?", help="path to repository root")
args = parser.parse_args()

root = pathlib.Path(args.path)

config_map, config_map_dir = read_config_map(root)

print("Validating config file paths")
not_found = validate_config_file_paths(config_map, config_map_dir)
if not_found:
print(" failed: the following config files were not found:", file=sys.stderr)
for idx, path in enumerate(not_found, start=1):
print(f"{idx}: {path}", file=sys.stderr)
sys.exit(len(not_found))
print("OK: All config files found")

print("Validating build configurations (distros, arches, image types)")
no_matches = validate_build_config(config_map)
if no_matches:
print("failed: the following configs do not match any known build configurations", file=sys.stderr)
for idx, (config, build_config) in enumerate(no_matches, start=1):
distros = ",".join(build_config.get("distros", ["*"]))
arches = ",".join(build_config.get("arches", ["*"]))
image_types = ",".join(build_config.get("image-types", ["*"]))
print(f"{idx} {config}:", file=sys.stderr)
print(f" distros: {distros}", file=sys.stderr)
print(f" arches: {arches}", file=sys.stderr)
print(f" image types: {image_types}", file=sys.stderr)

sys.exit(len(no_matches))
print("OK: All test configs have at least one valid build configuration")


if __name__ == "__main__":
main()
Loading