Allow multiple files in generic packages (#20661)
* Allow multiple files in generic packages. * Add deletion of a single file. * Update docs. * Change version check. Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
parent
75d96f4a02
commit
cc6927b2d8
4 changed files with 254 additions and 76 deletions
|
@ -27,7 +27,7 @@ To authenticate to the Package Registry, you need to provide [custom HTTP header
|
||||||
## Publish a package
|
## Publish a package
|
||||||
|
|
||||||
To publish a generic package perform a HTTP PUT operation with the package content in the request body.
|
To publish a generic package perform a HTTP PUT operation with the package content in the request body.
|
||||||
You cannot publish a package if a package of the same name and version already exists. You must delete the existing package first.
|
You cannot publish a file with the same name twice to a package. You must delete the existing package version first.
|
||||||
|
|
||||||
```
|
```
|
||||||
PUT https://gitea.example.com/api/packages/{owner}/generic/{package_name}/{package_version}/{file_name}
|
PUT https://gitea.example.com/api/packages/{owner}/generic/{package_name}/{package_version}/{file_name}
|
||||||
|
@ -36,9 +36,9 @@ PUT https://gitea.example.com/api/packages/{owner}/generic/{package_name}/{packa
|
||||||
| Parameter | Description |
|
| Parameter | Description |
|
||||||
| ----------------- | ----------- |
|
| ----------------- | ----------- |
|
||||||
| `owner` | The owner of the package. |
|
| `owner` | The owner of the package. |
|
||||||
| `package_name` | The package name. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), or underscores (`_`). |
|
| `package_name` | The package name. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), pluses (`+`), or underscores (`_`). |
|
||||||
| `package_version` | The package version, a non-empty string. |
|
| `package_version` | The package version, a non-empty string without trailing or leading whitespaces. |
|
||||||
| `file_name` | The filename. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), or underscores (`_`). |
|
| `file_name` | The filename. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), pluses (`+`), or underscores (`_`). |
|
||||||
|
|
||||||
Example request using HTTP Basic authentication:
|
Example request using HTTP Basic authentication:
|
||||||
|
|
||||||
|
@ -55,7 +55,8 @@ The server reponds with the following HTTP Status codes.
|
||||||
| HTTP Status Code | Meaning |
|
| HTTP Status Code | Meaning |
|
||||||
| ----------------- | ------- |
|
| ----------------- | ------- |
|
||||||
| `201 Created` | The package has been published. |
|
| `201 Created` | The package has been published. |
|
||||||
| `400 Bad Request` | The package name and/or version are invalid or a package with the same name and version already exist. |
|
| `400 Bad Request` | The package name and/or version and/or file name are invalid. |
|
||||||
|
| `409 Conflict` | A file with the same name exist already in the package. |
|
||||||
|
|
||||||
## Download a package
|
## Download a package
|
||||||
|
|
||||||
|
@ -80,3 +81,67 @@ Example request using HTTP Basic authentication:
|
||||||
curl --user your_username:your_token_or_password \
|
curl --user your_username:your_token_or_password \
|
||||||
https://gitea.example.com/api/packages/testuser/generic/test_package/1.0.0/file.bin
|
https://gitea.example.com/api/packages/testuser/generic/test_package/1.0.0/file.bin
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The server reponds with the following HTTP Status codes.
|
||||||
|
|
||||||
|
| HTTP Status Code | Meaning |
|
||||||
|
| ----------------- | ------- |
|
||||||
|
| `200 OK` | Success |
|
||||||
|
| `404 Not Found` | The package or file was not found. |
|
||||||
|
|
||||||
|
## Delete a package
|
||||||
|
|
||||||
|
To delete a generic package perform a HTTP DELETE operation. This will delete all files of this version.
|
||||||
|
|
||||||
|
```
|
||||||
|
DELETE https://gitea.example.com/api/packages/{owner}/generic/{package_name}/{package_version}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
| ----------------- | ----------- |
|
||||||
|
| `owner` | The owner of the package. |
|
||||||
|
| `package_name` | The package name. |
|
||||||
|
| `package_version` | The package version. |
|
||||||
|
|
||||||
|
Example request using HTTP Basic authentication:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl --user your_username:your_token_or_password -X DELETE \
|
||||||
|
https://gitea.example.com/api/packages/testuser/generic/test_package/1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
The server reponds with the following HTTP Status codes.
|
||||||
|
|
||||||
|
| HTTP Status Code | Meaning |
|
||||||
|
| ----------------- | ------- |
|
||||||
|
| `204 No Content` | Success |
|
||||||
|
| `404 Not Found` | The package was not found. |
|
||||||
|
|
||||||
|
## Delete a package file
|
||||||
|
|
||||||
|
To delete a file of a generic package perform a HTTP DELETE operation. This will delete the package version too if there is no file left.
|
||||||
|
|
||||||
|
```
|
||||||
|
DELETE https://gitea.example.com/api/packages/{owner}/generic/{package_name}/{package_version}/{filename}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
| ----------------- | ----------- |
|
||||||
|
| `owner` | The owner of the package. |
|
||||||
|
| `package_name` | The package name. |
|
||||||
|
| `package_version` | The package version. |
|
||||||
|
| `filename` | The filename. |
|
||||||
|
|
||||||
|
Example request using HTTP Basic authentication:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl --user your_username:your_token_or_password -X DELETE \
|
||||||
|
https://gitea.example.com/api/packages/testuser/generic/test_package/1.0.0/file.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
The server reponds with the following HTTP Status codes.
|
||||||
|
|
||||||
|
| HTTP Status Code | Meaning |
|
||||||
|
| ----------------- | ------- |
|
||||||
|
| `204 No Content` | Success |
|
||||||
|
| `404 Not Found` | The package or file was not found. |
|
||||||
|
|
|
@ -23,16 +23,16 @@ func TestPackageGeneric(t *testing.T) {
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
|
||||||
|
|
||||||
packageName := "te-st_pac.kage"
|
packageName := "te-st_pac.kage"
|
||||||
packageVersion := "1.0.3"
|
packageVersion := "1.0.3-te st"
|
||||||
filename := "fi-le_na.me"
|
filename := "fi-le_na.me"
|
||||||
content := []byte{1, 2, 3}
|
content := []byte{1, 2, 3}
|
||||||
|
|
||||||
url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, packageVersion, filename)
|
url := fmt.Sprintf("/api/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)
|
||||||
|
|
||||||
t.Run("Upload", func(t *testing.T) {
|
t.Run("Upload", func(t *testing.T) {
|
||||||
defer PrintCurrentTest(t)()
|
defer PrintCurrentTest(t)()
|
||||||
|
|
||||||
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
|
req := NewRequestWithBody(t, "PUT", url+"/"+filename, bytes.NewReader(content))
|
||||||
AddBasicAuthHeader(req, user.Name)
|
AddBasicAuthHeader(req, user.Name)
|
||||||
MakeRequest(t, req, http.StatusCreated)
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
@ -55,54 +55,139 @@ func TestPackageGeneric(t *testing.T) {
|
||||||
pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
|
pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(len(content)), pb.Size)
|
assert.Equal(t, int64(len(content)), pb.Size)
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("UploadExists", func(t *testing.T) {
|
t.Run("Exists", func(t *testing.T) {
|
||||||
defer PrintCurrentTest(t)()
|
defer PrintCurrentTest(t)()
|
||||||
|
|
||||||
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
|
req := NewRequestWithBody(t, "PUT", url+"/"+filename, bytes.NewReader(content))
|
||||||
AddBasicAuthHeader(req, user.Name)
|
AddBasicAuthHeader(req, user.Name)
|
||||||
MakeRequest(t, req, http.StatusBadRequest)
|
MakeRequest(t, req, http.StatusConflict)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Additional", func(t *testing.T) {
|
||||||
|
defer PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequestWithBody(t, "PUT", url+"/dummy.bin", bytes.NewReader(content))
|
||||||
|
AddBasicAuthHeader(req, user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
// Check deduplication
|
||||||
|
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, pfs, 2)
|
||||||
|
assert.Equal(t, pfs[0].BlobID, pfs[1].BlobID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidParameter", func(t *testing.T) {
|
||||||
|
defer PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, "invalid+package name", packageVersion, filename), bytes.NewReader(content))
|
||||||
|
AddBasicAuthHeader(req, user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusBadRequest)
|
||||||
|
|
||||||
|
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, "%20test ", filename), bytes.NewReader(content))
|
||||||
|
AddBasicAuthHeader(req, user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusBadRequest)
|
||||||
|
|
||||||
|
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, packageVersion, "inval+id.na me"), bytes.NewReader(content))
|
||||||
|
AddBasicAuthHeader(req, user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusBadRequest)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Download", func(t *testing.T) {
|
t.Run("Download", func(t *testing.T) {
|
||||||
defer PrintCurrentTest(t)()
|
defer PrintCurrentTest(t)()
|
||||||
|
|
||||||
req := NewRequest(t, "GET", url)
|
checkDownloadCount := func(count int64) {
|
||||||
|
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, pvs, 1)
|
||||||
|
assert.Equal(t, count, pvs[0].DownloadCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDownloadCount(0)
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", url+"/"+filename)
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
assert.Equal(t, content, resp.Body.Bytes())
|
assert.Equal(t, content, resp.Body.Bytes())
|
||||||
|
|
||||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric)
|
checkDownloadCount(1)
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, pvs, 1)
|
req = NewRequest(t, "GET", url+"/dummy.bin")
|
||||||
assert.Equal(t, int64(1), pvs[0].DownloadCount)
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
checkDownloadCount(2)
|
||||||
|
|
||||||
|
t.Run("NotExists", func(t *testing.T) {
|
||||||
|
defer PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", url+"/not.found")
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Delete", func(t *testing.T) {
|
t.Run("Delete", func(t *testing.T) {
|
||||||
defer PrintCurrentTest(t)()
|
defer PrintCurrentTest(t)()
|
||||||
|
|
||||||
req := NewRequest(t, "DELETE", url)
|
t.Run("File", func(t *testing.T) {
|
||||||
AddBasicAuthHeader(req, user.Name)
|
defer PrintCurrentTest(t)()
|
||||||
MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
|
||||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric)
|
req := NewRequest(t, "DELETE", url+"/"+filename)
|
||||||
assert.NoError(t, err)
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
assert.Empty(t, pvs)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("DownloadNotExists", func(t *testing.T) {
|
req = NewRequest(t, "DELETE", url+"/"+filename)
|
||||||
defer PrintCurrentTest(t)()
|
AddBasicAuthHeader(req, user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
req := NewRequest(t, "GET", url)
|
req = NewRequest(t, "GET", url+"/"+filename)
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("DeleteNotExists", func(t *testing.T) {
|
req = NewRequest(t, "DELETE", url+"/"+filename)
|
||||||
defer PrintCurrentTest(t)()
|
AddBasicAuthHeader(req, user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
|
||||||
req := NewRequest(t, "DELETE", url)
|
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric)
|
||||||
AddBasicAuthHeader(req, user.Name)
|
assert.NoError(t, err)
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
assert.Len(t, pvs, 1)
|
||||||
|
|
||||||
|
t.Run("RemovesVersion", func(t *testing.T) {
|
||||||
|
defer PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req = NewRequest(t, "DELETE", url+"/dummy.bin")
|
||||||
|
AddBasicAuthHeader(req, user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Empty(t, pvs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Version", func(t *testing.T) {
|
||||||
|
defer PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequestWithBody(t, "PUT", url+"/"+filename, bytes.NewReader(content))
|
||||||
|
AddBasicAuthHeader(req, user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
req = NewRequest(t, "DELETE", url)
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
req = NewRequest(t, "DELETE", url)
|
||||||
|
AddBasicAuthHeader(req, user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Empty(t, pvs)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", url+"/"+filename)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
|
||||||
|
req = NewRequest(t, "DELETE", url)
|
||||||
|
AddBasicAuthHeader(req, user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,12 +156,15 @@ func Routes() *web.Route {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
r.Group("/generic", func() {
|
r.Group("/generic", func() {
|
||||||
r.Group("/{packagename}/{packageversion}/{filename}", func() {
|
r.Group("/{packagename}/{packageversion}", func() {
|
||||||
r.Get("", generic.DownloadPackageFile)
|
r.Delete("", reqPackageAccess(perm.AccessModeWrite), generic.DeletePackage)
|
||||||
r.Group("", func() {
|
r.Group("/{filename}", func() {
|
||||||
r.Put("", generic.UploadPackage)
|
r.Get("", generic.DownloadPackageFile)
|
||||||
r.Delete("", generic.DeletePackage)
|
r.Group("", func() {
|
||||||
}, reqPackageAccess(perm.AccessModeWrite))
|
r.Put("", generic.UploadPackage)
|
||||||
|
r.Delete("", generic.DeletePackageFile)
|
||||||
|
}, reqPackageAccess(perm.AccessModeWrite))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
r.Group("/helm", func() {
|
r.Group("/helm", func() {
|
||||||
|
|
|
@ -31,22 +31,16 @@ func apiError(ctx *context.Context, status int, obj interface{}) {
|
||||||
|
|
||||||
// DownloadPackageFile serves the specific generic package.
|
// DownloadPackageFile serves the specific generic package.
|
||||||
func DownloadPackageFile(ctx *context.Context) {
|
func DownloadPackageFile(ctx *context.Context) {
|
||||||
packageName, packageVersion, filename, err := sanitizeParameters(ctx)
|
|
||||||
if err != nil {
|
|
||||||
apiError(ctx, http.StatusBadRequest, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
||||||
ctx,
|
ctx,
|
||||||
&packages_service.PackageInfo{
|
&packages_service.PackageInfo{
|
||||||
Owner: ctx.Package.Owner,
|
Owner: ctx.Package.Owner,
|
||||||
PackageType: packages_model.TypeGeneric,
|
PackageType: packages_model.TypeGeneric,
|
||||||
Name: packageName,
|
Name: ctx.Params("packagename"),
|
||||||
Version: packageVersion,
|
Version: ctx.Params("packageversion"),
|
||||||
},
|
},
|
||||||
&packages_service.PackageFileInfo{
|
&packages_service.PackageFileInfo{
|
||||||
Filename: filename,
|
Filename: ctx.Params("filename"),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -65,9 +59,17 @@ func DownloadPackageFile(ctx *context.Context) {
|
||||||
// UploadPackage uploads the specific generic package.
|
// UploadPackage uploads the specific generic package.
|
||||||
// Duplicated packages get rejected.
|
// Duplicated packages get rejected.
|
||||||
func UploadPackage(ctx *context.Context) {
|
func UploadPackage(ctx *context.Context) {
|
||||||
packageName, packageVersion, filename, err := sanitizeParameters(ctx)
|
packageName := ctx.Params("packagename")
|
||||||
if err != nil {
|
filename := ctx.Params("filename")
|
||||||
apiError(ctx, http.StatusBadRequest, err)
|
|
||||||
|
if !packageNameRegex.MatchString(packageName) || !filenameRegex.MatchString(filename) {
|
||||||
|
apiError(ctx, http.StatusBadRequest, errors.New("Invalid package name or filename"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
packageVersion := ctx.Params("packageversion")
|
||||||
|
if packageVersion != strings.TrimSpace(packageVersion) {
|
||||||
|
apiError(ctx, http.StatusBadRequest, errors.New("Invalid package version"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +90,7 @@ func UploadPackage(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
defer buf.Close()
|
defer buf.Close()
|
||||||
|
|
||||||
_, _, err = packages_service.CreatePackageAndAddFile(
|
_, _, err = packages_service.CreatePackageOrAddFileToExisting(
|
||||||
&packages_service.PackageCreationInfo{
|
&packages_service.PackageCreationInfo{
|
||||||
PackageInfo: packages_service.PackageInfo{
|
PackageInfo: packages_service.PackageInfo{
|
||||||
Owner: ctx.Package.Owner,
|
Owner: ctx.Package.Owner,
|
||||||
|
@ -107,8 +109,8 @@ func UploadPackage(ctx *context.Context) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == packages_model.ErrDuplicatePackageVersion {
|
if err == packages_model.ErrDuplicatePackageFile {
|
||||||
apiError(ctx, http.StatusBadRequest, err)
|
apiError(ctx, http.StatusConflict, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
apiError(ctx, http.StatusInternalServerError, err)
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
@ -120,19 +122,13 @@ func UploadPackage(ctx *context.Context) {
|
||||||
|
|
||||||
// DeletePackage deletes the specific generic package.
|
// DeletePackage deletes the specific generic package.
|
||||||
func DeletePackage(ctx *context.Context) {
|
func DeletePackage(ctx *context.Context) {
|
||||||
packageName, packageVersion, _, err := sanitizeParameters(ctx)
|
err := packages_service.RemovePackageVersionByNameAndVersion(
|
||||||
if err != nil {
|
|
||||||
apiError(ctx, http.StatusBadRequest, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = packages_service.RemovePackageVersionByNameAndVersion(
|
|
||||||
ctx.Doer,
|
ctx.Doer,
|
||||||
&packages_service.PackageInfo{
|
&packages_service.PackageInfo{
|
||||||
Owner: ctx.Package.Owner,
|
Owner: ctx.Package.Owner,
|
||||||
PackageType: packages_model.TypeGeneric,
|
PackageType: packages_model.TypeGeneric,
|
||||||
Name: packageName,
|
Name: ctx.Params("packagename"),
|
||||||
Version: packageVersion,
|
Version: ctx.Params("packageversion"),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -144,21 +140,50 @@ func DeletePackage(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Status(http.StatusOK)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sanitizeParameters(ctx *context.Context) (string, string, string, error) {
|
// DeletePackageFile deletes the specific file of a generic package.
|
||||||
packageName := ctx.Params("packagename")
|
func DeletePackageFile(ctx *context.Context) {
|
||||||
filename := ctx.Params("filename")
|
pv, pf, err := func() (*packages_model.PackageVersion, *packages_model.PackageFile, error) {
|
||||||
|
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeGeneric, ctx.Params("packagename"), ctx.Params("packageversion"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if !packageNameRegex.MatchString(packageName) || !filenameRegex.MatchString(filename) {
|
pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, ctx.Params("filename"), packages_model.EmptyFileKey)
|
||||||
return "", "", "", errors.New("Invalid package name or filename")
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pv, pf, nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
|
||||||
|
apiError(ctx, http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
packageVersion := strings.TrimSpace(ctx.Params("packageversion"))
|
pfs, err := packages_model.GetFilesByVersionID(ctx, pv.ID)
|
||||||
if packageVersion == "" {
|
if err != nil {
|
||||||
return "", "", "", errors.New("Invalid package version")
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return packageName, packageVersion, filename, nil
|
if len(pfs) == 1 {
|
||||||
|
if err := packages_service.RemovePackageVersion(ctx.Doer, pv); err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue