Skip to content

Commit 1c68585

Browse files
KN4CK3Rlafrikslunny
authored
Integrate alpine noarch packages into other architectures index (#29137)
Fixes #26691 Revert #24972 The alpine package manager expects `noarch` packages in the index of other architectures too. --------- Co-authored-by: Lauris BH <[email protected]> Co-authored-by: Lunny Xiao <[email protected]>
1 parent 756b952 commit 1c68585

File tree

4 files changed

+188
-62
lines changed

4 files changed

+188
-62
lines changed

modules/packages/alpine/metadata.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ const (
3434

3535
RepositoryPackage = "_alpine"
3636
RepositoryVersion = "_repository"
37+
38+
NoArch = "noarch"
3739
)
3840

3941
// https://wiki.alpinelinux.org/wiki/Apk_spec

routers/api/packages/alpine/alpine.go

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func GetRepositoryFile(ctx *context.Context) {
7272
ctx,
7373
pv,
7474
&packages_service.PackageFileInfo{
75-
Filename: alpine_service.IndexFilename,
75+
Filename: alpine_service.IndexArchiveFilename,
7676
CompositeKey: fmt.Sprintf("%s|%s|%s", ctx.Params("branch"), ctx.Params("repository"), ctx.Params("architecture")),
7777
},
7878
)
@@ -182,19 +182,38 @@ func UploadPackageFile(ctx *context.Context) {
182182
}
183183

184184
func DownloadPackageFile(ctx *context.Context) {
185-
pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
185+
branch := ctx.Params("branch")
186+
repository := ctx.Params("repository")
187+
architecture := ctx.Params("architecture")
188+
189+
opts := &packages_model.PackageFileSearchOptions{
186190
OwnerID: ctx.Package.Owner.ID,
187191
PackageType: packages_model.TypeAlpine,
188192
Query: ctx.Params("filename"),
189-
CompositeKey: fmt.Sprintf("%s|%s|%s", ctx.Params("branch"), ctx.Params("repository"), ctx.Params("architecture")),
190-
})
193+
CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, architecture),
194+
}
195+
pfs, _, err := packages_model.SearchFiles(ctx, opts)
191196
if err != nil {
192197
apiError(ctx, http.StatusInternalServerError, err)
193198
return
194199
}
195-
if len(pfs) != 1 {
196-
apiError(ctx, http.StatusNotFound, nil)
197-
return
200+
if len(pfs) == 0 {
201+
// Try again with architecture 'noarch'
202+
if architecture == alpine_module.NoArch {
203+
apiError(ctx, http.StatusNotFound, nil)
204+
return
205+
}
206+
207+
opts.CompositeKey = fmt.Sprintf("%s|%s|%s", branch, repository, alpine_module.NoArch)
208+
if pfs, _, err = packages_model.SearchFiles(ctx, opts); err != nil {
209+
apiError(ctx, http.StatusInternalServerError, err)
210+
return
211+
}
212+
213+
if len(pfs) == 0 {
214+
apiError(ctx, http.StatusNotFound, nil)
215+
return
216+
}
198217
}
199218

200219
s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])

services/packages/alpine/repository.go

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,18 @@ import (
2323
packages_model "code.gitea.io/gitea/models/packages"
2424
alpine_model "code.gitea.io/gitea/models/packages/alpine"
2525
user_model "code.gitea.io/gitea/models/user"
26+
"code.gitea.io/gitea/modules/container"
2627
"code.gitea.io/gitea/modules/json"
2728
packages_module "code.gitea.io/gitea/modules/packages"
2829
alpine_module "code.gitea.io/gitea/modules/packages/alpine"
2930
"code.gitea.io/gitea/modules/util"
3031
packages_service "code.gitea.io/gitea/services/packages"
3132
)
3233

33-
const IndexFilename = "APKINDEX.tar.gz"
34+
const (
35+
IndexFilename = "APKINDEX"
36+
IndexArchiveFilename = IndexFilename + ".tar.gz"
37+
)
3438

3539
// GetOrCreateRepositoryVersion gets or creates the internal repository package
3640
// The Alpine registry needs multiple index files which are stored in this package.
@@ -120,7 +124,22 @@ func BuildSpecificRepositoryFiles(ctx context.Context, ownerID int64, branch, re
120124
return err
121125
}
122126

