Template
1
0
Fork 0
mirror of https://codeberg.org/forgejo/forgejo synced 2024-11-22 09:54:24 +01:00

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 <earl-warren@noreply.codeberg.org>
Co-authored-by: Exploding Dragon <explodingfkl@gmail.com>
Co-committed-by: Exploding Dragon <explodingfkl@gmail.com>
This commit is contained in:
Exploding Dragon 2024-09-27 08:21:22 +00:00 committed by Earl Warren
parent 89d9307d56
commit 89742c4913
7 changed files with 151 additions and 100 deletions

View file

@ -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")
}
}

View file

@ -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() {

View file

@ -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
}
@ -210,7 +219,10 @@ func RemovePackage(ctx *context.Context) {
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 {

View file

@ -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 <name>-<version>-<release>-<arch>.pkg.<archive>")
}
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

View file

@ -1,4 +1,4 @@
{{if eq .PackageDescriptor.Package.Type "arch"}}
{{range .PackageDescriptor.Metadata.License}}<div class="item" title="{{$.locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "gt-mr-3"}} {{.}}</div>{{end}}
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
{{range .PackageDescriptor.Metadata.License}}<div class="item" title="{{$.locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.}}</div>{{end}}
{{end}}

View file

@ -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 {