Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
name: E2E Tests

on:
workflow_dispatch:

permissions:
contents: read

jobs:

e2e:
runs-on: ubuntu-latest
timeout-minutes: 5

strategy:
fail-fast: false
matrix:
include:
- name: Fedora 43
test: TestFedora
release: "43"
- name: CentOS Stream 10
test: TestCentOSStream
release: "10"
- name: AlmaLinux 10
test: TestAlmaLinux
release: "10"
- name: Rocky Linux 10
test: TestRockyLinux
release: "10"
- name: Debian trixie
test: TestDebian
release: trixie
- name: Ubuntu noble
test: TestUbuntu
release: noble
- name: Arch Linux latest
test: TestArch
release: latest

name: ${{ matrix.name }}

steps:

- uses: actions/checkout@v6

- uses: actions/setup-go@v6
with:
go-version: 'stable'
check-latest: true

- name: Run e2e test
env:
E2E_RELEASE: ${{ matrix.release }}
CONTAINER_RUNTIME: docker
run: go test -tags e2e -v -race -timeout 5m -run ${{ matrix.test }} ./test/e2e/
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Added

- End-to-end tests using real package managers (dnf, apt, pacman) in containers via `make e2e`
- `ubuntu-security` repository in default configuration

### Changed

Expand Down
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ make build # build binary → bin/
make ci-check # lint + govulncheck + tests
make run # run locally with debug logging
go test -v -race ./pkg/pkgproxy/ -run TestName # run a single test
make e2e # run all e2e tests (requires podman or docker)
make e2e DISTRO=fedora # run e2e tests for a specific distro
make e2e DISTRO=fedora RELEASE=42 # run e2e tests for a specific distro and release
```

## Pre-commit
Expand All @@ -28,6 +31,9 @@ pre-commit run codespell --all-files
- Do not delete failing tests.
- Update the `[Unreleased]` section of `CHANGELOG.md` for every user-facing change made to the codebase.
- Before pushing a release tag: rename `[Unreleased]` to `[v<version>] - <date>`, add a new empty `[Unreleased]` section above it, and commit.
- E2e tests must pass before a feature is considered complete.
- Adding support for a new Linux distribution requires adding corresponding e2e tests.
- Changes to client config snippets (sources.list, .repo files) must be replicated in the landing page snippets and in README.md.

## OpenSpec

Expand Down
17 changes: 15 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,25 @@ test: ## Run the tests against the codebase
$(info **************************************************)
go test $(GO_TEST_ARGS) $(GO_TEST_ARGS_EXTRA) ./...

# distroToTest converts a DISTRO value to the corresponding Go test function name.
# Usage: $(call distroToTest,fedora) → TestFedora
define distroToTest
$(strip $(if $(filter fedora,$(1)),TestFedora,\
$(if $(filter centos-stream,$(1)),TestCentOSStream,\
$(if $(filter almalinux,$(1)),TestAlmaLinux,\
$(if $(filter rockylinux,$(1)),TestRockyLinux,\
$(if $(filter debian,$(1)),TestDebian,\
$(if $(filter ubuntu,$(1)),TestUbuntu,\
$(if $(filter archlinux,$(1)),TestArch,\
$(error Unknown DISTRO: $(1). Use one of: fedora centos-stream almalinux rockylinux debian ubuntu archlinux)))))))))
endef

.PHONY: e2e
e2e: ## Run end-to-end tests (requires podman or docker)
e2e: ## Run end-to-end tests (requires podman or docker). Use DISTRO= and RELEASE= to filter.
$(info *************************************************)
$(info ********** EXECUTING 'e2e' MAKE TARGET **********)
$(info *************************************************)
go test -tags e2e $(GO_TEST_ARGS) $(GO_TEST_ARGS_EXTRA) ./test/e2e/
E2E_RELEASE=$(RELEASE) go test -tags e2e -timeout 15m $(GO_TEST_ARGS) $(GO_TEST_ARGS_EXTRA) $(if $(DISTRO),-run $(call distroToTest,$(DISTRO))) ./test/e2e/

.PHONY: coverage
coverage: ## Generates test coverage report
Expand Down
31 changes: 28 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,36 @@ baseurl=http://<pkgproxy>:8080/rockylinux/$releasever/BaseOS/$basearch/os/

E.g. Ubuntu 24.04 Noble Numbat: `/etc/apt/sources.list` (substitute your release codename):
```
deb http://<pkgproxy>:8080/ubuntu noble main restricted universe multiverse
deb http://<pkgproxy>:8080/ubuntu noble-updates main restricted universe multiverse
deb http://<pkgproxy>:8080/ubuntu noble-security main restricted universe multiverse
deb http://<pkgproxy>:8080/ubuntu noble main restricted universe multiverse
deb http://<pkgproxy>:8080/ubuntu noble-updates main restricted universe multiverse
deb http://<pkgproxy>:8080/ubuntu-security noble-security main restricted universe multiverse
```

## Testing

### End-to-End Tests

End-to-end tests validate pkgproxy against real package managers running in containers. They require either [Podman](https://podman.io/) or Docker.

Run all e2e tests:
```shell
make e2e
```

Run tests for a specific distribution:
```shell
make e2e DISTRO=fedora
```

Run tests for a specific distribution and release:
```shell
make e2e DISTRO=fedora RELEASE=42
```

Supported `DISTRO` values: `fedora`, `centos-stream`, `almalinux`, `rockylinux`, `debian`, `ubuntu`, `archlinux`.

When adding support for a new Linux distribution, corresponding e2e tests should be added as well.

## Building the Container Image

Build a container image locally using [ko](https://ko.build/):
Expand Down
6 changes: 6 additions & 0 deletions configs/pkgproxy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,9 @@ repositories:
mirrors:
- https://mirror.init7.net/ubuntu/
- http://archive.ubuntu.com/ubuntu/
ubuntu-security:
suffixes:
- .deb
mirrors:
- https://mirror.init7.net/ubuntu/
- https://security.ubuntu.com/ubuntu/
2 changes: 2 additions & 0 deletions openspec/changes/e2e-github-workflow/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-03-21
72 changes: 72 additions & 0 deletions openspec/changes/e2e-github-workflow/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
## Context

pkgproxy has a working e2e test suite (`test/e2e/e2e_test.go`) that validates proxying for Fedora, COPR, Debian, and Arch Linux using real containers and upstream mirrors. Currently these tests can only be run locally via `make e2e`. The test is structured as a single `TestE2E` function with subtests, sharing one pkgproxy instance.

The goal is to run these tests in GitHub Actions on demand, expand coverage to more distributions, and expose results via a matrix strategy for clear per-distro visibility.

## Goals / Non-Goals

**Goals:**
- Run e2e tests in GitHub Actions via `workflow_dispatch` with a matrix strategy (one job per distro/release tuple)
- Add e2e coverage for CentOS Stream 10, AlmaLinux 10, Rocky Linux 10, and Ubuntu Noble
- Add EPEL metadata testing for non-Fedora DNF distros
- Add COPR testing for all DNF-based distros
- Parameterize the `make e2e` target with optional `DISTRO` and `RELEASE` variables
- Add `ubuntu-security` repository to pkgproxy config

**Non-Goals:**
- Running e2e tests automatically on push or PR (manual trigger only)
- Testing multiple releases of the same distro in the initial matrix (infrastructure supports it, but not needed yet)
- Caching container images between workflow runs
- EPEL package installation testing (metadata-only, like debian-security)

## Decisions

### 1. One top-level test function per distro instead of subtests under TestE2E

Each distro gets its own top-level test function (`TestFedora`, `TestDebian`, etc.) rather than subtests under a shared `TestE2E`. This enables clean `-run` filtering from the matrix without regex escaping issues, and makes each distro test self-contained with its own setup (repo files, COPR, EPEL).

**Alternative considered:** Keeping `TestE2E` with subtests and filtering via `-run TestE2E/Fedora`. This works but couples all distros to a shared function and makes adding distro-specific setup (EPEL, different repo file patterns) messier.

### 2. E2E_RELEASE env var for release parameterization

Each test function reads `E2E_RELEASE` to determine the release version (e.g., `43` for Fedora, `trixie` for Debian). If unset, a sensible default is used. The matrix passes the release, and local `make e2e` works without any env vars.

**Alternative considered:** Separate test functions per release (e.g., `TestFedora42`, `TestFedora43`). This doesn't scale and duplicates code.

### 3. Inline matrix with explicit include entries

The workflow matrix uses `include` with explicit objects rather than a cross-product strategy. Each entry specifies the display name, test function, and release. This is verbose but immediately readable and avoids unexpected combinations.

### 4. Sources.list generation moves from shell script to Go test

For apt-based distros, the Go test generates the `sources.list` file and mounts it into the container, consistent with how DNF repo files are already handled. The `test-apt.sh` script is simplified to just `apt-get update && apt-get install`. This allows the Go test to control repo prefixes (`debian` vs `ubuntu`) and security repo inclusion per distro.

### 5. EPEL handled via additional repo file mount, metadata-only validation

For CentOS Stream, AlmaLinux, and Rocky Linux, the Go test mounts an additional EPEL `.repo` file. The existing `dnf makecache` in `test-dnf.sh` validates that EPEL metadata is accessible. No separate package install or cache assertion is needed for EPEL (mirrors metadata, not packages).

### 6. COPR for non-Fedora DNF distros uses epel-$releasever-$basearch pattern

The COPR repo URL pattern for non-Fedora distros is `/copr/ganto/jo/epel-$releasever-$basearch/` (vs `/copr/ganto/jo/fedora-$releasever-$basearch/` for Fedora).

### 7. Fully qualified container image names

All container images use fully qualified names (`docker.io/library/fedora:43`, `quay.io/centos/centos:stream10`) to avoid dependency on container runtime defaults. This ensures consistent behavior across podman and docker.

### 8. TestMain builds pkgproxy binary once for all test functions

A `TestMain` function in the e2e package builds the pkgproxy binary once into a shared temporary directory and stores the path in a package-level variable. Each top-level test function reuses this pre-built binary when starting its own pkgproxy process (with its own port, cache directory, and lifecycle). This avoids rebuilding the binary 7 times when running `make e2e` locally, and costs nothing in the GitHub Actions matrix (where each job runs only one test function).

**Alternative considered:** Building the binary in each test function (the current approach for `TestE2E`). This is simpler but wasteful when running all tests locally.

### 9. $basearch and $releasever instead of hardcoded values

Repo files use DNF/YUM variables (`$basearch`, `$releasever`) rather than hardcoded `x86_64` or version numbers. This allows the same tests to work on ARM64 (e.g., developer MacBooks) without modification.

## Risks / Trade-offs

- **Upstream mirror availability** → Tests depend on real upstream mirrors. Transient mirror failures will cause test failures. Mitigation: manual trigger only, so failures don't block CI. pkgproxy's retry configuration helps for mirrors that support it.
- **Container image pull rate limits** → Docker Hub rate limits may cause occasional failures. Mitigation: with only 7 matrix jobs on manual trigger, this is unlikely. Can add `docker login` step later if needed.
- **CentOS Stream URL structure uncertainty** → The exact mirror path for CentOS Stream 10 (`$releasever-stream/BaseOS/...`) needs validation. Mitigation: this is exactly what the e2e test will catch.
- **5-minute timeout per job** → Container image pulls + package installs on slow networks could exceed this. Mitigation: GitHub Actions runners have fast network; timeout can be bumped if needed.
31 changes: 31 additions & 0 deletions openspec/changes/e2e-github-workflow/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## Why

The e2e tests validate pkgproxy against real package managers and upstream mirrors but can only be run locally today. Adding a GitHub Actions workflow enables on-demand CI validation across all supported distributions, catches regressions from upstream mirror changes, and expands test coverage to distros not yet tested (CentOS Stream, AlmaLinux, Rocky Linux, Ubuntu).

## What Changes

- Add a new GitHub Actions workflow (`.github/workflows/e2e.yaml`) triggered only via `workflow_dispatch`, using a matrix strategy with one job per distro/release tuple and a 5-minute timeout per job.
- Restructure `test/e2e/e2e_test.go` from a single `TestE2E` function with subtests into separate top-level test functions per distro (`TestFedora`, `TestCentOSStream`, `TestAlmaLinux`, `TestRockyLinux`, `TestDebian`, `TestUbuntu`, `TestArch`), each parameterized by `E2E_RELEASE` env var with sensible defaults.
- Add new distro tests: CentOS Stream 10, AlmaLinux 10, Rocky Linux 10, Ubuntu Noble.
- DNF-based distros (except Fedora) include COPR (`epel-$releasever-$basearch` pattern) and EPEL repo metadata tests. Fedora includes COPR only.
- Move `sources.list` generation from `test-apt.sh` into the Go test (consistent with DNF repo file generation). Simplify `test-apt.sh` to just run `apt-get update && apt-get install`.
- Add `ubuntu-security` repository to `configs/pkgproxy.yaml`.
- Update `Makefile` with parameterized `e2e` target supporting optional `DISTRO` and `RELEASE` variables.
- Use fully qualified container image names everywhere (e.g., `docker.io/library/fedora:43`, `quay.io/centos/centos:stream10`).
- Use `$basearch` and `$releasever` variables instead of hardcoded architecture/release values.

## Capabilities

### New Capabilities
- `e2e-github-workflow`: GitHub Actions workflow with matrix strategy for running e2e tests per distro/release tuple on demand.
- `e2e-multi-distro`: Extended e2e test coverage for CentOS Stream, AlmaLinux, Rocky Linux, and Ubuntu, including EPEL and COPR for DNF-based distros.

### Modified Capabilities
- `e2e-testing`: Restructure from single TestE2E into per-distro top-level test functions, parameterize by E2E_RELEASE env var, move apt sources.list generation into Go test, support DISTRO/RELEASE make parameters.

## Impact

- **New file**: `.github/workflows/e2e.yaml`
- **Modified files**: `test/e2e/e2e_test.go`, `test/e2e/test-apt.sh`, `configs/pkgproxy.yaml`, `Makefile`, `README.md`, `CLAUDE.md`
- **New shell scripts**: None (existing scripts reused; `test-dnf.sh` and `test-pacman.sh` unchanged)
- **External dependencies**: Container images from Docker Hub and Quay.io; upstream package mirrors (Fedora, Debian, Ubuntu, Arch, CentOS, AlmaLinux, Rocky)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## ADDED Requirements

### Requirement: GitHub Actions e2e workflow with workflow_dispatch trigger
The project SHALL provide a GitHub Actions workflow at `.github/workflows/e2e.yaml` that runs e2e tests. The workflow SHALL be triggered only via `workflow_dispatch` (manual trigger). The workflow SHALL NOT run automatically on push or pull request events.

#### Scenario: Workflow is triggered manually
- **WHEN** a user triggers the e2e workflow via the GitHub Actions UI or API
- **THEN** the workflow starts and runs the e2e test matrix

#### Scenario: Workflow does not run on push
- **WHEN** code is pushed to any branch
- **THEN** the e2e workflow is NOT triggered

### Requirement: Matrix strategy with one job per distro/release tuple
The workflow SHALL use a GitHub Actions matrix strategy with one job per distro/release tuple. Each matrix entry SHALL define a display name, the Go test function name to run, and the release version. Each job SHALL appear as a separate entry in the GitHub Actions UI for clear failure isolation.

#### Scenario: Matrix produces separate jobs
- **WHEN** the e2e workflow is triggered
- **THEN** GitHub Actions creates one job per matrix entry, each visible as a separate row in the workflow run UI

#### Scenario: Each job runs exactly one distro test
- **WHEN** a matrix job executes
- **THEN** it runs only the Go test function specified by the matrix entry (e.g., `TestFedora`) with the release specified by `E2E_RELEASE`

### Requirement: 5-minute timeout per matrix job
Each matrix job SHALL have a `timeout-minutes` of 5.

#### Scenario: Job exceeds timeout
- **WHEN** a matrix job runs longer than 5 minutes
- **THEN** the job is cancelled by GitHub Actions

### Requirement: Matrix includes all supported distro/release tuples
The workflow matrix SHALL include the following distro/release tuples: Fedora 43, CentOS Stream 10, AlmaLinux 10, Rocky Linux 10, Debian trixie, Ubuntu noble, Arch Linux latest.

#### Scenario: All distros are represented in the matrix
- **WHEN** the e2e workflow is triggered
- **THEN** jobs are created for Fedora 43, CentOS Stream 10, AlmaLinux 10, Rocky Linux 10, Debian trixie, Ubuntu noble, and Arch Linux latest

### Requirement: Workflow job steps
Each matrix job SHALL checkout the repository, set up Go, and run the e2e test for the specific distro using `go test -tags e2e -run <TestFunction> ./test/e2e/` with the `E2E_RELEASE` environment variable set from the matrix.

#### Scenario: Job executes test with correct parameters
- **WHEN** a matrix job for Fedora 43 runs
- **THEN** it executes `go test -tags e2e -run TestFedora ./test/e2e/` with `E2E_RELEASE=43`
Loading
Loading