0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-06 17:08:27 +02:00

When the requested arch rpm is missing fall back to noarch (#37236)

This fixes: https://github.com/go-gitea/gitea/issues/37235

It uses the same changeset alpine packages got in:
https://github.com/go-gitea/gitea/issues/26691

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
chethenry 2026-04-21 04:52:28 -06:00 committed by GitHub
parent caff989f34
commit 5495b5d126
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 141 additions and 39 deletions

View File

@ -9,6 +9,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
@ -220,30 +221,38 @@ func UploadPackageFile(ctx *context.Context) {
func DownloadPackageFile(ctx *context.Context) {
name := ctx.PathParam("name")
version := ctx.PathParam("version")
architecture := ctx.PathParam("architecture")
group := ctx.PathParam("group")
s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeRpm,
Name: name,
Version: version,
},
&packages_service.PackageFileInfo{
Filename: fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.PathParam("architecture")),
CompositeKey: ctx.PathParam("group"),
},
ctx.Req.Method,
)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
} else {
apiError(ctx, http.StatusInternalServerError, err)
}
return
openForDownload := func(filename string) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
return packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeRpm,
Name: name,
Version: version,
},
&packages_service.PackageFileInfo{
Filename: filename,
CompositeKey: group,
},
ctx.Req.Method,
)
}
s, u, pf, err := openForDownload(fmt.Sprintf("%s-%s.%s.rpm", name, version, architecture))
if errors.Is(err, util.ErrNotExist) && architecture != "noarch" {
s, u, pf, err = openForDownload(fmt.Sprintf("%s-%s.%s.rpm", name, version, "noarch"))
}
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
return
} else if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
helper.ServePackageFile(ctx, s, u, pf)
}

View File

