Skip to content

Commit 723598b

Browse files
KN4CK3RGiteaBot
andauthored
Implement Cargo HTTP index (#24452)
This implements the HTTP index [RFC](https://rust-lang.github.io/rfcs/2789-sparse-index.html) for Cargo registries. Currently this is a preview feature and you need to use the nightly of `cargo`: `cargo +nightly -Z sparse-registry update` See rust-lang/cargo#9069 for more information. --------- Co-authored-by: Giteabot <teabot@gitea.io>
1 parent 48e3e38 commit 723598b

File tree

4 files changed

+141
-43
lines changed

4 files changed

+141
-43
lines changed

routers/api/packages/api.go

+6
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ func CommonRoutes(ctx gocontext.Context) *web.Route {
119119
r.Get("/owners", cargo.ListOwners)
120120
})
121121
})
122+
r.Get("/config.json", cargo.RepositoryConfig)
123+
r.Get("/1/{package}", cargo.EnumeratePackageVersions)
124+
r.Get("/2/{package}", cargo.EnumeratePackageVersions)
125+
// Use dummy placeholders because these parts are not of interest
126+
r.Get("/3/{_}/{package}", cargo.EnumeratePackageVersions)
127+
r.Get("/{_}/{__}/{package}", cargo.EnumeratePackageVersions)
122128
}, reqPackageAccess(perm.AccessModeRead))
123129
r.Group("/chef", func() {
124130
r.Group("/api/v1", func() {

routers/api/packages/cargo/cargo.go

+30
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package cargo
55

66
import (
7+
"errors"
78
"fmt"
89
"net/http"
910
"strconv"
@@ -45,6 +46,35 @@ func apiError(ctx *context.Context, status int, obj interface{}) {
4546
})
4647
}
4748

49+
// https://rust-lang.github.io/rfcs/2789-sparse-index.html
50+
func RepositoryConfig(ctx *context.Context) {
51+
ctx.JSON(http.StatusOK, cargo_service.BuildConfig(ctx.Package.Owner))
52+
}
53+
54+
func EnumeratePackageVersions(ctx *context.Context) {
55+
p, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.TypeCargo, ctx.Params("package"))
56+
if err != nil {
57+
if errors.Is(err, util.ErrNotExist) {
58+
apiError(ctx, http.StatusNotFound, err)
59+
} else {
60+
apiError(ctx, http.StatusInternalServerError, err)
61+
}
62+
return
63+
}
64+
65+
b, err := cargo_service.BuildPackageIndex(ctx, p)
66+
if err != nil {
67+
apiError(ctx, http.StatusInternalServerError, err)
68+
return
69+
}
70+
if b == nil {
71+
apiError(ctx, http.StatusNotFound, nil)
72+
return
73+
}
74+
75+
ctx.PlainTextBytes(http.StatusOK, b.Bytes())
76+
}
77+
4878
type SearchResult struct {
4979
Crates []*SearchResultCrate `json:"crates"`
5080
Meta SearchResultMeta `json:"meta"`

services/packages/cargo/index.go

+26-10
Original file line numberDiff line numberDiff line change
@@ -137,21 +137,21 @@ type IndexVersionEntry struct {
137137
Links string `json:"links,omitempty"`
138138
}
139139

140-
func addOrUpdatePackageIndex(ctx context.Context, t *files_service.TemporaryUploadRepository, p *packages_model.Package) error {
140+
func BuildPackageIndex(ctx context.Context, p *packages_model.Package) (*bytes.Buffer, error) {
141141
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
142142
PackageID: p.ID,
143143
Sort: packages_model.SortVersionAsc,
144144
})
145145
if err != nil {
146-
return fmt.Errorf("SearchVersions[%s]: %w", p.Name, err)
146+
return nil, fmt.Errorf("SearchVersions[%s]: %w", p.Name, err)
147147
}
148148
if len(pvs) == 0 {
149-
return nil
149+
return nil, nil
150150
}
151151

152152
pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
153153
if err != nil {
154-
return fmt.Errorf("GetPackageDescriptors[%s]: %w", p.Name, err)
154+
return nil, fmt.Errorf("GetPackageDescriptors[%s]: %w", p.Name, err)
155155
}
156156

157157
var b bytes.Buffer
@@ -179,14 +179,26 @@ func addOrUpdatePackageIndex(ctx context.Context, t *files_service.TemporaryUplo
179179
Links: metadata.Links,
180180
})
181181
if err != nil {
182-
return err
182+
return nil, err
183183
}
184184

185185
b.Write(entry)
186186
b.WriteString("\n")
187187
}
188188

189-
return writeObjectToIndex(t, BuildPackagePath(pds[0].Package.LowerName), &b)
189+
return &b, nil
190+
}
191+
192+
func addOrUpdatePackageIndex(ctx context.Context, t *files_service.TemporaryUploadRepository, p *packages_model.Package) error {
193+
b, err := BuildPackageIndex(ctx, p)
194+
if err != nil {
195+
return err
196+
}
197+
if b == nil {
198+
return nil
199+
}
200+
201+
return writeObjectToIndex(t, BuildPackagePath(p.LowerName), b)
190202
}
191203

192204
func getOrCreateIndexRepository(ctx context.Context, doer, owner *user_model.User) (*repo_model.Repository, error) {
@@ -212,6 +224,13 @@ type Config struct {
212224
APIURL string `json:"api"`
213225
}
214226

227+
func BuildConfig(owner *user_model.User) *Config {
228+
return &Config{
229+
DownloadURL: setting.AppURL + "api/packages/" + owner.Name + "/cargo/api/v1/crates",
230+
APIURL: setting.AppURL + "api/packages/" + owner.Name + "/cargo",
231+
}
232+
}
233+
215234
func createOrUpdateConfigFile(ctx context.Context, repo *repo_model.Repository, doer, owner *user_model.User) error {
216235
return alterRepositoryContent(
217236
ctx,
@@ -220,10 +239,7 @@ func createOrUpdateConfigFile(ctx context.Context, repo *repo_model.Repository,
220239
"Initialize Cargo Config",
221240
func(t *files_service.TemporaryUploadRepository) error {
222241
var b bytes.Buffer
223-
err := json.NewEncoder(&b).Encode(Config{
224-
DownloadURL: setting.AppURL + "api/packages/" + owner.Name + "/cargo/api/v1/crates",
225-
APIURL: setting.AppURL + "api/packages/" + owner.Name + "/cargo",
226-
})
242+
err := json.NewEncoder(&b).Encode(BuildConfig(owner))
227243
if err != nil {
228244
return err
229245
}

tests/integration/api_packages_cargo_test.go

+79-33
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func testPackageCargo(t *testing.T, _ *neturl.URL) {
9898
url := fmt.Sprintf("%s/api/v1/crates", root)
9999

100100
t.Run("Index", func(t *testing.T) {
101-
t.Run("Config", func(t *testing.T) {
101+
t.Run("Git/Config", func(t *testing.T) {
102102
defer tests.PrintCurrentTest(t)()
103103

104104
content := readGitContent(t, cargo_service.ConfigFileName)
@@ -110,6 +110,20 @@ func testPackageCargo(t *testing.T, _ *neturl.URL) {
110110
assert.Equal(t, url, config.DownloadURL)
111111
assert.Equal(t, root, config.APIURL)
112112
})
113+
114+
t.Run("HTTP/Config", func(t *testing.T) {
115+
defer tests.PrintCurrentTest(t)()
116+
117+
req := NewRequest(t, "GET", root+"/"+cargo_service.ConfigFileName)
118+
resp := MakeRequest(t, req, http.StatusOK)
119+
120+
var config cargo_service.Config
121+
err := json.Unmarshal(resp.Body.Bytes(), &config)
122+
assert.NoError(t, err)
123+
124+
assert.Equal(t, url, config.DownloadURL)
125+
assert.Equal(t, root, config.APIURL)
126+
})
113127
})
114128

115129
t.Run("Upload", func(t *testing.T) {
@@ -192,40 +206,72 @@ func testPackageCargo(t *testing.T, _ *neturl.URL) {
192206
MakeRequest(t, req, http.StatusConflict)
193207

194208
t.Run("Index", func(t *testing.T) {
195-
t.Run("Entry", func(t *testing.T) {
196-
defer tests.PrintCurrentTest(t)()
197-
198-
content := readGitContent(t, cargo_service.BuildPackagePath(packageName))
199-
200-
var entry cargo_service.IndexVersionEntry
201-
err := json.Unmarshal([]byte(content), &entry)
202-
assert.NoError(t, err)
203-
204-
assert.Equal(t, packageName, entry.Name)
205-
assert.Equal(t, packageVersion, entry.Version)
206-
assert.Equal(t, pb.HashSHA256, entry.FileChecksum)
207-
assert.False(t, entry.Yanked)
208-
assert.Len(t, entry.Dependencies, 1)
209-
dep := entry.Dependencies[0]
210-
assert.Equal(t, "dep", dep.Name)
211-
assert.Equal(t, "1.0", dep.Req)
212-
assert.Equal(t, "normal", dep.Kind)
213-
assert.True(t, dep.DefaultFeatures)
214-
assert.Empty(t, dep.Features)
215-
assert.False(t, dep.Optional)
216-
assert.Nil(t, dep.Target)
217-
assert.NotNil(t, dep.Registry)
218-
assert.Equal(t, "https://gitea.io/user/_cargo-index", *dep.Registry)
219-
assert.Nil(t, dep.Package)
209+
t.Run("Git", func(t *testing.T) {
210+
t.Run("Entry", func(t *testing.T) {
211+
defer tests.PrintCurrentTest(t)()
212+
213+
content := readGitContent(t, cargo_service.BuildPackagePath(packageName))
214+
215+
var entry cargo_service.IndexVersionEntry
216+
err := json.Unmarshal([]byte(content), &entry)
217+
assert.NoError(t, err)
218+
219+
assert.Equal(t, packageName, entry.Name)
220+
assert.Equal(t, packageVersion, entry.Version)
221+
assert.Equal(t, pb.HashSHA256, entry.FileChecksum)
222+
assert.False(t, entry.Yanked)
223+
assert.Len(t, entry.Dependencies, 1)
224+
dep := entry.Dependencies[0]
225+
assert.Equal(t, "dep", dep.Name)
226+
assert.Equal(t, "1.0", dep.Req)
227+
assert.Equal(t, "normal", dep.Kind)
228+
assert.True(t, dep.DefaultFeatures)
229+
assert.Empty(t, dep.Features)
230+
assert.False(t, dep.Optional)
231+
assert.Nil(t, dep.Target)
232+
assert.NotNil(t, dep.Registry)
233+
assert.Equal(t, "https://gitea.io/user/_cargo-index", *dep.Registry)
234+
assert.Nil(t, dep.Package)
235+
})
236+
237+
t.Run("Rebuild", func(t *testing.T) {
238+
defer tests.PrintCurrentTest(t)()
239+
240+
err := cargo_service.RebuildIndex(db.DefaultContext, user, user)
241+
assert.NoError(t, err)
242+
243+
_ = readGitContent(t, cargo_service.BuildPackagePath(packageName))
244+
})
220245
})
221246

222-
t.Run("Rebuild", func(t *testing.T) {
223-
defer tests.PrintCurrentTest(t)()
224-
225-
err := cargo_service.RebuildIndex(db.DefaultContext, user, user)
226-
assert.NoError(t, err)
227-
228-
_ = readGitContent(t, cargo_service.BuildPackagePath(packageName))
247+
t.Run("HTTP", func(t *testing.T) {
248+
t.Run("Entry", func(t *testing.T) {
249+
defer tests.PrintCurrentTest(t)()
250+
251+
req := NewRequest(t, "GET", root+"/"+cargo_service.BuildPackagePath(packageName))
252+
resp := MakeRequest(t, req, http.StatusOK)
253+
254+
var entry cargo_service.IndexVersionEntry
255+
err := json.Unmarshal(resp.Body.Bytes(), &entry)
256+
assert.NoError(t, err)
257+
258+
assert.Equal(t, packageName, entry.Name)
259+
assert.Equal(t, packageVersion, entry.Version)
260+
assert.Equal(t, pb.HashSHA256, entry.FileChecksum)
261+
assert.False(t, entry.Yanked)
262+
assert.Len(t, entry.Dependencies, 1)
263+
dep := entry.Dependencies[0]
264+
assert.Equal(t, "dep", dep.Name)
265+
assert.Equal(t, "1.0", dep.Req)
266+
assert.Equal(t, "normal", dep.Kind)
267+
assert.True(t, dep.DefaultFeatures)
268+
assert.Empty(t, dep.Features)
269+
assert.False(t, dep.Optional)
270+
assert.Nil(t, dep.Target)
271+
assert.NotNil(t, dep.Registry)
272+
assert.Equal(t, "https://gitea.io/user/_cargo-index", *dep.Registry)
273+
assert.Nil(t, dep.Package)
274+
})
229275
})
230276
})
231277
})

0 commit comments

Comments
 (0)