mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-23 15:21:35 +01:00
Merge pull request #5367 from steven-zou/fix_issue_multipart
Fix the multipart form data content missing issue by refill the content again before passing to the backend server
This commit is contained in:
commit
1c383473e1
@ -16,6 +16,7 @@ import (
|
||||
|
||||
const (
|
||||
readmeFileName = "README.md"
|
||||
valuesFileName = "values.yaml"
|
||||
)
|
||||
|
||||
//ChartVersionDetails keeps the detailed data info of the chart version
|
||||
@ -72,7 +73,7 @@ func (cho *ChartOperator) GetChartDetails(content []byte) (*ChartVersionDetails,
|
||||
values = parseRawValues([]byte(chartData.Values.GetRaw()))
|
||||
if len(values) > 0 {
|
||||
//Append values.yaml file
|
||||
files["values.yaml"] = chartData.Values.Raw
|
||||
files[valuesFileName] = chartData.Values.Raw
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,6 +75,10 @@ func (cc *ChartClient) GetContent(addr string) ([]byte, error) {
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
if err := extractError(content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to retrieve content from '%s' with error: %s", fullURI.Path, content)
|
||||
}
|
||||
|
||||
|
@ -49,19 +49,19 @@ func (mh *ManipulationHandler) ListCharts(w http.ResponseWriter, req *http.Reque
|
||||
|
||||
content, err := mh.apiClient.GetContent(url)
|
||||
if err != nil {
|
||||
writeInternalError(w, err)
|
||||
WriteInternalError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
chartList, err := mh.chartOperator.GetChartList(content)
|
||||
if err != nil {
|
||||
writeInternalError(w, err)
|
||||
WriteInternalError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(chartList)
|
||||
if err != nil {
|
||||
writeInternalError(w, err)
|
||||
WriteInternalError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ func (mh *ManipulationHandler) GetChart(w http.ResponseWriter, req *http.Request
|
||||
func (mh *ManipulationHandler) GetChartVersion(w http.ResponseWriter, req *http.Request) {
|
||||
chartV, err := mh.getChartVersion(req.URL.String())
|
||||
if err != nil {
|
||||
writeInternalError(w, err)
|
||||
WriteInternalError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -97,20 +97,20 @@ func (mh *ManipulationHandler) GetChartVersion(w http.ResponseWriter, req *http.
|
||||
}
|
||||
|
||||
if len(strings.TrimSpace(namespace)) == 0 {
|
||||
writeInternalError(w, errors.New("failed to extract namespace from the request"))
|
||||
WriteInternalError(w, errors.New("failed to extract namespace from the request"))
|
||||
return
|
||||
}
|
||||
|
||||
content, err := mh.getChartVersionContent(namespace, chartV.URLs[0])
|
||||
if err != nil {
|
||||
writeInternalError(w, err)
|
||||
WriteInternalError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
//Process bytes and get more details of chart version
|
||||
chartDetails, err = mh.chartOperator.GetChartDetails(content)
|
||||
if err != nil {
|
||||
writeInternalError(w, err)
|
||||
WriteInternalError(w, err)
|
||||
return
|
||||
}
|
||||
chartDetails.Metadata = chartV
|
||||
@ -127,7 +127,7 @@ func (mh *ManipulationHandler) GetChartVersion(w http.ResponseWriter, req *http.
|
||||
|
||||
bytes, err := json.Marshal(chartDetails)
|
||||
if err != nil {
|
||||
writeInternalError(w, err)
|
||||
WriteInternalError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -60,14 +60,14 @@ func (rh *RepositoryHandler) GetIndexFile(w http.ResponseWriter, req *http.Reque
|
||||
//Get project manager references
|
||||
projectMgr, err := filter.GetProjectManager(req)
|
||||
if err != nil {
|
||||
writeInternalError(w, err)
|
||||
WriteInternalError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
//Get all the projects
|
||||
results, err := projectMgr.List(nil)
|
||||
if err != nil {
|
||||
writeInternalError(w, err)
|
||||
WriteInternalError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -179,7 +179,7 @@ LOOP:
|
||||
//All the threads are done
|
||||
//Met an error
|
||||
if err != nil {
|
||||
writeInternalError(w, err)
|
||||
WriteInternalError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -195,7 +195,7 @@ LOOP:
|
||||
|
||||
bytes, err := yaml.Marshal(mergedIndexFile)
|
||||
if err != nil {
|
||||
writeInternalError(w, err)
|
||||
WriteInternalError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -14,8 +14,8 @@ const (
|
||||
contentTypeJSON = "application/json"
|
||||
)
|
||||
|
||||
//Write error to http client
|
||||
func writeError(w http.ResponseWriter, code int, err error) {
|
||||
//WriteError writes error to http client
|
||||
func WriteError(w http.ResponseWriter, code int, err error) {
|
||||
errorObj := make(map[string]string)
|
||||
errorObj["error"] = err.Error()
|
||||
errorContent, _ := json.Marshal(errorObj)
|
||||
@ -24,9 +24,9 @@ func writeError(w http.ResponseWriter, code int, err error) {
|
||||
w.Write(errorContent)
|
||||
}
|
||||
|
||||
//StatusCode == 500
|
||||
func writeInternalError(w http.ResponseWriter, err error) {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
//WriteInternalError writes error with statusCode == 500
|
||||
func WriteInternalError(w http.ResponseWriter, err error) {
|
||||
WriteError(w, http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
//Write JSON data to http client
|
||||
@ -36,6 +36,26 @@ func writeJSONData(w http.ResponseWriter, data []byte) {
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
//Extract error object '{"error": "****---***"}' from the content if existing
|
||||
//nil error will be returned if it does exist
|
||||
func extractError(content []byte) error {
|
||||
if len(content) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
errorObj := make(map[string]string)
|
||||
err := json.Unmarshal(content, &errorObj)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if errText, ok := errorObj["error"]; ok {
|
||||
return errors.New(errText)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//Parse the redis configuration to the beego cache pattern
|
||||
//Config pattern is "address:port[,weight,password,db_index]"
|
||||
func parseRedisConfig(redisConfigV string) (string, error) {
|
||||
|
@ -1,9 +1,13 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@ -25,6 +29,10 @@ const (
|
||||
accessLevelWrite
|
||||
accessLevelAll
|
||||
accessLevelSystem
|
||||
|
||||
formFieldNameForChart = "chart"
|
||||
formFiledNameForProv = "prov"
|
||||
headerContentType = "Content-Type"
|
||||
)
|
||||
|
||||
//chartController is a singleton instance
|
||||
@ -162,6 +170,21 @@ func (cra *ChartRepositoryAPI) UploadChartVersion() {
|
||||
return
|
||||
}
|
||||
|
||||
//Rewrite file content
|
||||
formFiles := make([]formFile, 0)
|
||||
formFiles = append(formFiles,
|
||||
formFile{
|
||||
formField: formFieldNameForChart,
|
||||
mustHave: true,
|
||||
},
|
||||
formFile{
|
||||
formField: formFiledNameForProv,
|
||||
})
|
||||
if err := cra.rewriteFileContent(formFiles, cra.Ctx.Request); err != nil {
|
||||
chartserver.WriteInternalError(cra.Ctx.ResponseWriter, err)
|
||||
return
|
||||
}
|
||||
|
||||
chartController.GetManipulationHandler().UploadChartVersion(cra.Ctx.ResponseWriter, cra.Ctx.Request)
|
||||
}
|
||||
|
||||
@ -172,6 +195,18 @@ func (cra *ChartRepositoryAPI) UploadChartProvFile() {
|
||||
return
|
||||
}
|
||||
|
||||
//Rewrite file content
|
||||
formFiles := make([]formFile, 0)
|
||||
formFiles = append(formFiles,
|
||||
formFile{
|
||||
formField: formFiledNameForProv,
|
||||
mustHave: true,
|
||||
})
|
||||
if err := cra.rewriteFileContent(formFiles, cra.Ctx.Request); err != nil {
|
||||
chartserver.WriteInternalError(cra.Ctx.ResponseWriter, err)
|
||||
return
|
||||
}
|
||||
|
||||
chartController.GetManipulationHandler().UploadProvenanceFile(cra.Ctx.ResponseWriter, cra.Ctx.Request)
|
||||
}
|
||||
|
||||
@ -310,3 +345,63 @@ func initializeChartController() (*chartserver.Controller, error) {
|
||||
|
||||
return controller, nil
|
||||
}
|
||||
|
||||
//formFile is used to represent the uploaded files in the form
|
||||
type formFile struct {
|
||||
//form field key contains the form file
|
||||
formField string
|
||||
|
||||
//flag to indicate if the file identified by the 'formField'
|
||||
//must exist
|
||||
mustHave bool
|
||||
}
|
||||
|
||||
//If the files are uploaded with multipart/form-data mimetype, beego will extract the data
|
||||
//from the request automatically. Then the request passed to the backend server with proxying
|
||||
//way will have empty content.
|
||||
//This method will refill the requests with file content.
|
||||
func (cra *ChartRepositoryAPI) rewriteFileContent(files []formFile, request *http.Request) error {
|
||||
if len(files) == 0 {
|
||||
return nil //no files, early return
|
||||
}
|
||||
|
||||
var body bytes.Buffer
|
||||
w := multipart.NewWriter(&body)
|
||||
defer func() {
|
||||
if err := w.Close(); err != nil {
|
||||
//Just log it
|
||||
hlog.Errorf("Failed to defer close multipart writer with error: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
//Process files by key one by one
|
||||
for _, f := range files {
|
||||
mFile, mHeader, err := cra.GetFile(f.formField)
|
||||
//Handle error case by case
|
||||
if err != nil {
|
||||
formatedErr := fmt.Errorf("Get file content with multipart header from key '%s' failed with error: %s", f.formField, err.Error())
|
||||
if f.mustHave || err != http.ErrMissingFile {
|
||||
return formatedErr
|
||||
}
|
||||
|
||||
//Error can be ignored, just log it
|
||||
hlog.Warning(formatedErr.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
fw, err := w.CreateFormFile(f.formField, mHeader.Filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Create form file with multipart header failed with error: %s", err.Error())
|
||||
}
|
||||
|
||||
_, err = io.Copy(fw, mFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Copy file stream in multipart form data failed with error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
request.Header.Set(headerContentType, w.FormDataContentType())
|
||||
request.Body = ioutil.NopCloser(&body)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
BIN
src/ui/harbor_ui
Executable file
BIN
src/ui/harbor_ui
Executable file
Binary file not shown.
Loading…
Reference in New Issue
Block a user