@ -32,14 +32,24 @@ import (
func TestPackageRpm(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
packageName := "gitea-test"
packageVersion := "1.0.2-1"
packageArchitecture := "x86_64"
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
base64RpmPackageContent := `H4sICFayB2QCAGdpdGVhLXRlc3QtMS4wLjItMS14ODZfNjQucnBtAO2YV4gTQRjHJzl7wbNhhxVF
// To build an RPM package, it needs tools lik "cpio", it's not easy to do in Golang.
// So here we only use pre-built package contents. And to save space, the content is gzipped and base64 encoded.
rpmContentFromGzipBase64 := func(t testing.TB, s string) []byte {
b, err := base64.StdEncoding.DecodeString(s)
require.NoError(t, err)
zr, err := gzip.NewReader(bytes.NewReader(b))
require.NoError(t, err)
content, err := io.ReadAll(zr)
require.NoError(t, err)
return content
}
packageRpmGzipBase64 := `H4sICFayB2QCAGdpdGVhLXRlc3QtMS4wLjItMS14ODZfNjQucnBtAO2YV4gTQRjHJzl7wbNhhxVF
VNwk2zd2PdvZ9Sxnd3Z3NllNsmF3o6congVFsWFHRWwIImIXfRER0QcRfPBJEXvvBQvWSfZTT0VQ
8TF/MuU33zcz3+zOJGEe73lyuQBRBWKWRzDrEddjuVAkxLMc+lsFUOWfm5bvvReAalWECg/TsivU
dyKa0U61aVnl6wj0Uxe4nc8F92hZiaYE8CO/P0r7/Quegr0c7M/AvoCaGZEIWNGUqMHrhhGROIUT
@ -67,14 +77,8 @@ Mu0UFYgZ/bYnuvn/vz4wtCz8qMwsHUvP0PX3tbYFUctAPdrY6tiiDtcCddDECahx7SuVNP5dpmb5
9tMDyaXb7OAlk5acuPn57ss9mw6Wym0m1Fq2cej7tUt2LL4/b8enXU2fndk+fvv57ndnt55/cQob
7tpp/pEjDS7cGPZ6BY430+7danDq6f42Nw49b9F7zp6BiKpJb9s5P0AYN2+L159cnrur636rx+v1
7ae1K28QbMMcqI8CqwIrgwg9nTOp8Oj9q81plUY7ZuwXN8Vvs8wbAAA=`
rpmPackageContent, err := base64.StdEncoding.DecodeString(base64RpmPackageContent)
assert.NoError(t, err)
zr, err := gzip.NewReader(bytes.NewReader(rpmPackageContent))
assert.NoError(t, err)
content, err := io.ReadAll(zr)
assert.NoError(t, err)
packageRpmContent := rpmContentFromGzipBase64(t, packageRpmGzipBase64)
decodeGzipXML := func(t testing.TB, resp *httptest.ResponseRecorder, v any) {
t.Helper()
@ -130,10 +134,10 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
t.Run("Upload", func(t *testing.T) {
url := groupURL + "/upload"
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(packageRpmContent))
MakeRequest(t, req, http.StatusUnauthorized)
req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(packageRpmContent)).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)
@ -156,9 +160,9 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
pb, err := packages.GetBlobByID(t.Context(), pfs[0].BlobID)
assert.NoError(t, err)
assert.Equal(t, int64(len(content)), pb.Size)
assert.Equal(t, int64(len(packageRpmContent)), pb.Size)
req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(packageRpmContent)).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusConflict)
})
@ -169,12 +173,12 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
// download the package without the file name
req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture))
resp := MakeRequest(t, req, http.StatusOK)
assert.Equal(t, content, resp.Body.Bytes())
assert.Equal(t, packageRpmContent, resp.Body.Bytes())
// download the package with a file name (it can be anything)
req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s/any-file-name", groupURL, packageName, packageVersion, packageArchitecture))
resp = MakeRequest(t, req, http.StatusOK)
assert.Equal(t, content, resp.Body.Bytes())
assert.Equal(t, packageRpmContent, resp.Body.Bytes())
})
t.Run("Repository", func(t *testing.T) {
@ -332,7 +336,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
assert.Equal(t, "sha256", p.Checksum.Type)
assert.Equal(t, "f1d5d2ffcbe4a7568e98b864f40d923ecca084e9b9bcd5977ed6521c46d3fa4c", p.Checksum.Checksum)
assert.Equal(t, "https://gitea.io", p.URL)
assert.EqualValues(t, len(content), p.Size.Package)
assert.EqualValues(t, len(packageRpmContent), p.Size.Package)
assert.EqualValues(t, 13, p.Size.Installed)
assert.EqualValues(t, 272, p.Size.Archive)
assert.Equal(t, fmt.Sprintf("package/%s/%s/%s/%s", packageName, packageVersion, packageArchitecture, fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture)), p.Location.Href)
@ -744,6 +748,95 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
})
})
t.Run("NoArch", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
noarchPackageName := "gitea-noarch-test"
noarchPackageVersion := "1.0.0-1"
noarchPackageGzipBase64 := `H4sICKC05mkAA2dpdGVhLW5vYXJjaC10ZXN0LTEuMC4wLTEubm9hcmNoLnJwbQDtmLtrFEEcx+dy` +
`p0ZROYliFMUTLJJiz53HPgbBBwZR0Jgiglo5sztzLt6Lu0uIYmEhFhFUsBUJahd8gHZWKvgnpLES` +
`VIwYo43a6Dm7+zvfhbXsF+ZmP/N7zM5y03wXZt89yyOjbiXqKGHVG6IVnLQ6qt2xcNku2xZG/6wc` +
`WvL70qXbr3PwuAyh4gMz74TnW2YumqJVZl76vQPKrQEeTjn/2swFM6rAb9N61Ezr84sQPwfx9xA/` +
`b8Il6nEpWSgwVz7lrqOZ9oWjqKZSShIQT/k4cHWIAtcJHNsR1OGKSp8w5QcMM+4SiSV1OSY293wi` +
`TAMmmBauL1XoeJ7mXAqhafL6BXzzSd+N2eFP9x8/nHt25u7CBbN49t8/YKZMmTJlypQpU6ZMmTJl` +
`ypTpP1biiXS73Sso8TR+8U1KCOU+mHkXSnyN3HPICc3oh5yeTxL7Jn3A88Brgd8Ab0Q/fJTlZmwC` +
`XgC2gd+h1FfZDbwI9SPAHyB+EPgjxMeAP0O/ceAvED8B/BVYp1xYC1wDXg/nuww8CPvFvlHePG6A` +
`+D3gjcBd4KG0X24d1B9N63Nw3sKxND9XApaQPwQcAm8HVsAMWANz4CrwjpQHrsJ+8D0GnkIcvsfA` +
`C9j/OPBLyL8GPA/xmZj3oj/8OZT4cwij0WStFK+VmiI4JSoKjf8EZdMgevVgRjbqqg1/mEMHxtGR` +
`erupgkhHKkTVqD4xhdLuf27VswLL7VZQbjVrf3mZ9KVX9IZJqkZyaG+j1mypdluF+6KqGhU11R5G` +
`EItXRqKKKf6xNiZOVxsiSW7vF5NqrKV0NDWMqNmfWRixsptYkgx+iV1O/Mn+nldpHSYlq4KCZtRA` +
`lTNRE3E4lDWp6mGjZaUHTWomOtrykdCUKxxgLjTzfKG0J2wqFfeVFjyMzT1CfC255lRRn5HQDT2J` +
`mcDMdQRzeNqL0NBmhEimlTDpnoeV7xGPYSmpZ3vcljQIbYf6SmjJXEwoVT6xpc+k4wkS/7fSC97t` +
`xvcCFbdcTO92X57apoHNSquLs6s3b3s0uHXPpes77+yZnp5eaa7m3PxbJ3YYvwFme3wbyRUAAA==`
noarchRpmPackageContent := rpmContentFromGzipBase64(t, noarchPackageGzipBase64)
// Upload the noarch RPM
req := NewRequestWithBody(t, "PUT", groupURL+"/upload", bytes.NewReader(noarchRpmPackageContent)).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)
// The noarch package should appear in primary.xml when any arch is requested.
// At this point the group contains the existing x86_64 package + this noarch one.
req = NewRequest(t, "GET", groupURL+"/repodata/primary.xml.gz")
resp := MakeRequest(t, req, http.StatusOK)
type PackageSummary struct {
Name string `xml:"name"`
Architecture string `xml:"arch"`
}
type PrimaryMetadata struct {
XMLName xml.Name `xml:"metadata"`
PackageCount int `xml:"packages,attr"`
Packages []PackageSummary `xml:"package"`
}
var primary PrimaryMetadata
decodeGzipXML(t, resp, &primary)
// Both the arch-specific package uploaded earlier and the noarch one must be present.
assert.Equal(t, 2, primary.PackageCount)
assert.Len(t, primary.Packages, 2)
archNames := make([]string, 0, len(primary.Packages))
for _, p := range primary.Packages {
archNames = append(archNames, p.Architecture)
}
assert.Contains(t, archNames, packageArchitecture) // x86_64 from the Upload subtest
assert.Contains(t, archNames, "noarch")
// noarch package must be downloadable regardless of the arch path used.
for _, arch := range []string{"noarch", "x86_64", "aarch64", "my_arch"} {
req = NewRequest(t, "GET", fmt.Sprintf(
"%s/package/%s/%s/%s",
groupURL, noarchPackageName, noarchPackageVersion, arch,
))
MakeRequest(t, req, http.StatusOK)
}
// Clean up: delete via the canonical noarch path.
req = NewRequest(t, "DELETE", fmt.Sprintf(
"%s/package/%s/%s/noarch",
groupURL, noarchPackageName, noarchPackageVersion,
)).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNoContent)
// After deletion, the noarch package must no longer be reachable via any arch.
for _, arch := range []string{"noarch", "x86_64", "aarch64"} {
req = NewRequest(t, "GET", fmt.Sprintf(
"%s/package/%s/%s/%s",
groupURL, noarchPackageName, noarchPackageVersion, arch,
))
MakeRequest(t, req, http.StatusNotFound)
}
// The x86_64 package from the Upload subtest must still be present.
req = NewRequest(t, "GET", groupURL+"/repodata/primary.xml.gz")
resp = MakeRequest(t, req, http.StatusOK)
var primaryAfter PrimaryMetadata
decodeGzipXML(t, resp, &primaryAfter)
assert.Equal(t, 1, primaryAfter.PackageCount)
assert.Equal(t, packageArchitecture, primaryAfter.Packages[0].Architecture)
})
t.Run("Delete", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
@ -765,7 +858,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
t.Run("UploadSign", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
url := groupURL + "/upload?sign=true"
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(packageRpmContent)).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)