123-
return buildPackagesIndex(ctx, ownerID, pv, branch, repository, architecture)
127+
architectures := container.SetOf(architecture)
128+
if architecture == alpine_module.NoArch {
129+
// Update all other architectures too when updating the noarch index
130+
additionalArchitectures, err := alpine_model.GetArchitectures(ctx, ownerID, repository)
131+
if err != nil {
132+
return err
133+
}
134+
architectures.AddMultiple(additionalArchitectures...)
135+
}
136+
137+
for architecture := range architectures {
138+
if err := buildPackagesIndex(ctx, ownerID, pv, branch, repository, architecture); err != nil {
139+
return err
140+
}
141+
}
142+
return nil
124143
}
125144

126145
type packageData struct {
@@ -133,8 +152,7 @@ type packageData struct {
133152

134153
type packageCache = map[*packages_model.PackageFile]*packageData
135154

136-
// https://wiki.alpinelinux.org/wiki/Apk_spec#APKINDEX_Format
137-
func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *packages_model.PackageVersion, branch, repository, architecture string) error {
155+
func searchPackageFiles(ctx context.Context, ownerID int64, branch, repository, architecture string) ([]*packages_model.PackageFile, error) {
138156
pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
139157
OwnerID: ownerID,
140158
PackageType: packages_model.TypeAlpine,
@@ -145,13 +163,30 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package
145163
alpine_module.PropertyArchitecture: architecture,
146164
},
147165
})
166+
if err != nil {
167+
return nil, err
168+
}
169+
return pfs, nil
170+
}
171+
172+
// https://wiki.alpinelinux.org/wiki/Apk_spec#APKINDEX_Format
173+
func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *packages_model.PackageVersion, branch, repository, architecture string) error {
174+
pfs, err := searchPackageFiles(ctx, ownerID, branch, repository, architecture)
148175
if err != nil {
149176
return err
150177
}
178+
if architecture != alpine_module.NoArch {
179+
// Add all noarch packages too
180+
noarchFiles, err := searchPackageFiles(ctx, ownerID, branch, repository, alpine_module.NoArch)
181+
if err != nil {
182+
return err
183+
}
184+
pfs = append(pfs, noarchFiles...)
185+
}
151186

152187
// Delete the package indices if there are no packages
153188
if len(pfs) == 0 {
154-
pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, IndexFilename, fmt.Sprintf("%s|%s|%s", branch, repository, architecture))
189+
pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, IndexArchiveFilename, fmt.Sprintf("%s|%s|%s", branch, repository, architecture))
155190
if err != nil && !errors.Is(err, util.ErrNotExist) {
156191
return err
157192
} else if pf == nil {
@@ -206,7 +241,7 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package
206241
fmt.Fprintf(&buf, "C:%s\n", pd.FileMetadata.Checksum)
207242
fmt.Fprintf(&buf, "P:%s\n", pd.Package.Name)
208243
fmt.Fprintf(&buf, "V:%s\n", pd.Version.Version)
209-
fmt.Fprintf(&buf, "A:%s\n", pd.FileMetadata.Architecture)
244+
fmt.Fprintf(&buf, "A:%s\n", architecture)
210245
if pd.VersionMetadata.Description != "" {
211246
fmt.Fprintf(&buf, "T:%s\n", pd.VersionMetadata.Description)
212247
}
@@ -244,7 +279,7 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package
244279

245280
h := sha1.New()
246281

247-
if err := writeGzipStream(io.MultiWriter(unsignedIndexContent, h), "APKINDEX", buf.Bytes(), true); err != nil {
282+
if err := writeGzipStream(io.MultiWriter(unsignedIndexContent, h), IndexFilename, buf.Bytes(), true); err != nil {
248283
return err
249284
}
250285

@@ -299,13 +334,18 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package
299334
repoVersion,
300335
&packages_service.PackageFileCreationInfo{
301336
PackageFileInfo: packages_service.PackageFileInfo{
302-
Filename: IndexFilename,
337+
Filename: IndexArchiveFilename,
303338
CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, architecture),
304339
},
305340
Creator: user_model.NewGhostUser(),
306341
Data: signedIndexContent,
307342
IsLead: false,
308343
OverwriteExisting: true,
344+
Properties: map[string]string{
345+
alpine_module.PropertyBranch: branch,
346+
alpine_module.PropertyRepository: repository,
347+
alpine_module.PropertyArchitecture: architecture,
348+
},
309349
},
310350
)
311351
return err

