Add support for API blob upload of release attachments (#29507)
Fixes #29502 Our endpoint is not Github compatible. https://docs.github.com/en/rest/releases/assets?apiVersion=2022-11-28#upload-a-release-asset --------- Co-authored-by: Giteabot <teabot@gitea.io> (cherry picked from commit 70c126e6184872a6ac63cae2f327fc745b25d1d7)
This commit is contained in:
		
							parent
							
								
									e159297443
								
							
						
					
					
						commit
						47a913d40d
					
				
					 4 changed files with 88 additions and 33 deletions
				
			
		| 
						 | 
				
			
			@ -4,7 +4,9 @@
 | 
			
		|||
package repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
| 
						 | 
				
			
			@ -154,6 +156,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
 | 
			
		|||
	// - application/json
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - multipart/form-data
 | 
			
		||||
	// - application/octet-stream
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: owner
 | 
			
		||||
	//   in: path
 | 
			
		||||
| 
						 | 
				
			
			@ -180,7 +183,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
 | 
			
		|||
	//   in: formData
 | 
			
		||||
	//   description: attachment to upload
 | 
			
		||||
	//   type: file
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   required: false
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "201":
 | 
			
		||||
	//     "$ref": "#/responses/Attachment"
 | 
			
		||||
| 
						 | 
				
			
			@ -202,20 +205,36 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	// Get uploaded file from request
 | 
			
		||||
	file, header, err := ctx.Req.FormFile("attachment")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "GetFile", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
	var content io.ReadCloser
 | 
			
		||||
	var filename string
 | 
			
		||||
	var size int64 = -1
 | 
			
		||||
 | 
			
		||||
	filename := header.Filename
 | 
			
		||||
	if query := ctx.FormString("name"); query != "" {
 | 
			
		||||
		filename = query
 | 
			
		||||
	if strings.HasPrefix(strings.ToLower(ctx.Req.Header.Get("Content-Type")), "multipart/form-data") {
 | 
			
		||||
		file, header, err := ctx.Req.FormFile("attachment")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "GetFile", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		defer file.Close()
 | 
			
		||||
 | 
			
		||||
		content = file
 | 
			
		||||
		size = header.Size
 | 
			
		||||
		filename = header.Filename
 | 
			
		||||
		if name := ctx.FormString("name"); name != "" {
 | 
			
		||||
			filename = name
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		content = ctx.Req.Body
 | 
			
		||||
		filename = ctx.FormString("name")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if filename == "" {
 | 
			
		||||
		ctx.Error(http.StatusBadRequest, "CreateReleaseAttachment", "Could not determine name of attachment.")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create a new attachment and save the file
 | 
			
		||||
	attach, err := attachment.UploadAttachment(ctx, file, setting.Repository.Release.AllowedTypes, header.Size, &repo_model.Attachment{
 | 
			
		||||
	attach, err := attachment.UploadAttachment(ctx, content, setting.Repository.Release.AllowedTypes, size, &repo_model.Attachment{
 | 
			
		||||
		Name:       filename,
 | 
			
		||||
		UploaderID: ctx.Doer.ID,
 | 
			
		||||
		RepoID:     ctx.Repo.Repository.ID,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,14 +44,14 @@ func NewAttachment(ctx context.Context, attach *repo_model.Attachment, file io.R
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// UploadAttachment upload new attachment into storage and update database
 | 
			
		||||
func UploadAttachment(ctx context.Context, file io.Reader, allowedTypes string, fileSize int64, opts *repo_model.Attachment) (*repo_model.Attachment, error) {
 | 
			
		||||
func UploadAttachment(ctx context.Context, file io.Reader, allowedTypes string, fileSize int64, attach *repo_model.Attachment) (*repo_model.Attachment, error) {
 | 
			
		||||
	buf := make([]byte, 1024)
 | 
			
		||||
	n, _ := util.ReadAtMost(file, buf)
 | 
			
		||||
	buf = buf[:n]
 | 
			
		||||
 | 
			
		||||
	if err := upload.Verify(buf, opts.Name, allowedTypes); err != nil {
 | 
			
		||||
	if err := upload.Verify(buf, attach.Name, allowedTypes); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return NewAttachment(ctx, opts, io.MultiReader(bytes.NewReader(buf), file), fileSize)
 | 
			
		||||
	return NewAttachment(ctx, attach, io.MultiReader(bytes.NewReader(buf), file), fileSize)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										6
									
								
								templates/swagger/v1_json.tmpl
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								templates/swagger/v1_json.tmpl
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -12828,7 +12828,8 @@
 | 
			
		|||
      },
 | 
			
		||||
      "post": {
 | 
			
		||||
        "consumes": [
 | 
			
		||||
          "multipart/form-data"
 | 
			
		||||
          "multipart/form-data",
 | 
			
		||||
          "application/octet-stream"
 | 
			
		||||
        ],
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
| 
						 | 
				
			
			@ -12871,8 +12872,7 @@
 | 
			
		|||
            "type": "file",
 | 
			
		||||
            "description": "attachment to upload",
 | 
			
		||||
            "name": "attachment",
 | 
			
		||||
            "in": "formData",
 | 
			
		||||
            "required": true
 | 
			
		||||
            "in": "formData"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -262,24 +262,60 @@ func TestAPIUploadAssetRelease(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
	filename := "image.png"
 | 
			
		||||
	buff := generateImg()
 | 
			
		||||
	body := &bytes.Buffer{}
 | 
			
		||||
 | 
			
		||||
	writer := multipart.NewWriter(body)
 | 
			
		||||
	part, err := writer.CreateFormFile("attachment", filename)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	_, err = io.Copy(part, &buff)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	err = writer.Close()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assetURL := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets", owner.Name, repo.Name, r.ID)
 | 
			
		||||
 | 
			
		||||
	req := NewRequestWithBody(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets?name=test-asset", owner.Name, repo.Name, r.ID), body).
 | 
			
		||||
		AddTokenAuth(token)
 | 
			
		||||
	req.Header.Add("Content-Type", writer.FormDataContentType())
 | 
			
		||||
	resp := MakeRequest(t, req, http.StatusCreated)
 | 
			
		||||
	t.Run("multipart/form-data", func(t *testing.T) {
 | 
			
		||||
		defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
	var attachment *api.Attachment
 | 
			
		||||
	DecodeJSON(t, resp, &attachment)
 | 
			
		||||
		body := &bytes.Buffer{}
 | 
			
		||||
 | 
			
		||||
	assert.EqualValues(t, "test-asset", attachment.Name)
 | 
			
		||||
	assert.EqualValues(t, 104, attachment.Size)
 | 
			
		||||
		writer := multipart.NewWriter(body)
 | 
			
		||||
		part, err := writer.CreateFormFile("attachment", filename)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		_, err = io.Copy(part, bytes.NewReader(buff.Bytes()))
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		err = writer.Close()
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
		req := NewRequestWithBody(t, http.MethodPost, assetURL, bytes.NewReader(body.Bytes())).
 | 
			
		||||
			AddTokenAuth(token).
 | 
			
		||||
			SetHeader("Content-Type", writer.FormDataContentType())
 | 
			
		||||
		resp := MakeRequest(t, req, http.StatusCreated)
 | 
			
		||||
 | 
			
		||||
		var attachment *api.Attachment
 | 
			
		||||
		DecodeJSON(t, resp, &attachment)
 | 
			
		||||
 | 
			
		||||
		assert.EqualValues(t, filename, attachment.Name)
 | 
			
		||||
		assert.EqualValues(t, 104, attachment.Size)
 | 
			
		||||
 | 
			
		||||
		req = NewRequestWithBody(t, http.MethodPost, assetURL+"?name=test-asset", bytes.NewReader(body.Bytes())).
 | 
			
		||||
			AddTokenAuth(token).
 | 
			
		||||
			SetHeader("Content-Type", writer.FormDataContentType())
 | 
			
		||||
		resp = MakeRequest(t, req, http.StatusCreated)
 | 
			
		||||
 | 
			
		||||
		var attachment2 *api.Attachment
 | 
			
		||||
		DecodeJSON(t, resp, &attachment2)
 | 
			
		||||
 | 
			
		||||
		assert.EqualValues(t, "test-asset", attachment2.Name)
 | 
			
		||||
		assert.EqualValues(t, 104, attachment2.Size)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("application/octet-stream", func(t *testing.T) {
 | 
			
		||||
		defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
		req := NewRequestWithBody(t, http.MethodPost, assetURL, bytes.NewReader(buff.Bytes())).
 | 
			
		||||
			AddTokenAuth(token)
 | 
			
		||||
		MakeRequest(t, req, http.StatusBadRequest)
 | 
			
		||||
 | 
			
		||||
		req = NewRequestWithBody(t, http.MethodPost, assetURL+"?name=stream.bin", bytes.NewReader(buff.Bytes())).
 | 
			
		||||
			AddTokenAuth(token)
 | 
			
		||||
		resp := MakeRequest(t, req, http.StatusCreated)
 | 
			
		||||
 | 
			
		||||
		var attachment *api.Attachment
 | 
			
		||||
		DecodeJSON(t, resp, &attachment)
 | 
			
		||||
 | 
			
		||||
		assert.EqualValues(t, "stream.bin", attachment.Name)
 | 
			
		||||
		assert.EqualValues(t, 104, attachment.Size)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue