Virtual repos only merge helm and pypi, so several rpm repos (e.g. many github_rpm remotes) cannot be served as one yum repo. - merge member primary/filelists/other into one repodata set; member order wins duplicate NEVRAs - read member repodata through local, github_rpm and proxied remote paths - prefix package locations (incl. xml:base under a member upstream) with the member name and 302 them to the member route - reject absolute and dot-segment member paths; escape the redirect and keep its query - return 502 when any member's repodata is unavailable - reuse each virtual's merge for 60s behind singleflight; serve data files from its current and previous merge (per replica) - add merger/engine unit tests and a dockerised dnf e2e case Reviewed-on: #131 Co-authored-by: unkin-agent <unkin-agent@unkin.net> Co-committed-by: unkin-agent <unkin-agent@unkin.net>
This commit was merged in pull request #131.
This commit is contained in:
@@ -3,9 +3,17 @@
|
||||
package e2edocker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestVirtualPyPIMerge uploads different packages to two pypi locals and
|
||||
@@ -52,3 +60,79 @@ func TestVirtualHelmMerge(t *testing.T) {
|
||||
t.Fatalf("merged helm index missing a member chart (want alpha and beta): %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
// TestVirtualRPMMerge merges a remote rpm repo and a local rpm repo carrying the
|
||||
// same package: the first member wins the duplicate, its package download
|
||||
// routes through the virtual, and a real dnf can consume the merged repo.
|
||||
func TestVirtualRPMMerge(t *testing.T) {
|
||||
createRepo(t, `{"name":"vrpm-remote","package_type":"rpm","repo_type":"remote","base_url":"`+mockUpstream()+`/rpm-mirror","stale_on_error":true}`)
|
||||
createRepo(t, `{"name":"vrpm-local","package_type":"rpm","repo_type":"local"}`)
|
||||
defer deleteRepo(t, "vrpm-remote")
|
||||
defer deleteRepo(t, "vrpm-local")
|
||||
|
||||
pkg := fixtureBytes(t, "rpmrepo/Packages/e2e-testpkg-1.0-1.noarch.rpm")
|
||||
uploadFile(t, "vrpm-local", "e2e-testpkg-1.0-1.noarch.rpm", pkg, "application/x-rpm")
|
||||
|
||||
createVirtual(t, `{"name":"vrpm","package_type":"rpm","members":["vrpm-remote","vrpm-local"]}`)
|
||||
defer deleteVirtual(t, "vrpm")
|
||||
|
||||
resp, body := getEventually(t, api("/api/v1/virtual/vrpm/repodata/repomd.xml"), 15*time.Second)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("virtual repomd.xml: status %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
var md struct {
|
||||
Data []struct {
|
||||
Type string `xml:"type,attr"`
|
||||
Location struct {
|
||||
Href string `xml:"href,attr"`
|
||||
} `xml:"location"`
|
||||
} `xml:"data"`
|
||||
}
|
||||
if err := xml.Unmarshal(body, &md); err != nil {
|
||||
t.Fatalf("parse repomd.xml: %v\n%s", err, body)
|
||||
}
|
||||
var primary []byte
|
||||
for _, d := range md.Data {
|
||||
if d.Type == "primary" {
|
||||
resp, gz := doRequest(t, http.MethodGet, api("/api/v1/virtual/vrpm/"+d.Location.Href), nil, "")
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("primary: status %d", resp.StatusCode)
|
||||
}
|
||||
r, err := gzip.NewReader(bytes.NewReader(gz))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
primary, _ = io.ReadAll(r)
|
||||
}
|
||||
}
|
||||
href := "vrpm-remote/Packages/e2e-testpkg-1.0-1.noarch.rpm"
|
||||
if strings.Count(string(primary), "<name>e2e-testpkg</name>") != 1 || !strings.Contains(string(primary), `href="`+href+`"`) {
|
||||
t.Fatalf("merged primary should hold one e2e-testpkg owned by the first member:\n%s", primary)
|
||||
}
|
||||
|
||||
resp, got := doRequest(t, http.MethodGet, api("/api/v1/virtual/vrpm/"+href), nil, "")
|
||||
if resp.StatusCode != http.StatusOK || !bytes.Equal(got, pkg) {
|
||||
t.Fatalf("package via virtual: status %d, %d bytes (want %d)", resp.StatusCode, len(got), len(pkg))
|
||||
}
|
||||
|
||||
network := os.Getenv("COMPOSE_NETWORK")
|
||||
internal := os.Getenv("ARTIFACTAPI_INTERNAL")
|
||||
if network == "" || internal == "" {
|
||||
t.Skip("COMPOSE_NETWORK/ARTIFACTAPI_INTERNAL not set; skipping real dnf")
|
||||
}
|
||||
if _, err := exec.LookPath("docker"); err != nil {
|
||||
t.Skip("docker not available on the test host")
|
||||
}
|
||||
repoConf := fmt.Sprintf("[vrpm]\nname=vrpm\nbaseurl=%s/api/v1/virtual/vrpm/\nenabled=1\ngpgcheck=0\nmetadata_expire=0\n", strings.TrimRight(internal, "/"))
|
||||
script := "set -euo pipefail; " +
|
||||
"printf '%s' \"$REPO\" > /etc/yum.repos.d/vrpm.repo; " +
|
||||
"dnf -y --disablerepo='*' --enablerepo=vrpm makecache; " +
|
||||
"dnf -y --disablerepo='*' --enablerepo=vrpm list --available; " +
|
||||
"dnf -y --disablerepo='*' --enablerepo=vrpm install e2e-testpkg; " +
|
||||
"rpm -q e2e-testpkg"
|
||||
out, err := exec.Command("docker", "run", "--rm", "--network", network, "-e", "REPO="+repoConf,
|
||||
"rockylinux:9", "bash", "-c", script).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("real dnf against rpm virtual failed: %v\n%s", err, out)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user