tests/integration/api_packages_alpine_test.go

Lines changed: 112 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"code.gitea.io/gitea/models/unittest"
2020
user_model "code.gitea.io/gitea/models/user"
2121
alpine_module "code.gitea.io/gitea/modules/packages/alpine"
22+
alpine_service "code.gitea.io/gitea/services/packages/alpine"
2223
"code.gitea.io/gitea/tests"
2324

2425
"github.com/stretchr/testify/assert"
@@ -59,7 +60,34 @@ Djfa/2q5bH4699v++uMAAAAAAAAAAAAAAAAAAAAAAHbgA/eXQh8AKAAA`
5960
content, err := base64.StdEncoding.DecodeString(base64AlpinePackageContent)
6061
assert.NoError(t, err)
6162

62-
branches := []string{"v3.16", "v3.17", "v3.18"}
63+
base64AlpinePackageNoArchContent := `H4sIAAAAAAACA9ML9nT30wsKdtQrLU4t0jUzTUo1NDVP0ysqTtQrKE1ioAYwAAIzExMwDQTotCGI
64+
bWhiampuYmRiaGrMYGBoZGZkxKBgwEAHUFpcklikoMAwQkHLB7eoE40P9n5jvx32t7Dy9rq7x19k
65+
66cJPV38t/h+vWe2jdXy+/PzPT0YTF5z39i4cPFptcLa1C1lD0z/XvrNp6In/7nP4PPCF2pZu8uV
66+
z74QXLxpY1XWJuVFysqVf+PdizccFbD6ZL/QPGXd1Ri1fec2XBNuYfK/rFa6wF/h3dK/W12f8mxP
67+
04iP3aCy+vPx7h9S+5M1LLkWr5M/4ezGt3bDW/FjBp/S9hiKP72s/XrJ0vWtO0zr5wa+D/X8XluW
68+
d7BLP7XS3YUhd8WbPPF/NW3691ONJbXsRb69O7BIMZC96uTri+utC/fbie5J+n7zhCxD4Aep/qet
69+
QnlCZyN8MhNdVNlNl7965R1nExrrGvfI/YQZFx8Dg+d9122hZsYd/24WL/L69OWrDAN/y//nS7im
70+
XEive3v7QeTe433TPj/X71+9yHiV6+E9k++3TL8V0Xoq9panhNt23fLgau/pTOvmKx6bV/pS26+Y
71+
5UP4viyuklYeu4/BZl6rLINe1L/uWuUXcH5z7pa2b9+/rp/v/8dFgc1PL3bO3/iVcrI//J/LMU2X
72+
Nzu1IaMmWXnGp7CmyQIR39d0Nai9/+tdPbfjvmsNH88Tu7uVrvNuJE0wjxfePXGv/KHNXD+mnG0t
73+
yTPu+Na0b5WR9O4t0yMd9T5k6ui7hOyU/jL/4dOn6neLwhdrZIZfcl1ectnGvUTurWDo1vY5Gw9k
74+
PTQLVgcA61F+7gAEAAAfiwgAAAAAAAID7VVNa9wwEPXZv2Ig53hHlizbCzkVkobQJtDkB4wl2SvW
75+
lhdbTpP++oyXQGEPLYU2paTvIs3X05PQSNnmjp4+OrJumjfZ3c3V9efL2+T3AhlaqePIOB0Rc50I
76+
VRSlypUoZIJCKJQJPCVvgGWONLGU5H1CCDDRD+4CU57S6zT5j3eCP9Tyv9T/GsuT/scyLxPAt+z/
77+
aRzjj/J+Fv9HcQZXLriJorPQPAM1i+8tyEzkGZ5PmJ7BMvvQQUt7tx4BPPJH4ccAIpN5Jjj+hSJc
78+
ugZAghDbArco4eH+A+SYq/Sw7wINDi6g89HReRhpMrvVzTzsFZlaV2Hbutmw4zVhmXo2djEe5u1m
79+
c6zNzDikR3mW1a61JepaC0SZHsjsqTsyPoR9GL+GdPbf1iSFtU5Xyu/c4+Q7H04lMfvgI3vT3hsX
80+
5rX40/U9b5CWOA78Mhrq+2ewLjrDp7VNWQbtaF6ZXVWZIhdV09RWOIvU6BqNboSxLSEpkrpQq80x
81+
W1Nla6NavuqtrJQ0sv17D+4L2oD1lwAIAAAfiwgAAAAAAAID7dM/SgNBFAbw6cSAnYXlXsDNm50/
82+
u1METBeIkEBMK87uzKKEJbB/IN7CxhN4AI/gNcRD6BWciI0WSiBGxO/XvA9mile8L+5P7WrkrfN1
83+
049dV1XXbNso0FK+zeDzJC4SxqVSqUwkV4IR51KkLFqxHeia1tZhFfY/cR4V7VXlB9QL0b5HnUXD
84+
6fj4bDI5ncXFpS8WTVfFs9GQD5wVxgrvlde5zMmJRKm89KVRmnhmyJYuo5RMj8Ef8EOV36j/6/yx
85+
/5qnxKJ1J8MZJifskD2Zu+fzxfggmT+83F4c3dw/7u1vtf/1ctl+9e+7dwAAAAAAAAAAAAAAAAAA
86+
AACAX/AKARNTyAAoAAA=`
87+
noarchContent, err := base64.StdEncoding.DecodeString(base64AlpinePackageNoArchContent)
88+
assert.NoError(t, err)
89+
90+
branches := []string{"v3.16", "v3.17"}
6391
repositories := []string{"main", "testing"}
6492

6593
rootURL := fmt.Sprintf("/api/packages/%s/alpine", user.Name)
@@ -139,63 +167,71 @@ Djfa/2q5bH4699v++uMAAAAAAAAAAAAAAAAAAAAAAHbgA/eXQh8AKAAA`
139167
})
140168
})
141169

