diff --git a/CHANGELOG.md b/CHANGELOG.md index f164652..4a1b8d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased](https://github.com/ganto/pkgproxy/commits/HEAD/) +### Added + +- End-to-end tests using real package managers (dnf, apt, pacman) in containers via `make e2e` + +### Changed + +- Landing page snippets for Debian/Ubuntu use `` placeholder instead of hardcoded codenames +- README client configuration examples updated to current stable releases (Debian trixie, Ubuntu noble) + ## [v0.1.0](https://github.com/ganto/pkgproxy/releases/tag/v0.1.0) - 2026-03-20 ### Added diff --git a/Makefile b/Makefile index 12f2e20..a9d83ca 100644 --- a/Makefile +++ b/Makefile @@ -97,6 +97,13 @@ test: ## Run the tests against the codebase $(info **************************************************) go test $(GO_TEST_ARGS) $(GO_TEST_ARGS_EXTRA) ./... +.PHONY: e2e +e2e: ## Run end-to-end tests (requires podman or docker) + $(info *************************************************) + $(info ********** EXECUTING 'e2e' MAKE TARGET **********) + $(info *************************************************) + go test -tags e2e $(GO_TEST_ARGS) $(GO_TEST_ARGS_EXTRA) ./test/e2e/ + .PHONY: coverage coverage: ## Generates test coverage report $(info ******************************************************) diff --git a/README.md b/README.md index fdcca9a..bbd541d 100644 --- a/README.md +++ b/README.md @@ -95,14 +95,13 @@ Server = http://:8080/archlinux/$repo/os/$arch ### Debian -E.g. Debian 11 Bullseye: `/etc/apt/sources.list`: +E.g. Debian 13 Trixie: `/etc/apt/sources.list` (substitute your release codename): ``` -deb http://:8080/debian bullseye main contrib non-free -deb http://:8080/debian bullseye-updates main contrib non-free -deb http://:8080/debian bullseye-backports main contrib non-free -deb http://:8080/debian-security bullseye-security main contrib non-free +deb http://:8080/debian trixie main contrib non-free non-free-firmware +deb http://:8080/debian trixie-updates main contrib non-free non-free-firmware +deb http://:8080/debian trixie-backports main contrib non-free non-free-firmware +deb http://:8080/debian-security trixie-security main contrib non-free non-free-firmware ``` -Adapt configuration accordingly for other Debian releases. ### CentOS @@ -124,7 +123,7 @@ baseurl=http://:8080/centos/$stream/BaseOS/$basearch/os/ ``` [baseos] # metalink=https://mirrors.centos.org/metalink?repo=centos-baseos-$stream&arch=$basearch&protocol=https,http -baseurl=http://merkur.oasis.home:8080/centos-stream/$stream/BaseOS/$basearch/os/ +baseurl=http://:8080/centos-stream/$stream/BaseOS/$basearch/os/ ``` ### EPEL @@ -166,11 +165,11 @@ baseurl=http://:8080/rockylinux/$releasever/BaseOS/$basearch/os/ ### Ubuntu -E.g. Ubuntu 22.04 Jammy Jellyfish: `/etc/apt/sources.list`: +E.g. Ubuntu 24.04 Noble Numbat: `/etc/apt/sources.list` (substitute your release codename): ``` -deb http://:8080/ubuntu jammy main restricted universe multiverse -deb http://:8080/ubuntu jammy-updates main restricted universe multiverse -deb http://:8080/ubuntu jammy-security main restricted universe multiverse +deb http://:8080/ubuntu noble main restricted universe multiverse +deb http://:8080/ubuntu noble-updates main restricted universe multiverse +deb http://:8080/ubuntu noble-security main restricted universe multiverse ``` ## Building the Container Image diff --git a/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/.openspec.yaml b/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/.openspec.yaml new file mode 100644 index 0000000..d8b0ed0 --- /dev/null +++ b/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-03-20 diff --git a/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/design.md b/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/design.md new file mode 100644 index 0000000..95184b2 --- /dev/null +++ b/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/design.md @@ -0,0 +1,96 @@ +## Context + +pkgproxy has comprehensive unit and integration tests using `httptest.NewServer` mock upstreams, but no tests exercise the full stack against real mirrors with real package managers. Mirror URL structures change across distro releases, and landing page client configuration snippets can drift from reality without anyone noticing until a user reports it. + +The production config (`configs/pkgproxy.yaml`) defines 11 repositories across RPM, DEB, and Arch ecosystems. The landing page (`pkg/pkgproxy/landing.go`) generates client configuration snippets, some of which hardcode release codenames (Debian: `bullseye`, Ubuntu: `jammy`) that are already stale. + +## Goals / Non-Goals + +**Goals:** +- Validate that real package managers (dnf, apt, pacman) can fetch metadata and install packages through pkgproxy using the production mirror configuration. +- Verify that pkgproxy caches package files (`.rpm`, `.deb`, `.tar.zst`) to disk during real downloads. +- Catch stale or broken mirror URLs in `configs/pkgproxy.yaml`. +- Keep landing page snippets, e2e test configs, and README documentation in sync via release-agnostic placeholders. + +**Non-Goals:** +- Testing all 11 configured repositories — start with fedora, copr, debian, archlinux. +- Running e2e tests in CI — these require network access and a container runtime (podman or docker); they are developer-local only. +- Building pkgproxy into a container image for testing — the binary runs on the host. +- Testing concurrent cache write safety — already covered by unit tests and naturally exercised by package manager parallelism. + +## Decisions + +### 1. Host-side pkgproxy process + container runtime distro containers + +**Decision:** Start pkgproxy as a subprocess (`go build` + exec) listening on `0.0.0.0:`. Run distro containers via the available container runtime, reaching the proxy at `host.containers.internal:` (podman) or `host.docker.internal:` (docker). + +The test auto-detects the container runtime: it checks for `podman` first, then `docker`. This can be overridden via the `CONTAINER_RUNTIME` environment variable (e.g. `CONTAINER_RUNTIME=docker make e2e`). The host gateway hostname is set automatically based on the detected runtime. + +**Alternatives considered:** +- *Container-to-container with podman network:* More complex networking setup, no additional value over host gateway DNS. +- *Build pkgproxy into a container and test container-to-container:* Couples e2e tests to container build pipeline, slower feedback loop. +- *Podman-only:* Simpler but excludes contributors/environments where only Docker is available. + +**Rationale:** Simplest setup. Both podman and docker support host gateway DNS names. Avoids container image build dependency. Auto-detection with override keeps the default frictionless while allowing explicit control. + +### 2. Real package managers in real distro containers + +**Decision:** Use `fedora:43`, `debian:trixie`, and `archlinux:latest` container images. Run actual `dnf`, `apt`, and `pacman` commands to refresh metadata and install a small package. + +**Alternatives considered:** +- *Replay HTTP request patterns with curl/Go client:* Would miss package manager quirks (parallel downloads, GPG verification, redirect handling). +- *Mock upstreams in containers:* Defeats the purpose of testing real mirrors. + +**Rationale:** The goal is to validate the real request patterns of real package managers against real mirrors. Only running the actual tools achieves this. + +### 3. Per-distro-family shell scripts mounted into containers + +**Decision:** Write short shell scripts (`test-dnf.sh`, `test-apt.sh`, `test-pacman.sh`) in `test/e2e/` that accept parameters (proxy address, release, packages). The Go test writes repo config files to a temp dir, mounts both the script and configs into the container. The `test-apt.sh` script configures a complete `sources.list` with both `debian` and `debian-security` entries in a single invocation, since `apt update` naturally hits all configured repos. + +**Alternatives considered:** +- *Inline podman run commands:* Harder to read, debug, and maintain. +- *Custom Containerfiles per distro:* Adds build step, slower iteration. + +**Rationale:** Scripts are easy to debug manually (`podman run ... bash /test-dnf.sh ...`), and no image build is needed. + +### 4. Shared cache directory across all distro tests + +**Decision:** All distro tests share a single cache directory (a temp dir created once per test run). Each pkgproxy repository writes to its own subdirectory naturally. + +**Alternatives considered:** +- *Separate cache dir per test:* Cleaner isolation but hides real cross-repo interactions. + +**Rationale:** Mirrors real-world usage. If shared cache causes issues, it would also cause issues in production. Sequential execution avoids race conditions. + +### 5. Sequential test execution + +**Decision:** Distro tests run sequentially within a single Go test function (subtests). + +**Rationale:** Network bandwidth is the bottleneck, not CPU. Sequential execution simplifies debugging. Package managers already introduce internal parallelism (multi-package downloads). + +### 6. GPG enabled except for COPR + +**Decision:** Keep GPG signature verification enabled for fedora, debian, and archlinux (keys ship with base images). Disable for COPR (`gpgcheck=0`) since the COPR GPG key requires a separate fetch. + +**Rationale:** GPG verification proves pkgproxy doesn't corrupt packages or strip headers. COPR key setup adds complexity with little additional value since the RPM flow is already tested via fedora. + +### 7. Release-agnostic landing page snippets + +**Decision:** Change Debian/Ubuntu snippets in `landing.go` from hardcoded codenames (`bullseye`, `jammy`) to `` placeholders, matching the existing COPR pattern (`/`). RPM-based snippets already use `$releasever`. The README keeps concrete examples (updated from `bullseye` to `trixie` for Debian, from `jammy` to current Ubuntu release) with a note to substitute the actual codename. + +**Rationale:** Eliminates snippet staleness in the landing page. E2e tests substitute concrete releases; users substitute their own. The README retains concrete examples for readability. The landing page snippets match the README in *structure* but use placeholders where the README uses concrete codenames. + +### 8. Build tag gating + +**Decision:** Use `//go:build e2e` build tag. Add `make e2e` target that runs `go test -tags e2e ./test/e2e/`. + +**Rationale:** Prevents e2e tests from running during `make test` or `make ci-check`. Explicit opt-in via `make e2e` or `-tags e2e`. + +## Risks / Trade-offs + +- **[Network dependency]** Tests require internet access to reach real mirrors. → Mitigation: Tests are gated behind build tag, never run in CI. Failures due to transient network issues are expected and acceptable. +- **[Mirror availability]** Upstream mirrors may be temporarily down or change URL structure. → Mitigation: Multiple mirrors per repo in config provide failover. Test failures signal real configuration issues that need attention. +- **[Container image pulls]** First run pulls multi-hundred-MB distro images. → Mitigation: Container runtime caches images locally. Document this in test README/comments. +- **[Host gateway DNS support]** `host.containers.internal` (podman) or `host.docker.internal` (docker) may not work in all configurations. → Mitigation: Document prerequisites. Note that `--network=host` is not a viable fallback as it requires elevated privileges. +- **[COPR repo stability]** `ganto/jo` COPR repo could be deleted or renamed. → Mitigation: Repo is maintained by the project owner; test will clearly fail pointing to the cause. +- **[Distro version pinning]** `fedora:43` will eventually become unsupported. → Mitigation: Version numbers are explicit constants in the test code, easy to bump. diff --git a/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/proposal.md b/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/proposal.md new file mode 100644 index 0000000..b7cea61 --- /dev/null +++ b/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/proposal.md @@ -0,0 +1,25 @@ +## Why + +pkgproxy's existing tests use mock HTTP servers (`httptest.NewServer`) to simulate upstreams. While these validate the proxy logic in isolation, they cannot catch real-world breakage: stale mirror URLs in `configs/pkgproxy.yaml`, incorrect URL path structures in the landing page client configuration snippets, or subtle incompatibilities with actual package manager request patterns. When a mirror changes its layout or a new distro release alters its repo structure, there is no automated way to detect the problem before users hit it. + +## What Changes + +- Add end-to-end tests that start a real pkgproxy process, run real Linux distribution containers (via podman or docker, auto-detected with override), and exercise real package managers (`dnf`, `apt`, `pacman`) against real upstream mirrors through the proxy. +- Update landing page configuration snippets for Debian/Ubuntu to use `` placeholders instead of hardcoded codenames, keeping them release-agnostic and in sync with what e2e tests validate. +- Update README client configuration examples for Debian/Ubuntu to use current stable releases (trixie, noble) with a note to substitute the actual codename. Fix stale hostname in CentOS Stream example. +- Add a `make e2e` target and gate the tests behind a `//go:build e2e` build tag. + +## Capabilities + +### New Capabilities +- `e2e-testing`: End-to-end test framework using podman containers with real package managers (dnf, apt, pacman) validating proxy behavior, mirror configs, and cache population against real upstream mirrors. + +### Modified Capabilities +- `http-landing-page`: Debian/Ubuntu configuration snippets switch from hardcoded release codenames to `` placeholders. + +## Impact + +- **New files:** `test/e2e/e2e_test.go`, per-distro shell scripts and repo config templates in `test/e2e/`. +- **Modified files:** `pkg/pkgproxy/landing.go` (snippet placeholders), `Makefile` (new `e2e` target), `CHANGELOG.md`. +- **Dependencies:** Requires `podman` or `docker` on the test host. No new Go dependencies. +- **CI:** The e2e tests are gated and will not run in the standard CI pipeline (`make ci-check`). diff --git a/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/specs/e2e-testing/spec.md b/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/specs/e2e-testing/spec.md new file mode 100644 index 0000000..c361b42 --- /dev/null +++ b/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/specs/e2e-testing/spec.md @@ -0,0 +1,123 @@ +## ADDED Requirements + +### Requirement: E2e test framework with container runtime +The project SHALL provide end-to-end tests in `test/e2e/` that start a real pkgproxy process, run Linux distribution containers via a container runtime (podman or docker), and exercise real package managers against real upstream mirrors through the proxy. Tests SHALL be gated behind a `//go:build e2e` build tag and a `make e2e` Makefile target. + +#### Scenario: E2e tests do not run during standard test suite +- **WHEN** a developer runs `make test` or `make ci-check` +- **THEN** no e2e tests are executed + +#### Scenario: E2e tests run via make target +- **WHEN** a developer runs `make e2e` +- **THEN** the e2e test suite is executed with the `e2e` build tag + +### Requirement: Container runtime auto-detection with override +The e2e test SHALL auto-detect the available container runtime by checking for `podman` first, then `docker`. The runtime SHALL be overridable via the `CONTAINER_RUNTIME` environment variable. The host gateway hostname SHALL be set automatically based on the detected runtime: `host.containers.internal` for podman, `host.docker.internal` for docker. + +#### Scenario: Podman is auto-detected when available +- **WHEN** `podman` is found on `PATH` and `CONTAINER_RUNTIME` is not set +- **THEN** the tests use `podman` as the container runtime and `host.containers.internal` as the host gateway + +#### Scenario: Docker is auto-detected as fallback +- **WHEN** `podman` is not found on `PATH` and `docker` is found and `CONTAINER_RUNTIME` is not set +- **THEN** the tests use `docker` as the container runtime and `host.docker.internal` as the host gateway + +#### Scenario: Environment variable overrides auto-detection +- **WHEN** `CONTAINER_RUNTIME=docker` is set +- **THEN** the tests use `docker` regardless of whether `podman` is available + +#### Scenario: No container runtime available +- **WHEN** neither `podman` nor `docker` is found and `CONTAINER_RUNTIME` is not set +- **THEN** the test is skipped with a descriptive message + +### Requirement: Pkgproxy starts as a host-side subprocess +The e2e test SHALL build and start pkgproxy as a subprocess listening on `0.0.0.0` with a dynamically allocated free port, using the production `configs/pkgproxy.yaml`. The process SHALL be terminated after all subtests complete. + +#### Scenario: Pkgproxy binds to a free port +- **WHEN** the e2e test suite starts +- **THEN** pkgproxy is built, started on `0.0.0.0:`, and reachable from podman containers via `host.containers.internal:` (podman) or `host.docker.internal:` (docker) + +#### Scenario: Pkgproxy is stopped after tests +- **WHEN** all e2e subtests have completed +- **THEN** the pkgproxy subprocess is terminated and the cache directory is cleaned up + +### Requirement: Shared cache directory across all distro tests +All distro subtests SHALL share a single temporary cache directory. Each repository writes to its own subdirectory within the cache (this is pkgproxy's default behavior). + +#### Scenario: Cache directory is shared +- **WHEN** multiple distro subtests run sequentially +- **THEN** they all use the same cache directory passed to pkgproxy via `--cachedir` + +### Requirement: Fedora e2e test +The test suite SHALL include a Fedora subtest using a `fedora:43` container that configures dnf to use pkgproxy for the `fedora` repository, refreshes metadata, and installs a small package (e.g. `tree`). GPG verification SHALL remain enabled. + +#### Scenario: Fedora metadata refresh succeeds +- **WHEN** the Fedora container runs `dnf makecache` through pkgproxy +- **THEN** the command exits successfully + +#### Scenario: Fedora package install succeeds +- **WHEN** the Fedora container runs `dnf install -y tree` through pkgproxy +- **THEN** the command exits successfully + +#### Scenario: Fedora packages are cached +- **WHEN** the Fedora package install completes +- **THEN** the cache directory contains at least one `.rpm` file under the `fedora/` subdirectory tree (recursive search) + +### Requirement: COPR e2e test +The test suite SHALL include a COPR subtest using the same `fedora:43` container that configures an additional dnf repository pointing at `ganto/jo` via the pkgproxy `copr` repository, and installs the `jo` package. GPG verification SHALL be disabled for the COPR repository (`gpgcheck=0`). + +#### Scenario: COPR package install succeeds +- **WHEN** the Fedora container runs `dnf install -y jo` from the COPR repository through pkgproxy +- **THEN** the command exits successfully + +#### Scenario: COPR packages are cached +- **WHEN** the COPR package install completes +- **THEN** the cache directory contains at least one `.rpm` file under the `copr/` subdirectory tree (recursive search) + +### Requirement: Debian e2e test +The test suite SHALL include a Debian subtest using a `debian:trixie` container that configures apt to use pkgproxy for the `debian` and `debian-security` repositories, refreshes metadata, and installs a small package (e.g. `tree`). + +#### Scenario: Debian metadata refresh succeeds +- **WHEN** the Debian container runs `apt update` through pkgproxy +- **THEN** the command exits successfully + +#### Scenario: Debian package install succeeds +- **WHEN** the Debian container runs `apt install -y tree` through pkgproxy +- **THEN** the command exits successfully + +#### Scenario: Debian packages are cached +- **WHEN** the Debian package install completes +- **THEN** the cache directory contains at least one `.deb` file under the `debian/` subdirectory tree (recursive search) + +### Requirement: Arch Linux e2e test +The test suite SHALL include an Arch Linux subtest using an `archlinux:latest` container that configures pacman to use pkgproxy for the `archlinux` repository, refreshes the package database, and installs a small package (e.g. `tree`). GPG verification SHALL remain enabled. + +#### Scenario: Arch metadata refresh succeeds +- **WHEN** the Arch container runs `pacman -Sy` through pkgproxy +- **THEN** the command exits successfully + +#### Scenario: Arch package install succeeds +- **WHEN** the Arch container runs `pacman -S --noconfirm tree` through pkgproxy +- **THEN** the command exits successfully + +#### Scenario: Arch packages are cached +- **WHEN** the Arch package install completes +- **THEN** the cache directory contains at least one `.tar.zst` file under the `archlinux/` subdirectory tree (recursive search) + +### Requirement: Per-distro-family shell scripts +The e2e tests SHALL use per-distro-family shell scripts (`test-dnf.sh`, `test-apt.sh`, `test-pacman.sh`) located in `test/e2e/` that are mounted into containers. Scripts SHALL accept parameters for proxy address and packages to install. + +#### Scenario: Shell script is mounted and executed +- **WHEN** a distro container is started +- **THEN** the corresponding shell script and repo config files are mounted into the container and the script is executed + +#### Scenario: DNF script handles both standard and COPR repos +- **WHEN** the Go test generates a `.repo` file with `gpgcheck=0` for COPR +- **THEN** `test-dnf.sh` uses the mounted repo config as-is, without modifying GPG settings + +### Requirement: Sequential distro test execution +Distro subtests SHALL run sequentially, not in parallel. + +#### Scenario: Tests run one at a time +- **WHEN** the e2e test suite executes +- **THEN** each distro subtest completes before the next one starts diff --git a/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/specs/http-landing-page/spec.md b/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/specs/http-landing-page/spec.md new file mode 100644 index 0000000..cdb754a --- /dev/null +++ b/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/specs/http-landing-page/spec.md @@ -0,0 +1,28 @@ +## MODIFIED Requirements + +### Requirement: Package manager configuration snippets match README +The landing page SHALL include copy-paste configuration snippets for repositories whose names appear in the project README client configuration section. Snippets MUST match the URL structure from the README including the full URI path suffix after the repository name (e.g. `/$releasever/BaseOS/$basearch/os/`), with `` replaced by the configured public address. Repositories not documented in the README SHALL have their snippet omitted entirely. DEB-based snippets (Debian, Ubuntu) SHALL use a `` placeholder instead of hardcoded release codenames, matching the placeholder convention used by the COPR snippet (``, ``). The README retains concrete codename examples for readability; the landing page uses placeholders. + +#### Scenario: Known RPM repository shows dnf/yum baseurl snippet with full path +- **WHEN** a repository name matches one documented in the README with `.rpm` suffixes +- **THEN** the landing page shows the exact `baseurl=http://
//` snippet from the README for that repository + +#### Scenario: Known DEB repository shows apt sources snippet with release placeholder +- **WHEN** a repository name matches one documented in the README with `.deb` suffixes +- **THEN** the landing page shows one or more `deb http://
/ ` lines using `` as a placeholder instead of a hardcoded codename + +#### Scenario: Known Arch repository shows pacman mirrorlist snippet with full path +- **WHEN** a repository name matches one documented in the README with `.tar.zst` or `.pkg.tar.*` suffixes +- **THEN** the landing page shows the exact `Server = http://
//$repo/os/$arch` snippet from the README + +#### Scenario: Unknown repository has no snippet +- **WHEN** a repository name has no matching entry in the README client configuration section +- **THEN** no configuration snippet is shown for that repository + +#### Scenario: Snippet uses listen host:port when no public host is set +- **WHEN** no public host is configured and pkgproxy is started with `--host h --port p` +- **THEN** all config snippets on the landing page use `h:p` as the address + +#### Scenario: Snippet uses public address verbatim without appending listen port +- **WHEN** a public address is configured via `--public-host` or `PKGPROXY_PUBLIC_HOST` +- **THEN** all config snippets use that value verbatim and the listen port is not appended diff --git a/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/tasks.md b/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/tasks.md new file mode 100644 index 0000000..aa9a39b --- /dev/null +++ b/openspec/changes/archive/2026-03-21-e2e-package-manager-tests/tasks.md @@ -0,0 +1,41 @@ +## 1. Landing page snippet update + +- [x] 1.1 Update Debian snippet in `snippetFuncs` (`pkg/pkgproxy/landing.go`) to use `` placeholder instead of hardcoded `bullseye` +- [x] 1.2 Update Ubuntu snippet in `snippetFuncs` to use `` placeholder instead of hardcoded `jammy` +- [x] 1.3 Update landing page unit tests in `pkg/pkgproxy/landing_test.go` to expect the new placeholder format +- [x] 1.4 Update README Debian examples from `bullseye` to `trixie` (current stable), Ubuntu examples from `jammy` to `noble` (current LTS), with a note to substitute the actual codename. Fix stale `merkur.oasis.home` hostname in CentOS Stream example to use `` + +## 2. E2e test directory structure + +- [x] 2.1 Create `test/e2e/` directory +- [x] 2.2 Create `test/e2e/e2e_test.go` with `//go:build e2e` tag, test scaffolding (build pkgproxy binary, start subprocess on `0.0.0.0:`, create shared temp cache dir, teardown) +- [x] 2.3 Implement container runtime auto-detection: check for `podman` first, then `docker`; allow override via `CONTAINER_RUNTIME` env var; set host gateway hostname accordingly (`host.containers.internal` vs `host.docker.internal`); skip test if neither is available +- [x] 2.4 Add `make e2e` target to Makefile that runs `go test -tags e2e -v -race ./test/e2e/` + +## 3. Distro shell scripts + +- [x] 3.1 Create `test/e2e/test-dnf.sh` — accepts proxy address and package names, takes a mounted repo config file (`.repo`), runs `dnf makecache` and `dnf install -y`. Handles both standard repos (GPG enabled) and COPR (GPG disabled) via the repo config content generated by the Go test +- [x] 3.2 Create `test/e2e/test-apt.sh` — accepts proxy address, release codename, and package names, writes a complete `sources.list` with both `debian` and `debian-security` entries in a single invocation, runs `apt update` and `apt install -y` +- [x] 3.3 Create `test/e2e/test-pacman.sh` — accepts proxy address and package names, configures pacman mirror, runs `pacman -Sy` and `pacman -S --noconfirm` + +## 4. Fedora + COPR subtest + +- [x] 4.1 Implement `TestE2E/Fedora` subtest: run `fedora:43` container with `test-dnf.sh`, configure `fedora` repo pointing at pkgproxy, install `tree` with GPG enabled +- [x] 4.2 Implement `TestE2E/COPR` subtest: run same `fedora:43` container with `test-dnf.sh`, configure `copr` repo (`ganto/jo`) pointing at pkgproxy, install `jo` with `gpgcheck=0` +- [x] 4.3 Assert cache directory contains `.rpm` files under `fedora/` and `copr/` subdirectory trees (recursive glob, e.g. `fedora/**/*.rpm`) + +## 5. Debian subtest + +- [x] 5.1 Implement `TestE2E/Debian` subtest: run `debian:trixie` container with `test-apt.sh`, configure `debian` and `debian-security` repos pointing at pkgproxy, install `tree` +- [x] 5.2 Assert cache directory contains `.deb` files under `debian/` subdirectory tree (recursive glob) + +## 6. Arch Linux subtest + +- [x] 6.1 Implement `TestE2E/Arch` subtest: run `archlinux:latest` container with `test-pacman.sh`, configure pacman mirror pointing at pkgproxy, install `tree` with GPG enabled +- [x] 6.2 Assert cache directory contains `.tar.zst` files under `archlinux/` subdirectory tree (recursive glob) + +## 7. Finalize + +- [x] 7.1 Run `make ci-check` to verify no regressions in existing tests +- [x] 7.2 Run `make e2e` to verify all distro subtests pass +- [x] 7.3 Update `CHANGELOG.md` with e2e test addition and landing page snippet change diff --git a/openspec/specs/e2e-testing/spec.md b/openspec/specs/e2e-testing/spec.md new file mode 100644 index 0000000..83f8156 --- /dev/null +++ b/openspec/specs/e2e-testing/spec.md @@ -0,0 +1,123 @@ +## Requirements + +### Requirement: E2e test framework with container runtime +The project SHALL provide end-to-end tests in `test/e2e/` that start a real pkgproxy process, run Linux distribution containers via a container runtime (podman or docker), and exercise real package managers against real upstream mirrors through the proxy. Tests SHALL be gated behind a `//go:build e2e` build tag and a `make e2e` Makefile target. + +#### Scenario: E2e tests do not run during standard test suite +- **WHEN** a developer runs `make test` or `make ci-check` +- **THEN** no e2e tests are executed + +#### Scenario: E2e tests run via make target +- **WHEN** a developer runs `make e2e` +- **THEN** the e2e test suite is executed with the `e2e` build tag + +### Requirement: Container runtime auto-detection with override +The e2e test SHALL auto-detect the available container runtime by checking for `podman` first, then `docker`. The runtime SHALL be overridable via the `CONTAINER_RUNTIME` environment variable. The host gateway hostname SHALL be set automatically based on the detected runtime: `host.containers.internal` for podman, `host.docker.internal` for docker. + +#### Scenario: Podman is auto-detected when available +- **WHEN** `podman` is found on `PATH` and `CONTAINER_RUNTIME` is not set +- **THEN** the tests use `podman` as the container runtime and `host.containers.internal` as the host gateway + +#### Scenario: Docker is auto-detected as fallback +- **WHEN** `podman` is not found on `PATH` and `docker` is found and `CONTAINER_RUNTIME` is not set +- **THEN** the tests use `docker` as the container runtime and `host.docker.internal` as the host gateway + +#### Scenario: Environment variable overrides auto-detection +- **WHEN** `CONTAINER_RUNTIME=docker` is set +- **THEN** the tests use `docker` regardless of whether `podman` is available + +#### Scenario: No container runtime available +- **WHEN** neither `podman` nor `docker` is found and `CONTAINER_RUNTIME` is not set +- **THEN** the test is skipped with a descriptive message + +### Requirement: Pkgproxy starts as a host-side subprocess +The e2e test SHALL build and start pkgproxy as a subprocess listening on `0.0.0.0` with a dynamically allocated free port, using the production `configs/pkgproxy.yaml`. The process SHALL be terminated after all subtests complete. + +#### Scenario: Pkgproxy binds to a free port +- **WHEN** the e2e test suite starts +- **THEN** pkgproxy is built, started on `0.0.0.0:`, and reachable from podman containers via `host.containers.internal:` (podman) or `host.docker.internal:` (docker) + +#### Scenario: Pkgproxy is stopped after tests +- **WHEN** all e2e subtests have completed +- **THEN** the pkgproxy subprocess is terminated and the cache directory is cleaned up + +### Requirement: Shared cache directory across all distro tests +All distro subtests SHALL share a single temporary cache directory. Each repository writes to its own subdirectory within the cache (this is pkgproxy's default behavior). + +#### Scenario: Cache directory is shared +- **WHEN** multiple distro subtests run sequentially +- **THEN** they all use the same cache directory passed to pkgproxy via `--cachedir` + +### Requirement: Fedora e2e test +The test suite SHALL include a Fedora subtest using a `fedora:43` container that configures dnf to use pkgproxy for the `fedora` repository, refreshes metadata, and installs a small package (e.g. `tree`). GPG verification SHALL remain enabled. + +#### Scenario: Fedora metadata refresh succeeds +- **WHEN** the Fedora container runs `dnf makecache` through pkgproxy +- **THEN** the command exits successfully + +#### Scenario: Fedora package install succeeds +- **WHEN** the Fedora container runs `dnf install -y tree` through pkgproxy +- **THEN** the command exits successfully + +#### Scenario: Fedora packages are cached +- **WHEN** the Fedora package install completes +- **THEN** the cache directory contains at least one `.rpm` file under the `fedora/` subdirectory tree (recursive search) + +### Requirement: COPR e2e test +The test suite SHALL include a COPR subtest using the same `fedora:43` container that configures an additional dnf repository pointing at `ganto/jo` via the pkgproxy `copr` repository, and installs the `jo` package. GPG verification SHALL be disabled for the COPR repository (`gpgcheck=0`). + +#### Scenario: COPR package install succeeds +- **WHEN** the Fedora container runs `dnf install -y jo` from the COPR repository through pkgproxy +- **THEN** the command exits successfully + +#### Scenario: COPR packages are cached +- **WHEN** the COPR package install completes +- **THEN** the cache directory contains at least one `.rpm` file under the `copr/` subdirectory tree (recursive search) + +### Requirement: Debian e2e test +The test suite SHALL include a Debian subtest using a `debian:trixie` container that configures apt to use pkgproxy for the `debian` and `debian-security` repositories, refreshes metadata, and installs a small package (e.g. `tree`). + +#### Scenario: Debian metadata refresh succeeds +- **WHEN** the Debian container runs `apt update` through pkgproxy +- **THEN** the command exits successfully + +#### Scenario: Debian package install succeeds +- **WHEN** the Debian container runs `apt install -y tree` through pkgproxy +- **THEN** the command exits successfully + +#### Scenario: Debian packages are cached +- **WHEN** the Debian package install completes +- **THEN** the cache directory contains at least one `.deb` file under the `debian/` subdirectory tree (recursive search) + +### Requirement: Arch Linux e2e test +The test suite SHALL include an Arch Linux subtest using an `archlinux:latest` container that configures pacman to use pkgproxy for the `archlinux` repository, refreshes the package database, and installs a small package (e.g. `tree`). GPG verification SHALL remain enabled. + +#### Scenario: Arch metadata refresh succeeds +- **WHEN** the Arch container runs `pacman -Sy` through pkgproxy +- **THEN** the command exits successfully + +#### Scenario: Arch package install succeeds +- **WHEN** the Arch container runs `pacman -S --noconfirm tree` through pkgproxy +- **THEN** the command exits successfully + +#### Scenario: Arch packages are cached +- **WHEN** the Arch package install completes +- **THEN** the cache directory contains at least one `.tar.zst` file under the `archlinux/` subdirectory tree (recursive search) + +### Requirement: Per-distro-family shell scripts +The e2e tests SHALL use per-distro-family shell scripts (`test-dnf.sh`, `test-apt.sh`, `test-pacman.sh`) located in `test/e2e/` that are mounted into containers. Scripts SHALL accept parameters for proxy address and packages to install. + +#### Scenario: Shell script is mounted and executed +- **WHEN** a distro container is started +- **THEN** the corresponding shell script and repo config files are mounted into the container and the script is executed + +#### Scenario: DNF script handles both standard and COPR repos +- **WHEN** the Go test generates a `.repo` file with `gpgcheck=0` for COPR +- **THEN** `test-dnf.sh` uses the mounted repo config as-is, without modifying GPG settings + +### Requirement: Sequential distro test execution +Distro subtests SHALL run sequentially, not in parallel. + +#### Scenario: Tests run one at a time +- **WHEN** the e2e test suite executes +- **THEN** each distro subtest completes before the next one starts diff --git a/openspec/specs/http-landing-page/spec.md b/openspec/specs/http-landing-page/spec.md index 2c63d20..c3d4b62 100644 --- a/openspec/specs/http-landing-page/spec.md +++ b/openspec/specs/http-landing-page/spec.md @@ -16,15 +16,15 @@ pkgproxy SHALL serve an HTML landing page at `GET /` that lists all configured r - **THEN** each upstream mirror URL is rendered as an HTML anchor (``) that opens the mirror in the browser ### Requirement: Package manager configuration snippets match README -The landing page SHALL include copy-paste configuration snippets for repositories whose names appear in the project README client configuration section. Snippets MUST use the exact format from the README including the full URI path suffix after the repository name (e.g. `/$releasever/BaseOS/$basearch/os/`), with `` replaced by the configured public address. Repositories not documented in the README SHALL have their snippet omitted entirely. +The landing page SHALL include copy-paste configuration snippets for repositories whose names appear in the project README client configuration section. Snippets MUST match the URL structure from the README including the full URI path suffix after the repository name (e.g. `/$releasever/BaseOS/$basearch/os/`), with `` replaced by the configured public address. Repositories not documented in the README SHALL have their snippet omitted entirely. DEB-based snippets (Debian, Ubuntu) SHALL use a `` placeholder instead of hardcoded release codenames, matching the placeholder convention used by the COPR snippet (``, ``). The README retains concrete codename examples for readability; the landing page uses placeholders. #### Scenario: Known RPM repository shows dnf/yum baseurl snippet with full path - **WHEN** a repository name matches one documented in the README with `.rpm` suffixes - **THEN** the landing page shows the exact `baseurl=http://
//` snippet from the README for that repository -#### Scenario: Known DEB repository shows apt sources snippet with suite and components +#### Scenario: Known DEB repository shows apt sources snippet with release placeholder - **WHEN** a repository name matches one documented in the README with `.deb` suffixes -- **THEN** the landing page shows the exact one or more `deb http://
/ ` lines from the README for that repository +- **THEN** the landing page shows one or more `deb http://
/ ` lines using `` as a placeholder instead of a hardcoded codename #### Scenario: Known Arch repository shows pacman mirrorlist snippet with full path - **WHEN** a repository name matches one documented in the README with `.tar.zst` or `.pkg.tar.*` suffixes diff --git a/pkg/pkgproxy/landing.go b/pkg/pkgproxy/landing.go index 457f178..7e53221 100644 --- a/pkg/pkgproxy/landing.go +++ b/pkg/pkgproxy/landing.go @@ -70,12 +70,12 @@ var snippetFuncs = map[string]func(string) string{ "baseurl=http://" + addr + "/copr///fedora-$releasever-$basearch/" }, "debian": func(addr string) string { - return "deb http://" + addr + "/debian bullseye main contrib non-free\n" + - "deb http://" + addr + "/debian bullseye-updates main contrib non-free\n" + - "deb http://" + addr + "/debian bullseye-backports main contrib non-free" + return "deb http://" + addr + "/debian main contrib non-free non-free-firmware\n" + + "deb http://" + addr + "/debian -updates main contrib non-free non-free-firmware\n" + + "deb http://" + addr + "/debian -backports main contrib non-free non-free-firmware" }, "debian-security": func(addr string) string { - return "deb http://" + addr + "/debian-security bullseye-security main contrib non-free" + return "deb http://" + addr + "/debian-security -security main contrib non-free non-free-firmware" }, "epel": func(addr string) string { return "[epel]\n" + @@ -93,9 +93,9 @@ var snippetFuncs = map[string]func(string) string{ "baseurl=http://" + addr + "/rockylinux/$releasever/BaseOS/$basearch/os/" }, "ubuntu": func(addr string) string { - return "deb http://" + addr + "/ubuntu jammy main restricted universe multiverse\n" + - "deb http://" + addr + "/ubuntu jammy-updates main restricted universe multiverse\n" + - "deb http://" + addr + "/ubuntu jammy-security main restricted universe multiverse" + return "deb http://" + addr + "/ubuntu main restricted universe multiverse\n" + + "deb http://" + addr + "/ubuntu -updates main restricted universe multiverse\n" + + "deb http://" + addr + "/ubuntu -security main restricted universe multiverse" }, } diff --git a/pkg/pkgproxy/landing_test.go b/pkg/pkgproxy/landing_test.go index a76c886..81e0782 100644 --- a/pkg/pkgproxy/landing_test.go +++ b/pkg/pkgproxy/landing_test.go @@ -77,12 +77,12 @@ func TestLandingHandlerKnownSnippets(t *testing.T) { {"archlinux", ".tar.zst", "Server = http://localhost:8080/archlinux/$repo/os/$arch"}, {"centos", ".rpm", "baseurl=http://localhost:8080/centos/$releasever/os/$basearch/"}, {"centos-stream", ".rpm", "baseurl=http://localhost:8080/centos-stream/$stream/BaseOS/$basearch/os/"}, - {"debian", ".deb", "http://localhost:8080/debian"}, - {"debian-security", ".deb", "http://localhost:8080/debian-security"}, + {"debian", ".deb", "deb http://localhost:8080/debian <release> main contrib non-free non-free-firmware"}, + {"debian-security", ".deb", "deb http://localhost:8080/debian-security <release>-security main contrib non-free non-free-firmware"}, {"epel", ".rpm", "baseurl=http://localhost:8080/epel/$releasever/Everything/$basearch/"}, {"fedora", ".rpm", "baseurl=http://localhost:8080/fedora/releases/$releasever/Everything/$basearch/os/"}, {"rockylinux", ".rpm", "baseurl=http://localhost:8080/rockylinux/$releasever/BaseOS/$basearch/os/"}, - {"ubuntu", ".deb", "http://localhost:8080/ubuntu"}, + {"ubuntu", ".deb", "deb http://localhost:8080/ubuntu <release> main restricted universe multiverse"}, } for _, tt := range tests { t.Run(tt.repo, func(t *testing.T) { diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go new file mode 100644 index 0000000..d8a60b0 --- /dev/null +++ b/test/e2e/e2e_test.go @@ -0,0 +1,216 @@ +//go:build e2e + +package e2e + +import ( + "fmt" + "net" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// containerRuntime holds the detected container runtime binary name. +var containerRuntime string + +// hostGateway holds the hostname that containers use to reach the host. +var hostGateway string + +func detectContainerRuntime(t *testing.T) { + t.Helper() + + if v := os.Getenv("CONTAINER_RUNTIME"); v != "" { + if _, err := exec.LookPath(v); err != nil { + t.Skipf("CONTAINER_RUNTIME=%s not found on PATH", v) + } + containerRuntime = v + } else if _, err := exec.LookPath("podman"); err == nil { + containerRuntime = "podman" + } else if _, err := exec.LookPath("docker"); err == nil { + containerRuntime = "docker" + } else { + t.Skip("no container runtime (podman or docker) found on PATH") + } + + if containerRuntime == "podman" { + hostGateway = "host.containers.internal" + } else { + hostGateway = "host.docker.internal" + } + t.Logf("container runtime: %s, host gateway: %s", containerRuntime, hostGateway) +} + +func freePort(t *testing.T) int { + t.Helper() + l, err := net.Listen("tcp", "0.0.0.0:0") + require.NoError(t, err) + port := l.Addr().(*net.TCPAddr).Port + l.Close() + return port +} + +func buildPkgproxy(t *testing.T) string { + t.Helper() + bin := filepath.Join(t.TempDir(), "pkgproxy") + cmd := exec.Command("go", "build", "-o", bin, ".") + cmd.Dir = filepath.Join(projectRoot()) + cmd.Env = append(os.Environ(), "CGO_ENABLED=0") + out, err := cmd.CombinedOutput() + require.NoError(t, err, "build failed: %s", out) + return bin +} + +func projectRoot() string { + // test/e2e/ -> project root + wd, _ := os.Getwd() + return filepath.Join(wd, "..", "..") +} + +func startPkgproxy(t *testing.T, bin string, port int, cacheDir string) *exec.Cmd { + t.Helper() + configPath := filepath.Join(projectRoot(), "configs", "pkgproxy.yaml") + cmd := exec.Command(bin, "serve", + "--host", "0.0.0.0", + "--port", fmt.Sprintf("%d", port), + "--config", configPath, + "--cachedir", cacheDir, + "--debug", + ) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + require.NoError(t, cmd.Start()) + t.Cleanup(func() { + _ = cmd.Process.Kill() + _ = cmd.Wait() + }) + + // Wait for pkgproxy to be ready. + addr := fmt.Sprintf("127.0.0.1:%d", port) + deadline := time.Now().Add(10 * time.Second) + for time.Now().Before(deadline) { + conn, err := net.DialTimeout("tcp", addr, 200*time.Millisecond) + if err == nil { + conn.Close() + return cmd + } + time.Sleep(100 * time.Millisecond) + } + t.Fatalf("pkgproxy did not become ready on %s", addr) + return cmd +} + +func runContainer(t *testing.T, image string, mounts []string, cmdArgs []string) { + t.Helper() + args := []string{"run", "--rm"} + for _, m := range mounts { + args = append(args, "-v", m) + } + args = append(args, image) + args = append(args, cmdArgs...) + + t.Logf("running: %s %v", containerRuntime, args) + cmd := exec.Command(containerRuntime, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + require.NoError(t, err, "container command failed") +} + +func assertCachedFiles(t *testing.T, cacheDir string, repoPrefix string, suffix string) { + t.Helper() + var matches []string + filepath.Walk(filepath.Join(cacheDir, repoPrefix), func(path string, info os.FileInfo, err error) error { + if err != nil { + return nil + } + if !info.IsDir() && strings.HasSuffix(path, suffix) { + matches = append(matches, path) + } + return nil + }) + assert.NotEmpty(t, matches, "expected %s files under %s/%s", suffix, cacheDir, repoPrefix) + if len(matches) > 0 { + t.Logf("found %d %s files under %s/%s (first: %s)", len(matches), suffix, cacheDir, repoPrefix, matches[0]) + } +} + +func TestE2E(t *testing.T) { + detectContainerRuntime(t) + + bin := buildPkgproxy(t) + port := freePort(t) + cacheDir := t.TempDir() + + startPkgproxy(t, bin, port, cacheDir) + + proxyAddr := fmt.Sprintf("%s:%d", hostGateway, port) + scriptDir := filepath.Join(projectRoot(), "test", "e2e") + + t.Run("Fedora", func(t *testing.T) { + repoFile := filepath.Join(t.TempDir(), "pkgproxy-fedora.repo") + repoContent := fmt.Sprintf(`[fedora] +# metalink=https://mirrors.fedoraproject.org/metalink?repo=fedora-$releasever&arch=$basearch +baseurl=http://%s/fedora/releases/$releasever/Everything/$basearch/os/ +gpgcheck=1 +gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-$releasever-$basearch +`, proxyAddr) + require.NoError(t, os.WriteFile(repoFile, []byte(repoContent), 0644)) + + runContainer(t, "fedora:43", + []string{ + filepath.Join(scriptDir, "test-dnf.sh") + ":/test-dnf.sh:ro,z", + repoFile + ":/etc/yum.repos.d/pkgproxy-fedora.repo:ro,z", + }, + []string{"bash", "/test-dnf.sh", proxyAddr, "tree"}, + ) + + assertCachedFiles(t, cacheDir, "fedora", ".rpm") + }) + + t.Run("COPR", func(t *testing.T) { + repoFile := filepath.Join(t.TempDir(), "pkgproxy-copr.repo") + repoContent := fmt.Sprintf(`[copr:copr.fedorainfracloud.org:ganto:jo] +baseurl=http://%s/copr/ganto/jo/fedora-$releasever-$basearch/ +gpgcheck=0 +`, proxyAddr) + require.NoError(t, os.WriteFile(repoFile, []byte(repoContent), 0644)) + + runContainer(t, "fedora:43", + []string{ + filepath.Join(scriptDir, "test-dnf.sh") + ":/test-dnf.sh:ro,z", + repoFile + ":/etc/yum.repos.d/pkgproxy-copr.repo:ro,z", + }, + []string{"bash", "/test-dnf.sh", proxyAddr, "jo"}, + ) + + assertCachedFiles(t, cacheDir, "copr", ".rpm") + }) + + t.Run("Debian", func(t *testing.T) { + runContainer(t, "debian:trixie", + []string{ + filepath.Join(scriptDir, "test-apt.sh") + ":/test-apt.sh:ro,z", + }, + []string{"bash", "/test-apt.sh", proxyAddr, "trixie", "tree"}, + ) + + assertCachedFiles(t, cacheDir, "debian", ".deb") + }) + + t.Run("Arch", func(t *testing.T) { + runContainer(t, "archlinux:latest", + []string{ + filepath.Join(scriptDir, "test-pacman.sh") + ":/test-pacman.sh:ro,z", + }, + []string{"bash", "/test-pacman.sh", proxyAddr, "tree"}, + ) + + assertCachedFiles(t, cacheDir, "archlinux", ".tar.zst") + }) +} diff --git a/test/e2e/test-apt.sh b/test/e2e/test-apt.sh new file mode 100755 index 0000000..55935a7 --- /dev/null +++ b/test/e2e/test-apt.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# test-apt.sh — Install packages via apt through pkgproxy. +# Usage: test-apt.sh [package...] +set -euo pipefail + +PROXY_ADDR="$1"; shift +RELEASE="$1"; shift +PACKAGES=("$@") + +echo "==> Proxy: ${PROXY_ADDR}" +echo "==> Release: ${RELEASE}" +echo "==> Packages: ${PACKAGES[*]}" + +# Write sources.list pointing at pkgproxy for both debian and debian-security. +cat > /etc/apt/sources.list < Done" diff --git a/test/e2e/test-dnf.sh b/test/e2e/test-dnf.sh new file mode 100755 index 0000000..5178834 --- /dev/null +++ b/test/e2e/test-dnf.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# test-dnf.sh — Install packages via dnf through pkgproxy. +# Usage: test-dnf.sh [package...] +# +# Expects a .repo file to be mounted into /etc/yum.repos.d/ by the caller. +# The repo config controls GPG settings (gpgcheck=1 for standard repos, +# gpgcheck=0 for COPR). +set -euo pipefail + +PROXY_ADDR="$1"; shift +PACKAGES=("$@") + +echo "==> Proxy: ${PROXY_ADDR}" +echo "==> Packages: ${PACKAGES[*]}" + +# Remove all default repo files so only the mounted pkgproxy repo is used. +find /etc/yum.repos.d/ -name '*.repo' ! -name 'pkgproxy-*' -delete + +dnf makecache +dnf install -y "${PACKAGES[@]}" + +echo "==> Done" diff --git a/test/e2e/test-pacman.sh b/test/e2e/test-pacman.sh new file mode 100755 index 0000000..f81cd14 --- /dev/null +++ b/test/e2e/test-pacman.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# test-pacman.sh — Install packages via pacman through pkgproxy. +# Usage: test-pacman.sh [package...] +set -euo pipefail + +PROXY_ADDR="$1"; shift +PACKAGES=("$@") + +echo "==> Proxy: ${PROXY_ADDR}" +echo "==> Packages: ${PACKAGES[*]}" + +# Configure pacman mirror to use pkgproxy. +echo "Server = http://${PROXY_ADDR}/archlinux/\$repo/os/\$arch" > /etc/pacman.d/mirrorlist + +pacman -Sy +pacman -S --noconfirm "${PACKAGES[@]}" + +echo "==> Done"