diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml new file mode 100644 index 0000000..9d42fcd --- /dev/null +++ b/.github/workflows/e2e.yaml @@ -0,0 +1,32 @@ +name: E2E + +on: + push: + branches: + - main + pull_request: + +jobs: + e2e-skopeo: + name: E2E using CNCF Distribution Registry + runs-on: ubuntu-24.04 + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Install Skopeo + run: | + sudo apt-get update && sudo apt-get install skopeo + skopeo --version + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + - name: Install Poetry + run: | + pipx install poetry + - name: Install dependencies + run: | + make install + - name: Run E2E tests + run: | + make test-e2e-skopeo diff --git a/Makefile b/Makefile index 38c9953..8f1475f 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,10 @@ build: install test: poetry run pytest -s -x -rA +.PHONY: test-e2e-skopeo +test-e2e-skopeo: + poetry run pytest --e2e-skopeo -s -x -rA + .PHONY: lint lint: install poetry run ruff check --fix diff --git a/olot/backend/skopeo.py b/olot/backend/skopeo.py new file mode 100644 index 0000000..1e55ac8 --- /dev/null +++ b/olot/backend/skopeo.py @@ -0,0 +1,12 @@ +import os +import shutil +import subprocess +import typing + +def is_skopeo() -> bool : + return shutil.which("skopeo") is not None + +def skopeo_pull(base_image: str, dest: typing.Union[str, os.PathLike]): + if isinstance(dest, os.PathLike): + dest = str(dest) + return subprocess.run(["skopeo", "copy", "--multi-arch", "all", "docker://"+base_image, "oci:"+dest+":latest"], check=True) diff --git a/olot/basics.py b/olot/basics.py index 54386ff..aa28667 100644 --- a/olot/basics.py +++ b/olot/basics.py @@ -163,6 +163,8 @@ def check_ocilayout(ocilayout: Path): m = OCIImageLayout.model_validate_json(f.read()) if not m.imageLayoutVersion == ImageLayoutVersion.field_1_0_0: raise ValueError(f"Unexpected ocilayout in {ocilayout}") + else: + return True def tar_into_ocilayout(ocilayout: Path, model: Path): diff --git a/pyproject.toml b/pyproject.toml index 4e1a8d0..9ddc5a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,10 @@ olot = "olot.cli:cli" [tool.poetry.group.test.dependencies] pytest = "^8.3.4" +[tool.pytest.ini_options] +markers = [ + "e2e_skopeo: end-to-end testing with skopeo", +] [tool.poetry.group.dev.dependencies] datamodel-code-generator = "^0.26.3" diff --git a/tests/backend/test_skopeo.py b/tests/backend/test_skopeo.py new file mode 100644 index 0000000..4c79967 --- /dev/null +++ b/tests/backend/test_skopeo.py @@ -0,0 +1,26 @@ + +import pytest +from olot.backend.skopeo import is_skopeo, skopeo_pull +from olot.basics import check_ocilayout, read_ocilayout_root_index + +@pytest.mark.e2e_skopeo +def test_is_skopeo(): + assert is_skopeo() + + +@pytest.mark.e2e_skopeo +def test_skopeo_pull(tmp_path): + """Test skopeo to pull/dl a known base-image to an oci-layout + """ + skopeo_pull("quay.io/mmortari/hello-world-wait", tmp_path) + + assert check_ocilayout(tmp_path) + + mut = read_ocilayout_root_index(tmp_path) + assert mut.schemaVersion == 2 + assert len(mut.manifests) == 1 + manifest0 = mut.manifests[0] + assert manifest0.mediaType == "application/vnd.oci.image.index.v1+json" + assert manifest0.digest == "sha256:d437889e826ecce2116ac711469bd09b1bb3c64d45055cbf23a6f8f3db223b8b" + assert manifest0.size == 491 + diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..8eea23f --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,31 @@ +import pytest + + +def pytest_collection_modifyitems(config, items): + for item in items: + skip_e2e_skopeo = pytest.mark.skip( + reason="this is an end-to-end test, requires explicit opt-in --e2e-skopeo option to run." + ) + skip_not_e2e = pytest.mark.skip( + reason="skipping non-e2e tests; opt-out of --e2e -like options to run." + ) + if "e2e_skopeo" in item.keywords: + if not config.getoption("--e2e-skopeo"): + item.add_marker(skip_e2e_skopeo) + continue + # elif "e2e_model_registry" in item.keywords: + # if not config.getoption("--e2e-model-registry"): + # item.add_marker(skip_e2e_model_registry) + # continue + + if config.getoption("--e2e-skopeo"): # or config.getoption("--e2e-model-registry"): + item.add_marker(skip_not_e2e) + + +def pytest_addoption(parser): + parser.addoption( + "--e2e-skopeo", + action="store_true", + default=False, + help="opt-in to run tests marked with e2e_skopeo", + )