Skip to content

Refactor maven package registry #33049

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions models/packages/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,18 @@ func GetPackageByID(ctx context.Context, packageID int64) (*Package, error) {
return p, nil
}

// UpdatePackageNameByID updates the package's name, it is only for internal usage, for example: rename some legacy packages
func UpdatePackageNameByID(ctx context.Context, ownerID int64, packageType Type, packageID int64, name string) error {
var cond builder.Cond = builder.Eq{
"package.id": packageID,
"package.owner_id": ownerID,
"package.type": packageType,
"package.is_internal": false,
}
_, err := db.GetEngine(ctx).Where(cond).Update(&Package{Name: name, LowerName: strings.ToLower(name)})
return err
}

// GetPackageByName gets a package by name
func GetPackageByName(ctx context.Context, ownerID int64, packageType Type, name string) (*Package, error) {
var cond builder.Cond = builder.Eq{
Expand Down
5 changes: 5 additions & 0 deletions modules/web/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ func TestRouter(t *testing.T) {
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
handlerMark: "match-path",
})
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1%2fd2/fn", resultStruct{
method: "GET",
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1%2fd2/fn", "dir": "d1%2fd2", "file": "fn"},
handlerMark: "match-path",
})
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/000", resultStruct{
method: "GET",
pathParams: map[string]string{"reponame": "the-repo", "username": "the-user", "*": "d1/d2/000"},
Expand Down
2 changes: 0 additions & 2 deletions routers/api/packages/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,8 +430,6 @@ func CommonRoutes() *web.Router {
r.Post("/api/charts", reqPackageAccess(perm.AccessModeWrite), helm.UploadPackage)
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/maven", func() {
// FIXME: this path design is not right.
// It should be `/.../{groupId}/{artifactId}/{version}`, but not `/.../{groupId}-{artifactId}/{version}`
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile)
r.Get("/*", maven.DownloadPackageFile)
r.Head("/*", maven.ProvidePackageFileHeader)
Expand Down
76 changes: 56 additions & 20 deletions routers/api/packages/maven/maven.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"errors"
"io"
"net/http"
"path/filepath"
"path"
"regexp"
"sort"
"strconv"
Expand All @@ -25,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
maven_module "code.gitea.io/gitea/modules/packages/maven"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
Expand All @@ -44,7 +45,7 @@ const (

var (
errInvalidParameters = errors.New("request parameters are invalid")
illegalCharacters = regexp.MustCompile(`[\\/:"<>|?\*]`)
illegalCharacters = regexp.MustCompile(`[\\/:"<>|?*]`)
)

func apiError(ctx *context.Context, status int, obj any) {
Expand Down Expand Up @@ -85,8 +86,10 @@ func handlePackageFile(ctx *context.Context, serveContent bool) {
func serveMavenMetadata(ctx *context.Context, params parameters) {
// /com/foo/project/maven-metadata.xml[.md5/.sha1/.sha256/.sha512]

packageName := params.GroupID + "-" + params.ArtifactID
pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, packageName)
pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageName())
if errors.Is(err, util.ErrNotExist) {
pvs, err = packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageNameLegacy())
}
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
Expand Down Expand Up @@ -116,10 +119,10 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {

latest := pds[len(pds)-1]
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
lastModifed := latest.Version.CreatedUnix.AsTime().UTC().Format(http.TimeFormat)
ctx.Resp.Header().Set("Last-Modified", lastModifed)
lastModified := latest.Version.CreatedUnix.AsTime().UTC().Format(http.TimeFormat)
ctx.Resp.Header().Set("Last-Modified", lastModified)

ext := strings.ToLower(filepath.Ext(params.Filename))
ext := strings.ToLower(path.Ext(params.Filename))
if isChecksumExtension(ext) {
var hash []byte
switch ext {
Expand Down Expand Up @@ -147,11 +150,12 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
}

func servePackageFile(ctx *context.Context, params parameters, serveContent bool) {
packageName := params.GroupID + "-" + params.ArtifactID

pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, packageName, params.Version)
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageName(), params.Version)
if errors.Is(err, util.ErrNotExist) {
pv, err = packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageNameLegacy(), params.Version)
}
if err != nil {
if err == packages_model.ErrPackageNotExist {
if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err)
} else {
apiError(ctx, http.StatusInternalServerError, err)
Expand All @@ -161,14 +165,14 @@ func servePackageFile(ctx *context.Context, params parameters, serveContent bool

filename := params.Filename

ext := strings.ToLower(filepath.Ext(filename))
ext := strings.ToLower(path.Ext(filename))
if isChecksumExtension(ext) {
filename = filename[:len(filename)-len(ext)]
}

pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, filename, packages_model.EmptyFileKey)
if err != nil {
if err == packages_model.ErrPackageFileNotExist {
if errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err)
} else {
apiError(ctx, http.StatusInternalServerError, err)
Expand Down Expand Up @@ -238,15 +242,17 @@ func UploadPackageFile(ctx *context.Context) {
return
}

log.Trace("Parameters: %+v", params)

// Ignore the package index /<name>/maven-metadata.xml
if params.IsMeta && params.Version == "" {
ctx.Status(http.StatusOK)
return
}

packageName := params.GroupID + "-" + params.ArtifactID
packageName := params.toInternalPackageName()
if ctx.FormBool("use_legacy_package_name") {
// for testing purpose only
packageName = params.toInternalPackageNameLegacy()
}

// for the same package, only one upload at a time
releaser, err := globallock.Lock(ctx, mavenPkgNameKey(packageName))
Expand Down Expand Up @@ -274,13 +280,26 @@ func UploadPackageFile(ctx *context.Context) {
Creator: ctx.Doer,
}

ext := filepath.Ext(params.Filename)
// old maven package uses "groupId-artifactId" as package name, so we need to update to the new format "groupId:artifactId"
legacyPackage, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageNameLegacy())
if err != nil && !errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusInternalServerError, err)
return
} else if legacyPackage != nil {
err = packages_model.UpdatePackageNameByID(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, legacyPackage.ID, packageName)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
}

