diff --git a/routers/api/packages/alpine/alpine.go b/routers/api/packages/alpine/alpine.go index d3560dc4b99..9a593f7caa2 100644 --- a/routers/api/packages/alpine/alpine.go +++ b/routers/api/packages/alpine/alpine.go @@ -67,15 +67,34 @@ func GetRepositoryFile(ctx *context.Context) { return } + branch := ctx.PathParam("branch") + repository := ctx.PathParam("repository") + architecture := ctx.PathParam("architecture") + s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion( ctx, pv, &packages_service.PackageFileInfo{ Filename: alpine_service.IndexArchiveFilename, - CompositeKey: fmt.Sprintf("%s|%s|%s", ctx.PathParam("branch"), ctx.PathParam("repository"), ctx.PathParam("architecture")), + CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, architecture), }, ctx.Req.Method, ) + // A repository that only contains "noarch" packages has no per-architecture + // index. Since noarch packages are installable on every architecture, fall + // back to the noarch index so clients requesting their own architecture + // (e.g. x86_64) can still discover them. + if errors.Is(err, util.ErrNotExist) && architecture != alpine_module.NoArch { + s, u, pf, err = packages_service.OpenFileForDownloadByPackageVersion( + ctx, + pv, + &packages_service.PackageFileInfo{ + Filename: alpine_service.IndexArchiveFilename, + CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, alpine_module.NoArch), + }, + ctx.Req.Method, + ) + } if err != nil { if errors.Is(err, util.ErrNotExist) { apiError(ctx, http.StatusNotFound, err) diff --git a/tests/integration/api_packages_alpine_test.go b/tests/integration/api_packages_alpine_test.go index 0fe158e7f04..5b097534e28 100644 --- a/tests/integration/api_packages_alpine_test.go +++ b/tests/integration/api_packages_alpine_test.go @@ -268,6 +268,36 @@ AACAX/AKARNTyAAoAAA=` AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNoContent) }) + + t.Run("NoArchOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // A repository that only contains noarch packages has no per-architecture index, + // but apk always requests the index for its own architecture (e.g. x86_64). + // That request must fall back to the noarch index instead of 404ing. + noarchRepository := repository + "-noarchonly" + + req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/%s/%s", rootURL, branch, noarchRepository), bytes.NewReader(noarchContent)). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/x86_64/APKINDEX.tar.gz", rootURL, branch, noarchRepository)) + resp := MakeRequest(t, req, http.StatusOK) + + content, err := readIndexContent(resp.Body) + assert.NoError(t, err) + + assert.Contains(t, content, "C:Q1kbH5WoIPFccQYyATanaKXd2cJcc=\n") + assert.Contains(t, content, "A:noarch\n") + + // The noarch index is still directly retrievable too. + req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/noarch/APKINDEX.tar.gz", rootURL, branch, noarchRepository)) + MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/noarch/gitea-noarch-1.4-r0.apk", rootURL, branch, noarchRepository)). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNoContent) + }) }) } }