Skip to content

Commit f15868d

Browse files
fix(packages): serve noarch Alpine index for any requested architecture (#38479)
Fixes #38456 ## Problem The Alpine package registry serves one `APKINDEX.tar.gz` per architecture. When a repository contains only `noarch` packages (no architecture-specific packages), only the `noarch` index is built. Because `apk` substitutes `$ARCH` with the host architecture and requests e.g. `x86_64/APKINDEX.tar.gz`, such a repository returned HTTP 404 and was unusable, matching the report in #38456. ## Fix `GetRepositoryFile` now falls back to the `noarch` index when the requested architecture has no index of its own, mirroring the fallback already present in the sibling `DownloadPackageFile` handler. `noarch` packages are installable on every architecture, so serving them for any requested architecture is correct. The index-build side is unchanged; only the serving path gains the fallback, so mixed repositories (which already merge `noarch` into each per-architecture index) are unaffected. ## AI assistance disclosure This change was implemented with the help of an AI coding assistant, which gitea's CONTRIBUTING.md explicitly welcomes when disclosed. I have reviewed the change, understand it, and can explain and defend it. ## Tests Added a `NoArchOnly` subtest to `TestPackageAlpine` that publishes only a `noarch` package to a fresh repository and asserts that `GET .../x86_64/APKINDEX.tar.gz` now returns `200` (previously `404`) and that the served index lists the noarch package. Verified locally with `go build`/`go vet` on the changed package and a compile of the integration test package (`go test -c`); the full integration run relies on CI. --------- Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
1 parent a77bf48 commit f15868d

2 files changed

Lines changed: 50 additions & 1 deletion

File tree

‎routers/api/packages/alpine/alpine.go‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,34 @@ func GetRepositoryFile(ctx *context.Context) {
6767
return
6868
}
6969

70+
branch := ctx.PathParam("branch")
71+
repository := ctx.PathParam("repository")
72+
architecture := ctx.PathParam("architecture")
73+
7074
s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion(
7175
ctx,
7276
pv,
7377
&packages_service.PackageFileInfo{
7478
Filename: alpine_service.IndexArchiveFilename,
75-
CompositeKey: fmt.Sprintf("%s|%s|%s", ctx.PathParam("branch"), ctx.PathParam("repository"), ctx.PathParam("architecture")),
79+
CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, architecture),
7680
},
7781
ctx.Req.Method,
7882
)
83+
// A repository that only contains "noarch" packages has no per-architecture
84+
// index. Since noarch packages are installable on every architecture, fall
85+
// back to the noarch index so clients requesting their own architecture
86+
// (e.g. x86_64) can still discover them.
87+
if errors.Is(err, util.ErrNotExist) && architecture != alpine_module.NoArch {
88+
s, u, pf, err = packages_service.OpenFileForDownloadByPackageVersion(
89+
ctx,
90+
pv,
91+
&packages_service.PackageFileInfo{
92+
Filename: alpine_service.IndexArchiveFilename,
93+
CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, alpine_module.NoArch),
94+
},
95+
ctx.Req.Method,
96+
)
97+
}
7998
if err != nil {
8099
if errors.Is(err, util.ErrNotExist) {
81100
apiError(ctx, http.StatusNotFound, err)

‎tests/integration/api_packages_alpine_test.go‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,36 @@ AACAX/AKARNTyAAoAAA=`
268268
AddBasicAuth(user.Name)
269269
MakeRequest(t, req, http.StatusNoContent)
270270
})
271+
272+
t.Run("NoArchOnly", func(t *testing.T) {
273+
defer tests.PrintCurrentTest(t)()
274+
275+
// A repository that only contains noarch packages has no per-architecture index,
276+
// but apk always requests the index for its own architecture (e.g. x86_64).
277+
// That request must fall back to the noarch index instead of 404ing.
278+
noarchRepository := repository + "-noarchonly"
279+
280+
req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/%s/%s", rootURL, branch, noarchRepository), bytes.NewReader(noarchContent)).
281+
AddBasicAuth(user.Name)
282+
MakeRequest(t, req, http.StatusCreated)
283+
284+
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/x86_64/APKINDEX.tar.gz", rootURL, branch, noarchRepository))
285+
resp := MakeRequest(t, req, http.StatusOK)
286+
287+
content, err := readIndexContent(resp.Body)
288+
assert.NoError(t, err)
289+
290+
assert.Contains(t, content, "C:Q1kbH5WoIPFccQYyATanaKXd2cJcc=\n")
291+
assert.Contains(t, content, "A:noarch\n")
292+
293+
// The noarch index is still directly retrievable too.
294+
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/noarch/APKINDEX.tar.gz", rootURL, branch, noarchRepository))
295+
MakeRequest(t, req, http.StatusOK)
296+
297+
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/noarch/gitea-noarch-1.4-r0.apk", rootURL, branch, noarchRepository)).
298+
AddBasicAuth(user.Name)
299+
MakeRequest(t, req, http.StatusNoContent)
300+
})
271301
})
272302
}
273303
}

0 commit comments

Comments
 (0)