ci: migrate workflows to Gitea Actions
CI / container-policy (push) Successful in 8s
CI / javascript-check (push) Successful in 18s
CI / python-tests (push) Failing after 4m44s
CI / container-verify (push) Skipped
CI / container-publish (push) Skipped

This commit is contained in:
BartelLuis
2026-09-14 20:23:20 +02:00
parent 06c3474636
commit 9c528c6eca
13 changed files with 472 additions and 303 deletions
+117 -15
View File
@@ -12,7 +12,8 @@ import pytest
ROOT = Path(__file__).resolve().parents[1]
VERSION = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))["project"]["version"]
SHA = "1234567890abcdef1234567890abcdef12345678"
REGISTRY_IMAGE = "ghcr.io/team/proxmox-ais"
REGISTRY_HOST = "gitlab.bartelluis.de"
REGISTRY_IMAGE = f"{REGISTRY_HOST}/team/proxmox-ais"
DIGEST = f"{REGISTRY_IMAGE}@sha256:{'a' * 64}"
PASSWORD = "dummy-ci-token-$()-do-not-print"
@@ -37,6 +38,7 @@ def container_ci(tmp_path):
(tmp_path / "bin").mkdir()
(tmp_path / "tmp").mkdir()
shutil.copyfile(ROOT / "ci/container.sh", tmp_path / "ci/container.sh")
shutil.copyfile(ROOT / "ci/publish-policy.sh", tmp_path / "ci/publish-policy.sh")
shutil.copyfile(ROOT / "pyproject.toml", tmp_path / "pyproject.toml")
smoke_source = (ROOT / "tests/container_smoke.py").read_bytes()
(tmp_path / "tests/container_smoke.py").write_bytes(smoke_source)
@@ -78,14 +80,17 @@ esac
"GITHUB_RUN_ATTEMPT": "1",
"GITHUB_JOB": "container_publish",
"GITHUB_REPOSITORY": "Team/Proxmox-AIS",
"GITHUB_SERVER_URL": "https://github.com",
"GITHUB_SERVER_URL": "https://gitlab.bartelluis.de",
"GITHUB_EVENT_NAME": "push",
"GITHUB_REF_PROTECTED": "true",
"GITHUB_REF_PROTECTED": "false",
"GITHUB_REF_TYPE": "branch",
"GITHUB_REF_NAME": "main",
"DEFAULT_BRANCH": "main",
"PUBLISH_IMAGES": "true",
"GITHUB_ACTOR": "ci-user",
"GHCR_TOKEN": PASSWORD,
"REGISTRY_HOST": REGISTRY_HOST,
"REGISTRY_USERNAME": "registry-owner",
"REGISTRY_TOKEN": PASSWORD,
"MOCK_DOCKER_LOG": (tmp_path / "docker.log").as_posix(),
"MOCK_SMOKE_STDIN": (tmp_path / "smoke.stdin").as_posix(),
"MOCK_LOGIN_STDIN": (tmp_path / "login.stdin").as_posix(),
@@ -123,7 +128,7 @@ def test_default_branch_publishes_only_after_hardened_image_smoke(container_ci):
assert build[build.index("--platform") + 1] == "linux/amd64"
assert "--provenance=false" in build and "--sbom=false" in build
assert f"org.opencontainers.image.revision={SHA}" in build
assert "org.opencontainers.image.source=https://github.com/Team/Proxmox-AIS" in build
assert "org.opencontainers.image.source=https://gitlab.bartelluis.de/Team/Proxmox-AIS" in build
assert build[build.index("--tag") + 1] == build_image
smoke = outcome.calls[1]
for option in ["--read-only", "--cap-drop", "ALL", "no-new-privileges:true", "/tmp:rw,noexec,nosuid,size=128m"]:
@@ -138,7 +143,7 @@ def test_default_branch_publishes_only_after_hardened_image_smoke(container_ci):
]
assert (outcome.root / "deploy.env").read_text() == f"PROVISIONER_IMAGE={DIGEST}\n"
assert "ci-210-1-container_publish" in (outcome.root / "build.env").read_text()
assert outcome.calls[2] == ["login", "ghcr.io", "--username", "ci-user", "--password-stdin"]
assert outcome.calls[2] == ["login", REGISTRY_HOST, "--username", "registry-owner", "--password-stdin"]
assert (outcome.root / "login.stdin").read_text() == PASSWORD
assert PASSWORD not in outcome.result.stdout + outcome.result.stderr + (outcome.root / "docker.log").read_text()
config_path = Path((outcome.root / "docker-config.path").read_text())
@@ -155,8 +160,8 @@ def test_release_tag_matches_project_version_and_does_not_move_edge(container_ci
def test_pull_request_verifies_without_registry_credentials(container_ci):
outcome = container_ci(
"verify", GITHUB_EVENT_NAME="pull_request", GITHUB_REF_NAME="1/merge",
GITHUB_REF_PROTECTED="false", GITHUB_ACTOR=None, GHCR_TOKEN=None,
"verify", GITHUB_EVENT_NAME="pull_request", GITHUB_REF_NAME="1/head",
PUBLISH_IMAGES=None, GITHUB_ACTOR=None, REGISTRY_USERNAME=None, REGISTRY_TOKEN=None,
)
assert outcome.result.returncode == 0, outcome.result.stderr
assert [call[0] for call in outcome.calls] == ["build", "run", "rm", "image"]
@@ -166,14 +171,49 @@ def test_pull_request_verifies_without_registry_credentials(container_ci):
assert not (outcome.root / "deploy.env").exists()
@pytest.mark.parametrize("protected", ["false", None])
def test_gitea_publication_opt_in_does_not_depend_on_ref_protected(container_ci, protected):
outcome = container_ci(GITHUB_REF_PROTECTED=protected, GITHUB_ACTOR=None)
assert outcome.result.returncode == 0, outcome.result.stderr
assert outcome.calls[2] == ["login", REGISTRY_HOST, "--username", "registry-owner", "--password-stdin"]
assert (outcome.root / "deploy.env").is_file()
@pytest.mark.parametrize("registry_host", [
None, "", "https://registry.example.test", "registry.example.test/images",
"registry.example.test\nother.example.test", "Registry.example.test",
"registry.example.test:invalid", "registry.example.test:",
"-registry.example.test", "user@registry.example.test",
])
def test_invalid_registry_host_fails_before_build(container_ci, registry_host):
outcome = container_ci(REGISTRY_HOST=registry_host)
assert outcome.result.returncode != 0
assert not outcome.calls
assert not (outcome.root / "deploy.env").exists()
def test_registry_port_is_used_for_login_tags_and_deployment_digest(container_ci):
registry_host = "registry.example.test:5000"
registry_image = f"{registry_host}/team/proxmox-ais"
digest = f"{registry_image}@sha256:{'b' * 64}"
outcome = container_ci(REGISTRY_HOST=registry_host, MOCK_REPO_DIGESTS=digest)
assert outcome.result.returncode == 0, outcome.result.stderr
assert outcome.calls[2] == ["login", registry_host, "--username", "registry-owner", "--password-stdin"]
assert [call[1] for call in outcome.calls if call[0] == "push"] == [
f"{registry_image}:sha-{SHA}", f"{registry_image}:edge"
]
assert (outcome.root / "deploy.env").read_text() == f"PROVISIONER_IMAGE={digest}\n"
@pytest.mark.parametrize("overrides", [
{"GITHUB_REF_NAME": "feature/ci"},
{"GITHUB_REF_NAME": f"v{VERSION}"},
{"GITHUB_REF_NAME": None},
{"DEFAULT_BRANCH": ""},
{"GITHUB_REF_PROTECTED": "false"},
{"GITHUB_REF_PROTECTED": None},
{"GITHUB_REF_PROTECTED": "TRUE"},
{"PUBLISH_IMAGES": "false"},
{"PUBLISH_IMAGES": None},
{"PUBLISH_IMAGES": "TRUE"},
{"PUBLISH_IMAGES": "1"},
{"GITHUB_EVENT_NAME": "pull_request"},
{"GITHUB_EVENT_NAME": "pull_request_target"},
{"GITHUB_EVENT_NAME": "workflow_run"},
@@ -191,9 +231,9 @@ def test_pull_request_verifies_without_registry_credentials(container_ci):
{"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v0.1.00"},
{"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v0.1"},
{"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v0.1.0.0"},
{"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": f"v{VERSION}", "GITHUB_REF_PROTECTED": "false"},
{"GHCR_TOKEN": None},
{"GITHUB_ACTOR": None},
{"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": f"v{VERSION}", "PUBLISH_IMAGES": "false"},
{"REGISTRY_TOKEN": None},
{"REGISTRY_USERNAME": None},
{"GITHUB_SHA": "1234"},
{"GITHUB_RUN_ID": "invalid"},
{"GITHUB_RUN_ATTEMPT": "../2"},
@@ -214,7 +254,7 @@ def test_unauthorized_or_invalid_publication_fails_before_build(container_ci, ov
@pytest.mark.parametrize("ref_type, ref_name", [("branch", "main"), ("tag", f"v{VERSION}")])
def test_manual_run_can_publish_protected_default_branch_or_release(container_ci, ref_type, ref_name):
def test_manual_run_can_publish_opted_in_default_branch_or_release(container_ci, ref_type, ref_name):
outcome = container_ci(
GITHUB_EVENT_NAME="workflow_dispatch", GITHUB_REF_TYPE=ref_type, GITHUB_REF_NAME=ref_name,
)
@@ -269,6 +309,68 @@ def test_missing_or_invalid_registry_digest_fails_without_artifact(container_ci,
assert not (outcome.root / "deploy.env").exists()
@pytest.mark.parametrize("overrides, expected", [
({}, "true"),
({"GITHUB_EVENT_NAME": "workflow_dispatch"}, "true"),
({"GITHUB_REF_NAME": "trunk", "DEFAULT_BRANCH": "trunk"}, "true"),
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v0.0.0"}, "true"),
({"GITHUB_EVENT_NAME": "workflow_dispatch", "GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v12.34.56"}, "true"),
({"GITHUB_REF_PROTECTED": None}, "true"),
({"PUBLISH_IMAGES": None}, "false"),
({"PUBLISH_IMAGES": ""}, "false"),
({"PUBLISH_IMAGES": "false"}, "false"),
({"PUBLISH_IMAGES": "TRUE"}, "false"),
({"PUBLISH_IMAGES": "1"}, "false"),
({"PUBLISH_IMAGES": "true\n"}, "false"),
({"PUBLISH_IMAGES": "false", "GITHUB_REF_PROTECTED": "true"}, "false"),
({"GITHUB_EVENT_NAME": None}, "false"),
({"GITHUB_EVENT_NAME": "pull_request"}, "false"),
({"GITHUB_EVENT_NAME": "pull_request_target"}, "false"),
({"GITHUB_EVENT_NAME": "workflow_run"}, "false"),
({"GITHUB_EVENT_NAME": "schedule"}, "false"),
({"GITHUB_REF_TYPE": None}, "false"),
({"GITHUB_REF_TYPE": "pull_request"}, "false"),
({"GITHUB_REF_NAME": "feature/ci"}, "false"),
({"GITHUB_REF_NAME": None}, "false"),
({"DEFAULT_BRANCH": None}, "false"),
({"DEFAULT_BRANCH": "", "GITHUB_REF_NAME": ""}, "false"),
({"GITHUB_REF_NAME": "main;touch injected"}, "false"),
({"GITHUB_REF_NAME": "$(touch injected)"}, "false"),
({"GITHUB_REF_NAME": "`touch injected`"}, "false"),
({"GITHUB_REF_NAME": "release/$(touch${IFS}injected)", "DEFAULT_BRANCH": "release/$(touch${IFS}injected)"}, "true"),
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "main"}, "false"),
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v01.2.3"}, "false"),
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v1.02.3"}, "false"),
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v1.2.03"}, "false"),
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v1.2"}, "false"),
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v1.2.3.4"}, "false"),
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v1.2.3-rc1"}, "false"),
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v1.2.3+build"}, "false"),
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v1.2.3\nother"}, "false"),
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v1.2.3$(touch injected)"}, "false"),
])
def test_publication_policy_matrix(tmp_path, overrides, expected):
shutil.copyfile(ROOT / "ci/publish-policy.sh", tmp_path / "publish-policy.sh")
environment = {
**os.environ,
"PUBLISH_IMAGES": "true",
"GITHUB_EVENT_NAME": "push",
"GITHUB_REF_TYPE": "branch",
"GITHUB_REF_NAME": "main",
"DEFAULT_BRANCH": "main",
"GITHUB_REF_PROTECTED": "false",
**overrides,
}
environment = {name: value for name, value in environment.items() if value is not None}
result = subprocess.run(
[posix_shell(), "publish-policy.sh"], cwd=tmp_path, env=environment,
capture_output=True, text=True, timeout=10,
)
assert result.returncode == 0, result.stderr
assert result.stdout == f"{expected}\n"
assert not (tmp_path / "injected").exists(), "Ref names must never be evaluated as shell code"
@pytest.fixture
def python_ci(tmp_path):
shell = posix_shell()