Merge pull request #7482 from wy65701436/chart-upload

Fix chart upload issue on event based
This commit is contained in:
Steven Zou 2019-04-23 17:33:08 +08:00 committed by GitHub
commit 69d9a28860
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 86 additions and 54 deletions

View File

@ -14,6 +14,7 @@ import (
"github.com/goharbor/harbor/src/common/models"
hlog "github.com/goharbor/harbor/src/common/utils/log"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/proto/hapi/chart"
helm_repo "k8s.io/helm/pkg/repo"
)
@ -71,13 +72,7 @@ type ChartOperator struct{}
// GetChartDetails parse the details from the provided content bytes
func (cho *ChartOperator) GetChartDetails(content []byte) (*ChartVersionDetails, error) {
if content == nil || len(content) == 0 {
return nil, errors.New("zero content")
}
// Load chart from in-memory content
reader := bytes.NewReader(content)
chartData, err := chartutil.LoadArchive(reader)
chartData, err := cho.GetChartData(content)
if err != nil {
return nil, err
}
@ -164,6 +159,21 @@ func (cho *ChartOperator) GetChartList(content []byte) ([]*ChartInfo, error) {
return chartList, nil
}
// GetChartData returns raw data of chart
func (cho *ChartOperator) GetChartData(content []byte) (*chart.Chart, error) {
if content == nil || len(content) == 0 {
return nil, errors.New("zero content")
}
reader := bytes.NewReader(content)
chartData, err := chartutil.LoadArchive(reader)
if err != nil {
return nil, err
}
return chartData, nil
}
// GetChartVersions returns the chart versions
func (cho *ChartOperator) GetChartVersions(content []byte) (ChartVersions, error) {
if content == nil || len(content) == 0 {

View File

@ -17,7 +17,6 @@ import (
hlog "github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/replication"
rep_event "github.com/goharbor/harbor/src/replication/event"
"github.com/goharbor/harbor/src/replication/model"
)
const (
@ -88,32 +87,21 @@ func director(target *url.URL, cred *Credential, req *http.Request) {
func modifyResponse(res *http.Response) error {
// Upload chart success, then to the notification to replication handler
if res.StatusCode == http.StatusCreated {
// 201 and has chart_upload(namespace-repository-version) context
// 201 and has chart_upload_event context
// means this response is for uploading chart success.
chartUpload := res.Request.Context().Value(common.ChartUploadCtxKey).(string)
if chartUpload != "" {
chartUploadSplitted := strings.Split(chartUpload, ":")
if len(chartUploadSplitted) == 3 {
chartUploadEvent := res.Request.Context().Value(common.ChartUploadCtxKey)
e, ok := chartUploadEvent.(*rep_event.Event)
if !ok {
hlog.Error("failed to convert chart upload context into replication event.")
} else {
// Todo: it used as the replacement of webhook, will be removed when webhook to be introduced.
go func() {
e := &rep_event.Event{
Type: rep_event.EventTypeChartUpload,
Resource: &model.Resource{
Type: model.ResourceTypeChart,
Metadata: &model.ResourceMetadata{
Repository: &model.Repository{
Name: fmt.Sprintf("%s/%s", chartUploadSplitted[0], chartUploadSplitted[1]),
},
Vtags: []string{chartUploadSplitted[2]},
},
},
}
if err := replication.EventHandler.Handle(e); err != nil {
hlog.Errorf("failed to handle event: %v", err)
}
}()
}
}
}
// Accept cases

View File

@ -139,5 +139,5 @@ const (
OIDCCallbackPath = "/c/oidc/callback"
ChartUploadCtxKey = contextKey("chart_upload")
ChartUploadCtxKey = contextKey("chart_upload_event")
)

View File

@ -19,6 +19,8 @@ import (
"github.com/goharbor/harbor/src/common/rbac"
hlog "github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/config"
rep_event "github.com/goharbor/harbor/src/replication/event"
"github.com/goharbor/harbor/src/replication/model"
)
const (
@ -296,30 +298,13 @@ func (cra *ChartRepositoryAPI) UploadChartVersion() {
cra.SendInternalServerError(err)
return
}
if err := cra.addEventContext(formFiles, cra.Ctx.Request); err != nil {
hlog.Errorf("Failed to add chart upload context, %v", err)
}
// set namespace/repository/version for replication event.
_, header, err := cra.GetFile(formFieldNameForChart)
if err != nil {
cra.SendInternalServerError(err)
return
}
req := cra.Ctx.Request
charFileName := header.Filename
if !strings.HasSuffix(charFileName, ".tgz") {
cra.SendInternalServerError(fmt.Errorf("chart file expected %s to end with .tgz", charFileName))
return
}
charFileName = strings.TrimSuffix(charFileName, ".tgz")
// colon cannot be used as namespace
charFileName = strings.Replace(charFileName, "-", ":", -1)
// value sample: library:redis:4.0.3 (namespace:repository:version)
ctx := context.WithValue(cra.Ctx.Request.Context(), common.ChartUploadCtxKey, cra.namespace+":"+charFileName)
req = req.WithContext(ctx)
// Directly proxy to the backend
chartController.ProxyTraffic(cra.Ctx.ResponseWriter, req)
chartController.ProxyTraffic(cra.Ctx.ResponseWriter, cra.Ctx.Request)
}
// UploadChartProvFile handles POST /api/:repo/prov
@ -428,6 +413,53 @@ type formFile struct {
mustHave bool
}
// The func is for event based chart replication policy.
// It will add a context for uploading request with key chart_upload, and consumed by upload response.
func (cra *ChartRepositoryAPI) addEventContext(files []formFile, request *http.Request) error {
if len(files) == 0 {
return nil
}
for _, f := range files {
if f.formField == formFieldNameForChart {
mFile, _, err := cra.GetFile(f.formField)
if err != nil {
hlog.Errorf("failed to read file content for upload event, %v", err)
return err
}
var Buf bytes.Buffer
_, err = io.Copy(&Buf, mFile)
if err != nil {
hlog.Errorf("failed to copy file content for upload event, %v", err)
return err
}
chartOpr := chartserver.ChartOperator{}
chartDetails, err := chartOpr.GetChartData(Buf.Bytes())
if err != nil {
hlog.Errorf("failed to get chart content for upload event, %v", err)
return err
}
e := &rep_event.Event{
Type: rep_event.EventTypeChartUpload,
Resource: &model.Resource{
Type: model.ResourceTypeChart,
Metadata: &model.ResourceMetadata{
Repository: &model.Repository{
Name: fmt.Sprintf("%s/%s", cra.namespace, chartDetails.Metadata.Name),
},
Vtags: []string{chartDetails.Metadata.Version},
},
},
}
*request = *(request.WithContext(context.WithValue(request.Context(), common.ChartUploadCtxKey, e)))
break
}
}
return nil
}
// 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.
@ -449,6 +481,7 @@ func (cra *ChartRepositoryAPI) rewriteFileContent(files []formFile, request *htt
// 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())
@ -470,6 +503,7 @@ func (cra *ChartRepositoryAPI) rewriteFileContent(files []formFile, request *htt
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())