mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	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:
		@@ -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 New Issue
	
	Block a user