ext := path.Ext(params.Filename)

// Do not upload checksum files but compare the hashes.
if isChecksumExtension(ext) {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, pvci.Owner.ID, pvci.PackageType, pvci.Name, pvci.Version)
if err != nil {
if err == packages_model.ErrPackageNotExist {
if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err)
return
}
Expand All @@ -289,7 +308,7 @@ func UploadPackageFile(ctx *context.Context) {
}
pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, params.Filename[:len(params.Filename)-len(ext)], packages_model.EmptyFileKey)
if err != nil {
if err == packages_model.ErrPackageFileNotExist {
if errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err)
return
}
Expand Down Expand Up @@ -343,7 +362,7 @@ func UploadPackageFile(ctx *context.Context) {

if pvci.Metadata != nil {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, pvci.Owner.ID, pvci.PackageType, pvci.Name, pvci.Version)
if err != nil && err != packages_model.ErrPackageNotExist {
if err != nil && !errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusInternalServerError, err)
return
}
Expand Down Expand Up @@ -399,9 +418,26 @@ type parameters struct {
IsMeta bool
}

func (p *parameters) toInternalPackageName() string {
// there cuold be 2 choices: "/" or ":"
// Maven says: "groupId:artifactId:version" in their document: https://maven.apache.org/pom.html#Maven_Coordinates
// but it would be slightly ugly in URL: "/-/packages/maven/group-id%3Aartifact-id"
return p.GroupID + ":" + p.ArtifactID
}

func (p *parameters) toInternalPackageNameLegacy() string {
return p.GroupID + "-" + p.ArtifactID
}

func extractPathParameters(ctx *context.Context) (parameters, error) {
parts := strings.Split(ctx.PathParam("*"), "/")

// formats:
// * /com/group/id/artifactId/maven-metadata.xml[.md5|.sha1|.sha256|.sha512]
// * /com/group/id/artifactId/version-SNAPSHOT/maven-metadata.xml[.md5|.sha1|.sha256|.sha512]
// * /com/group/id/artifactId/version/any-file
// * /com/group/id/artifactId/version-SNAPSHOT/any-file

p := parameters{
Filename: parts[len(parts)-1],
}
Expand Down
Loading
Loading