142-
t.Run("Index", func(t *testing.T) {
143-
defer tests.PrintCurrentTest(t)()
170+
readIndexContent := func(r io.Reader) (string, error) {
171+
br := bufio.NewReader(r)
144172

145-
url := fmt.Sprintf("%s/%s/%s/x86_64/APKINDEX.tar.gz", rootURL, branch, repository)
173+
gzr, err := gzip.NewReader(br)
174+
if err != nil {
175+
return "", err
176+
}
146177

147-
req := NewRequest(t, "GET", url)
148-
resp := MakeRequest(t, req, http.StatusOK)
149-
150-
assert.Condition(t, func() bool {
151-
br := bufio.NewReader(resp.Body)
152-
153-
gzr, err := gzip.NewReader(br)
154-
assert.NoError(t, err)
178+
for {
179+
gzr.Multistream(false)
155180

181+
tr := tar.NewReader(gzr)
156182
for {
157-
gzr.Multistream(false)
183+
hd, err := tr.Next()
184+
if err == io.EOF {
185+
break
186+
}
187+
if err != nil {
188+
return "", err
189+
}
158190

159-
tr := tar.NewReader(gzr)
160-
for {
161-
hd, err := tr.Next()
162-
if err == io.EOF {
163-
break
191+
if hd.Name == alpine_service.IndexFilename {
192+
buf, err := io.ReadAll(tr)
193+
if err != nil {
194+
return "", err
164195
}
165-
assert.NoError(t, err)
166196

167-
if hd.Name == "APKINDEX" {
168-
buf, err := io.ReadAll(tr)
169-
assert.NoError(t, err)
170-
171-
s := string(buf)
172-
173-
assert.Contains(t, s, "C:Q1/se1PjO94hYXbfpNR1/61hVORIc=\n")
174-
assert.Contains(t, s, "P:"+packageName+"\n")
175-
assert.Contains(t, s, "V:"+packageVersion+"\n")
176-
assert.Contains(t, s, "A:x86_64\n")
177-
assert.Contains(t, s, "T:Gitea Test Package\n")
178-
assert.Contains(t, s, "U:https://gitea.io/\n")
179-
assert.Contains(t, s, "L:MIT\n")
180-
assert.Contains(t, s, "S:1353\n")
181-
assert.Contains(t, s, "I:4096\n")
182-
assert.Contains(t, s, "o:gitea-test\n")
183-
assert.Contains(t, s, "m:KN4CK3R <[email protected]>\n")
184-
assert.Contains(t, s, "t:1679498030\n")
185-
186-
return true
187-
}
197+
return string(buf), nil
188198
}
199+
}
189200

190-
err = gzr.Reset(br)
191-
if err == io.EOF {
192-
break
193-
}
194-
assert.NoError(t, err)
201+
err = gzr.Reset(br)
202+
if err == io.EOF {
203+
break
204+
}
205+
if err != nil {
206+
return "", err
195207
}
208+
}
196209

197-
return false
198-
})
210+
return "", io.EOF
211+
}
212+
213+
t.Run("Index", func(t *testing.T) {
214+
defer tests.PrintCurrentTest(t)()
215+
216+
req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/x86_64/APKINDEX.tar.gz", rootURL, branch, repository))
217+
resp := MakeRequest(t, req, http.StatusOK)
218+
219+
content, err := readIndexContent(resp.Body)
220+
assert.NoError(t, err)
221+
222+
assert.Contains(t, content, "C:Q1/se1PjO94hYXbfpNR1/61hVORIc=\n")
223+
assert.Contains(t, content, "P:"+packageName+"\n")
224+
assert.Contains(t, content, "V:"+packageVersion+"\n")
225+
assert.Contains(t, content, "A:x86_64\n")
226+
assert.NotContains(t, content, "A:noarch\n")
227+
assert.Contains(t, content, "T:Gitea Test Package\n")
228+
assert.Contains(t, content, "U:https://gitea.io/\n")
229+
assert.Contains(t, content, "L:MIT\n")
230+
assert.Contains(t, content, "S:1353\n")
231+
assert.Contains(t, content, "I:4096\n")
232+
assert.Contains(t, content, "o:gitea-test\n")
233+
assert.Contains(t, content, "m:KN4CK3R <[email protected]>\n")
234+
assert.Contains(t, content, "t:1679498030\n")
199235
})
200236

201237
t.Run("Download", func(t *testing.T) {
@@ -204,6 +240,35 @@ Djfa/2q5bH4699v++uMAAAAAAAAAAAAAAAAAAAAAAHbgA/eXQh8AKAAA`
204240
req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/x86_64/%s-%s.apk", rootURL, branch, repository, packageName, packageVersion))
205241
MakeRequest(t, req, http.StatusOK)
206242
})
243+
244+
t.Run("NoArch", func(t *testing.T) {
245+
defer tests.PrintCurrentTest(t)()
246+
247+
req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/%s/%s", rootURL, branch, repository), bytes.NewReader(noarchContent)).
248+
AddBasicAuth(user.Name)
249+
MakeRequest(t, req, http.StatusCreated)
250+
251+
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/x86_64/APKINDEX.tar.gz", rootURL, branch, repository))
252+
resp := MakeRequest(t, req, http.StatusOK)
253+
254+
content, err := readIndexContent(resp.Body)
255+
assert.NoError(t, err)
256+
257+
assert.Contains(t, content, "C:Q1/se1PjO94hYXbfpNR1/61hVORIc=\n")
258+
assert.Contains(t, content, "A:x86_64\n")
259+
assert.Contains(t, content, "C:Q1kbH5WoIPFccQYyATanaKXd2cJcc=\n")
260+
assert.NotContains(t, content, "A:noarch\n")
261+
262+
// noarch package should be available with every architecture requested
263+
for _, arch := range []string{alpine_module.NoArch, "x86_64", "my_arch"} {
264+
req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/%s/gitea-noarch-1.4-r0.apk", rootURL, branch, repository, arch))
265+
MakeRequest(t, req, http.StatusOK)
266+
}
267+
268+
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/noarch/gitea-noarch-1.4-r0.apk", rootURL, branch, repository)).
269+
AddBasicAuth(user.Name)
270+
MakeRequest(t, req, http.StatusNoContent)
271+
})
207272
})
208273
}
209274
}

0 commit comments

Comments
 (0)