From 89742c49135e47372e272596bd536d5d1244721f Mon Sep 17 00:00:00 2001 From: Exploding Dragon Date: Fri, 27 Sep 2024 08:21:22 +0000 Subject: [PATCH] feat: add architecture-specific removal support for arch package (#5351) - [x] add architecture-specific removal support - [x] Fix upload competition - [x] Fix not checking input when downloading docs: https://codeberg.org/forgejo/docs/pulls/874 ### Release notes - [ ] I do not want this change to show in the release notes. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5351 Reviewed-by: Earl Warren Co-authored-by: Exploding Dragon Co-committed-by: Exploding Dragon --- modules/packages/arch/metadata.go | 69 ++++++++++----------- routers/api/packages/api.go | 12 ++-- routers/api/packages/arch/arch.go | 47 +++++++++----- services/packages/arch/repository.go | 65 ++++++++++--------- templates/package/content/arch.tmpl | 4 +- templates/package/metadata/arch.tmpl | 4 +- tests/integration/api_packages_arch_test.go | 50 ++++++++++++--- 7 files changed, 151 insertions(+), 100 deletions(-) diff --git a/modules/packages/arch/metadata.go b/modules/packages/arch/metadata.go index 0e0867031..6cdde75cd 100644 --- a/modules/packages/arch/metadata.go +++ b/modules/packages/arch/metadata.go @@ -39,8 +39,8 @@ const ( var ( reName = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+$`) reVer = regexp.MustCompile(`^[a-zA-Z0-9:_.+]+-+[0-9]+$`) - reOptDep = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?[a-zA-Z0-9@._+-]+)?(:.*)?$`) - rePkgVer = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?[a-zA-Z0-9@._+-]+)?$`) + reOptDep = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?([0-9]+:)?[a-zA-Z0-9@._+-]+)?(:.*)?$`) + rePkgVer = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?([0-9]+:)?[a-zA-Z0-9@._+-]+)?$`) magicZSTD = []byte{0x28, 0xB5, 0x2F, 0xFD} magicXZ = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A} @@ -71,7 +71,7 @@ type VersionMetadata struct { Conflicts []string `json:"conflicts,omitempty"` Replaces []string `json:"replaces,omitempty"` Backup []string `json:"backup,omitempty"` - Xdata []string `json:"xdata,omitempty"` + XData []string `json:"xdata,omitempty"` } // FileMetadata Metadata related to specific package file. @@ -125,7 +125,7 @@ func ParsePackage(r *packages.HashedBuffer) (*Package, error) { defer tarball.Close() var pkg *Package - var mtree bool + var mTree bool for { f, err := tarball.Read() @@ -135,24 +135,24 @@ func ParsePackage(r *packages.HashedBuffer) (*Package, error) { if err != nil { return nil, err } - defer f.Close() - switch f.Name() { case ".PKGINFO": pkg, err = ParsePackageInfo(tarballType, f) if err != nil { + _ = f.Close() return nil, err } case ".MTREE": - mtree = true + mTree = true } + _ = f.Close() } if pkg == nil { return nil, util.NewInvalidArgumentErrorf(".PKGINFO file not found") } - if !mtree { + if !mTree { return nil, util.NewInvalidArgumentErrorf(".MTREE file not found") } @@ -220,7 +220,7 @@ func ParsePackageInfo(compressType string, r io.Reader) (*Package, error) { case "replaces": p.VersionMetadata.Replaces = append(p.VersionMetadata.Replaces, value) case "xdata": - p.VersionMetadata.Xdata = append(p.VersionMetadata.Xdata, value) + p.VersionMetadata.XData = append(p.VersionMetadata.XData, value) case "builddate": bd, err := strconv.ParseInt(value, 10, 64) if err != nil { @@ -260,48 +260,43 @@ func ValidatePackageSpec(p *Package) error { return util.NewInvalidArgumentErrorf("invalid project URL") } } - for _, cd := range p.VersionMetadata.CheckDepends { - if !rePkgVer.MatchString(cd) { - return util.NewInvalidArgumentErrorf("invalid check dependency: %s", cd) + for _, checkDepend := range p.VersionMetadata.CheckDepends { + if !rePkgVer.MatchString(checkDepend) { + return util.NewInvalidArgumentErrorf("invalid check dependency: %s", checkDepend) } } - for _, d := range p.VersionMetadata.Depends { - if !rePkgVer.MatchString(d) { - return util.NewInvalidArgumentErrorf("invalid dependency: %s", d) + for _, depend := range p.VersionMetadata.Depends { + if !rePkgVer.MatchString(depend) { + return util.NewInvalidArgumentErrorf("invalid dependency: %s", depend) } } - for _, md := range p.VersionMetadata.MakeDepends { - if !rePkgVer.MatchString(md) { - return util.NewInvalidArgumentErrorf("invalid make dependency: %s", md) + for _, makeDepend := range p.VersionMetadata.MakeDepends { + if !rePkgVer.MatchString(makeDepend) { + return util.NewInvalidArgumentErrorf("invalid make dependency: %s", makeDepend) } } - for _, p := range p.VersionMetadata.Provides { - if !rePkgVer.MatchString(p) { - return util.NewInvalidArgumentErrorf("invalid provides: %s", p) + for _, provide := range p.VersionMetadata.Provides { + if !rePkgVer.MatchString(provide) { + return util.NewInvalidArgumentErrorf("invalid provides: %s", provide) } } - for _, p := range p.VersionMetadata.Conflicts { - if !rePkgVer.MatchString(p) { - return util.NewInvalidArgumentErrorf("invalid conflicts: %s", p) + for _, conflict := range p.VersionMetadata.Conflicts { + if !rePkgVer.MatchString(conflict) { + return util.NewInvalidArgumentErrorf("invalid conflicts: %s", conflict) } } - for _, p := range p.VersionMetadata.Replaces { - if !rePkgVer.MatchString(p) { - return util.NewInvalidArgumentErrorf("invalid replaces: %s", p) + for _, replace := range p.VersionMetadata.Replaces { + if !rePkgVer.MatchString(replace) { + return util.NewInvalidArgumentErrorf("invalid replaces: %s", replace) } } - for _, p := range p.VersionMetadata.Replaces { - if !rePkgVer.MatchString(p) { - return util.NewInvalidArgumentErrorf("invalid xdata: %s", p) + for _, optDepend := range p.VersionMetadata.OptDepends { + if !reOptDep.MatchString(optDepend) { + return util.NewInvalidArgumentErrorf("invalid optional dependency: %s", optDepend) } } - for _, od := range p.VersionMetadata.OptDepends { - if !reOptDep.MatchString(od) { - return util.NewInvalidArgumentErrorf("invalid optional dependency: %s", od) - } - } - for _, bf := range p.VersionMetadata.Backup { - if strings.HasPrefix(bf, "/") { + for _, b := range p.VersionMetadata.Backup { + if strings.HasPrefix(b, "/") { return util.NewInvalidArgumentErrorf("backup file contains leading forward slash") } } diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 76a8fd471..c72f81270 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -175,18 +175,20 @@ func CommonRoutes() *web.Route { arch.PushPackage(ctx) return } else if isDelete { - if groupLen < 2 { + if groupLen < 3 { ctx.Status(http.StatusBadRequest) return } - if groupLen == 2 { + if groupLen == 3 { ctx.SetParams("group", "") ctx.SetParams("package", pathGroups[0]) ctx.SetParams("version", pathGroups[1]) + ctx.SetParams("arch", pathGroups[2]) } else { - ctx.SetParams("group", strings.Join(pathGroups[:groupLen-2], "/")) - ctx.SetParams("package", pathGroups[groupLen-2]) - ctx.SetParams("version", pathGroups[groupLen-1]) + ctx.SetParams("group", strings.Join(pathGroups[:groupLen-3], "/")) + ctx.SetParams("package", pathGroups[groupLen-3]) + ctx.SetParams("version", pathGroups[groupLen-2]) + ctx.SetParams("arch", pathGroups[groupLen-1]) } reqPackageAccess(perm.AccessModeWrite)(ctx) if ctx.Written() { diff --git a/routers/api/packages/arch/arch.go b/routers/api/packages/arch/arch.go index 2d3481a33..15fcc37c7 100644 --- a/routers/api/packages/arch/arch.go +++ b/routers/api/packages/arch/arch.go @@ -9,12 +9,14 @@ import ( "fmt" "io" "net/http" + "path/filepath" "regexp" "strings" packages_model "code.gitea.io/gitea/models/packages" packages_module "code.gitea.io/gitea/modules/packages" arch_module "code.gitea.io/gitea/modules/packages/arch" + "code.gitea.io/gitea/modules/sync" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/services/context" @@ -25,6 +27,8 @@ import ( var ( archPkgOrSig = regexp.MustCompile(`^.*\.pkg\.tar\.\w+(\.sig)*$`) archDBOrSig = regexp.MustCompile(`^.*.db(\.tar\.gz)*(\.sig)*$`) + + locker = sync.NewExclusivePool() ) func apiError(ctx *context.Context, status int, obj any) { @@ -33,6 +37,14 @@ func apiError(ctx *context.Context, status int, obj any) { }) } +func refreshLocker(ctx *context.Context, group string) func() { + key := fmt.Sprintf("pkg_%d_arch_pkg_%s", ctx.Package.Owner.ID, group) + locker.CheckIn(key) + return func() { + locker.CheckOut(key) + } +} + func GetRepositoryKey(ctx *context.Context) { _, pub, err := arch_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID) if err != nil { @@ -48,7 +60,8 @@ func GetRepositoryKey(ctx *context.Context) { func PushPackage(ctx *context.Context) { group := ctx.Params("group") - + releaser := refreshLocker(ctx, group) + defer releaser() upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusInternalServerError, err) @@ -154,6 +167,7 @@ func PushPackage(ctx *context.Context) { }) if err != nil { apiError(ctx, http.StatusInternalServerError, err) + return } if err = arch_service.BuildPacmanDB(ctx, ctx.Package.Owner.ID, group, p.FileMetadata.Arch); err != nil { apiError(ctx, http.StatusInternalServerError, err) @@ -169,7 +183,7 @@ func GetPackageOrDB(ctx *context.Context) { arch = ctx.Params("arch") ) if archPkgOrSig.MatchString(file) { - pkg, err := arch_service.GetPackageFile(ctx, group, file, ctx.Package.Owner.ID) + pkg, u, pf, err := arch_service.GetPackageFile(ctx, group, file, ctx.Package.Owner.ID) if err != nil { if errors.Is(err, util.ErrNotExist) { apiError(ctx, http.StatusNotFound, err) @@ -178,15 +192,12 @@ func GetPackageOrDB(ctx *context.Context) { } return } - - ctx.ServeContent(pkg, &context.ServeHeaderOptions{ - Filename: file, - }) + helper.ServePackageFile(ctx, pkg, u, pf) return } if archDBOrSig.MatchString(file) { - pkg, err := arch_service.GetPackageDBFile(ctx, group, arch, ctx.Package.Owner.ID, + pkg, u, pf, err := arch_service.GetPackageDBFile(ctx, group, arch, ctx.Package.Owner.ID, strings.HasSuffix(file, ".sig")) if err != nil { if errors.Is(err, util.ErrNotExist) { @@ -196,9 +207,7 @@ func GetPackageOrDB(ctx *context.Context) { } return } - ctx.ServeContent(pkg, &context.ServeHeaderOptions{ - Filename: file, - }) + helper.ServePackageFile(ctx, pkg, u, pf) return } @@ -207,10 +216,13 @@ func GetPackageOrDB(ctx *context.Context) { func RemovePackage(ctx *context.Context) { var ( - group = ctx.Params("group") - pkg = ctx.Params("package") - ver = ctx.Params("version") + group = ctx.Params("group") + pkg = ctx.Params("package") + ver = ctx.Params("version") + pkgArch = ctx.Params("arch") ) + releaser := refreshLocker(ctx, group) + defer releaser() pv, err := packages_model.GetVersionByNameAndVersion( ctx, ctx.Package.Owner.ID, packages_model.TypeArch, pkg, ver, ) @@ -229,7 +241,13 @@ func RemovePackage(ctx *context.Context) { } deleted := false for _, file := range files { - if file.CompositeKey == group { + extName := fmt.Sprintf("-%s.pkg.tar%s", pkgArch, filepath.Ext(file.LowerName)) + if strings.HasSuffix(file.LowerName, ".sig") { + extName = fmt.Sprintf("-%s.pkg.tar%s.sig", pkgArch, + filepath.Ext(strings.TrimSuffix(file.LowerName, filepath.Ext(file.LowerName)))) + } + if file.CompositeKey == group && + strings.HasSuffix(file.LowerName, extName) { deleted = true err := packages_service.RemovePackageFileAndVersionIfUnreferenced(ctx, ctx.ContextUser, file) if err != nil { @@ -242,6 +260,7 @@ func RemovePackage(ctx *context.Context) { err = arch_service.BuildCustomRepositoryFiles(ctx, ctx.Package.Owner.ID, group) if err != nil { apiError(ctx, http.StatusInternalServerError, err) + return } ctx.Status(http.StatusNoContent) } else { diff --git a/services/packages/arch/repository.go b/services/packages/arch/repository.go index de7246742..58433ab5c 100644 --- a/services/packages/arch/repository.go +++ b/services/packages/arch/repository.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "net/url" "os" "path/filepath" "sort" @@ -20,6 +21,7 @@ import ( packages_module "code.gitea.io/gitea/modules/packages" arch_module "code.gitea.io/gitea/modules/packages/arch" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/sync" "code.gitea.io/gitea/modules/util" packages_service "code.gitea.io/gitea/services/packages" @@ -28,6 +30,8 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/packet" ) +var locker = sync.NewExclusivePool() + func GetOrCreateRepositoryVersion(ctx context.Context, ownerID int64) (*packages_model.PackageVersion, error) { return packages_service.GetOrCreateInternalPackageVersion(ctx, ownerID, packages_model.TypeArch, arch_module.RepositoryPackage, arch_module.RepositoryVersion) } @@ -101,6 +105,9 @@ func NewFileSign(ctx context.Context, ownerID int64, input io.Reader) (*packages // BuildPacmanDB Create db signature cache func BuildPacmanDB(ctx context.Context, ownerID int64, group, arch string) error { + key := fmt.Sprintf("pkg_%d_arch_db_%s", ownerID, group) + locker.CheckIn(key) + defer locker.CheckOut(key) pv, err := GetOrCreateRepositoryVersion(ctx, ownerID) if err != nil { return err @@ -173,15 +180,18 @@ func createDB(ctx context.Context, ownerID int64, group, arch string) (*packages if err != nil { return nil, err } + defer db.Close() gw := gzip.NewWriter(db) + defer gw.Close() tw := tar.NewWriter(gw) + defer tw.Close() count := 0 for _, pkg := range pkgs { versions, err := packages_model.GetVersionsByPackageName( ctx, ownerID, packages_model.TypeArch, pkg.Name, ) if err != nil { - return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err) + return nil, err } sort.Slice(versions, func(i, j int) bool { return versions[i].CreatedUnix > versions[j].CreatedUnix @@ -190,7 +200,7 @@ func createDB(ctx context.Context, ownerID int64, group, arch string) (*packages for _, ver := range versions { files, err := packages_model.GetFilesByVersionID(ctx, ver.ID) if err != nil { - return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err) + return nil, err } var pf *packages_model.PackageFile for _, file := range files { @@ -213,7 +223,7 @@ func createDB(ctx context.Context, ownerID int64, group, arch string) (*packages ctx, packages_model.PropertyTypeFile, pf.ID, arch_module.PropertyDescription, ) if err != nil { - return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err) + return nil, err } if len(pps) >= 1 { meta := []byte(pps[0].Value) @@ -223,60 +233,50 @@ func createDB(ctx context.Context, ownerID int64, group, arch string) (*packages Mode: int64(os.ModePerm), } if err = tw.WriteHeader(header); err != nil { - return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err) + return nil, err } if _, err := tw.Write(meta); err != nil { - return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err) + return nil, err } count++ break } } } - defer gw.Close() - defer tw.Close() if count == 0 { - return nil, errors.Join(db.Close(), io.EOF) + return nil, io.EOF } return db, nil } // GetPackageFile Get data related to provided filename and distribution, for package files // update download counter. -func GetPackageFile(ctx context.Context, group, file string, ownerID int64) (io.ReadSeekCloser, error) { - pf, err := getPackageFile(ctx, group, file, ownerID) - if err != nil { - return nil, err +func GetPackageFile(ctx context.Context, group, file string, ownerID int64) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { + fileSplit := strings.Split(file, "-") + if len(fileSplit) <= 3 { + return nil, nil, nil, errors.New("invalid file format, need ---.pkg.") } - - filestream, _, _, err := packages_service.GetPackageFileStream(ctx, pf) - return filestream, err -} - -// Ejects parameters required to get package file property from file name. -func getPackageFile(ctx context.Context, group, file string, ownerID int64) (*packages_model.PackageFile, error) { var ( - splt = strings.Split(file, "-") - pkgname = strings.Join(splt[0:len(splt)-3], "-") - vername = splt[len(splt)-3] + "-" + splt[len(splt)-2] + pkgName = strings.Join(fileSplit[0:len(fileSplit)-3], "-") + pkgVer = fileSplit[len(fileSplit)-3] + "-" + fileSplit[len(fileSplit)-2] ) - - version, err := packages_model.GetVersionByNameAndVersion(ctx, ownerID, packages_model.TypeArch, pkgname, vername) + version, err := packages_model.GetVersionByNameAndVersion(ctx, ownerID, packages_model.TypeArch, pkgName, pkgVer) if err != nil { - return nil, err + return nil, nil, nil, err } - pkgfile, err := packages_model.GetFileForVersionByName(ctx, version.ID, file, group) + pkgFile, err := packages_model.GetFileForVersionByName(ctx, version.ID, file, group) if err != nil { - return nil, err + return nil, nil, nil, err } - return pkgfile, nil + + return packages_service.GetPackageFileStream(ctx, pkgFile) } -func GetPackageDBFile(ctx context.Context, group, arch string, ownerID int64, signFile bool) (io.ReadSeekCloser, error) { +func GetPackageDBFile(ctx context.Context, group, arch string, ownerID int64, signFile bool) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { pv, err := GetOrCreateRepositoryVersion(ctx, ownerID) if err != nil { - return nil, err + return nil, nil, nil, err } fileName := fmt.Sprintf("%s.db", arch) if signFile { @@ -284,10 +284,9 @@ func GetPackageDBFile(ctx context.Context, group, arch string, ownerID int64, si } file, err := packages_model.GetFileForVersionByName(ctx, pv.ID, fileName, group) if err != nil { - return nil, err + return nil, nil, nil, err } - filestream, _, _, err := packages_service.GetPackageFileStream(ctx, file) - return filestream, err + return packages_service.GetPackageFileStream(ctx, file) } // GetOrCreateKeyPair gets or creates the PGP keys used to sign repository metadata files diff --git a/templates/package/content/arch.tmpl b/templates/package/content/arch.tmpl index bcc24b585..6138b1d69 100644 --- a/templates/package/content/arch.tmpl +++ b/templates/package/content/arch.tmpl @@ -16,11 +16,11 @@ pacman-key --lsign-key '{{$.SignMail}}'

 {{- if gt (len $.Groups) 1 -}}
-# {{ctx.Locale.Tr "packages.arch.pacman.repo.multi"  $.PackageDescriptor.Package.LowerName}}
+# {{ctx.Locale.Tr "packages.arch.pacman.repo.multi" $.PackageDescriptor.Package.LowerName}}
 
 {{end -}}
 {{- $GroupSize := (len .Groups) -}}
-{{-  range $i,$v :=  .Groups -}}
+{{-  range $i,$v := .Groups -}}
 {{- if gt $i 0}}
 {{end -}}{{- if gt $GroupSize 1 -}}
 # {{ctx.Locale.Tr "packages.arch.pacman.repo.multi.item" .}}
diff --git a/templates/package/metadata/arch.tmpl b/templates/package/metadata/arch.tmpl
index 822973eb7..89001b979 100644
--- a/templates/package/metadata/arch.tmpl
+++ b/templates/package/metadata/arch.tmpl
@@ -1,4 +1,4 @@
 {{if eq .PackageDescriptor.Package.Type "arch"}}
-	{{range .PackageDescriptor.Metadata.License}}
{{svg "octicon-law" 16 "gt-mr-3"}} {{.}}
{{end}} - {{if .PackageDescriptor.Metadata.ProjectURL}}
{{svg "octicon-link-external" 16 "mr-3"}} {{ctx.Locale.Tr "packages.details.project_site"}}
{{end}} + {{if .PackageDescriptor.Metadata.ProjectURL}}
{{svg "octicon-link-external" 16 "tw-mr-2"}} {{ctx.Locale.Tr "packages.details.project_site"}}
{{end}} + {{range .PackageDescriptor.Metadata.License}}
{{svg "octicon-law" 16 "tw-mr-2"}} {{.}}
{{end}} {{end}} diff --git a/tests/integration/api_packages_arch_test.go b/tests/integration/api_packages_arch_test.go index af275de44..2cf018641 100644 --- a/tests/integration/api_packages_arch_test.go +++ b/tests/integration/api_packages_arch_test.go @@ -14,6 +14,7 @@ import ( "io" "net/http" "strings" + "sync" "testing" "testing/fstest" @@ -258,11 +259,15 @@ HMhNSS1IzUsBcpJAPFAwwUXSM0u4BjoaR8EoGAWjgGQAAILFeyQADAAA AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusCreated) - req = NewRequestWithBody(t, "DELETE", rootURL+"/base/notfound/1.0.0-1", nil). + req = NewRequestWithBody(t, "DELETE", rootURL+"/base/notfound/1.0.0-1/any", nil). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNotFound) - req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1", nil). + req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1/x86_64", nil). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1/any", nil). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNoContent) @@ -270,12 +275,22 @@ HMhNSS1IzUsBcpJAPFAwwUXSM0u4BjoaR8EoGAWjgGQAAILFeyQADAAA respPkg := MakeRequest(t, req, http.StatusOK) files, err := listTarGzFiles(respPkg.Body.Bytes()) require.NoError(t, err) - require.Len(t, files, 1) // other pkg in L225 + require.Len(t, files, 1) - req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1", nil). + req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1/any", nil). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", groupURL+"/x86_64/base.db") + + req = NewRequest(t, "GET", groupURL+"/x86_64/base.db"). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1/aarch64", nil). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequest(t, "GET", groupURL+"/aarch64/base.db"). + AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNotFound) }) @@ -294,12 +309,33 @@ HMhNSS1IzUsBcpJAPFAwwUXSM0u4BjoaR8EoGAWjgGQAAILFeyQADAAA resp := MakeRequest(t, req, http.StatusOK) require.Equal(t, pkgs[key], resp.Body.Bytes()) - req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1", nil). + req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1/any", nil). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNoContent) }) } } + t.Run("Concurrent Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + var wg sync.WaitGroup + + targets := []string{"any", "aarch64", "x86_64"} + for _, tag := range targets { + wg.Add(1) + go func(i string) { + defer wg.Done() + req := NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgs[i])). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusCreated) + }(tag) + } + wg.Wait() + for _, target := range targets { + req := NewRequestWithBody(t, "DELETE", rootURL+"/test/1.0.0-1/"+target, nil). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNoContent) + } + }) } func getProperty(data, key string) string { @@ -318,10 +354,10 @@ func getProperty(data, key string) string { func listTarGzFiles(data []byte) (fstest.MapFS, error) { reader, err := gzip.NewReader(bytes.NewBuffer(data)) - defer reader.Close() if err != nil { return nil, err } + defer reader.Close() tarRead := tar.NewReader(reader) files := make(fstest.MapFS) for {