diff --git a/src/chartserver/handler_manipulation.go b/src/chartserver/handler_manipulation.go index 6957fa14a..ffcdc3435 100644 --- a/src/chartserver/handler_manipulation.go +++ b/src/chartserver/handler_manipulation.go @@ -7,7 +7,13 @@ import ( "strings" "github.com/ghodss/yaml" + "github.com/goharbor/harbor/src/replication" + rep_event "github.com/goharbor/harbor/src/replication/event" + "github.com/goharbor/harbor/src/replication/model" helm_repo "k8s.io/helm/pkg/repo" + + "github.com/goharbor/harbor/src/common/utils/log" + "os" ) // ListCharts gets the chart list under the namespace @@ -63,7 +69,38 @@ func (c *Controller) DeleteChartVersion(namespace, chartName, version string) er url := fmt.Sprintf("%s/%s/%s", c.APIPrefix(namespace), chartName, version) - return c.apiClient.DeleteContent(url) + err := c.apiClient.DeleteContent(url) + if err != nil { + return err + } + + // send notification to replication handler + // Todo: it used as the replacement of webhook, will be removed when webhook to be introduced. + if os.Getenv("UTTEST") != "true" { + go func() { + e := &rep_event.Event{ + Type: rep_event.EventTypeChartDelete, + Resource: &model.Resource{ + Type: model.ResourceTypeChart, + Deleted: true, + Metadata: &model.ResourceMetadata{ + Namespace: &model.Namespace{ + Name: namespace, + }, + Repository: &model.Repository{ + Name: chartName, + }, + Vtags: []string{version}, + }, + }, + } + if err := replication.EventHandler.Handle(e); err != nil { + log.Errorf("failed to handle event: %v", err) + } + }() + } + + return nil } // GetChartVersion returns the summary of the specified chart version. diff --git a/src/chartserver/reverse_proxy.go b/src/chartserver/reverse_proxy.go index d17e20ad8..f8d5842d2 100644 --- a/src/chartserver/reverse_proxy.go +++ b/src/chartserver/reverse_proxy.go @@ -4,6 +4,11 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/goharbor/harbor/src/common" + 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" "io/ioutil" "log" "net/http" @@ -80,6 +85,39 @@ func director(target *url.URL, cred *Credential, req *http.Request) { // Modify the http response 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 + // 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 { + // 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{ + Namespace: &model.Namespace{ + Name: chartUploadSplitted[0], + }, + Repository: &model.Repository{ + Name: chartUploadSplitted[1], + }, + Vtags: []string{chartUploadSplitted[2]}, + }, + }, + } + if err := replication.EventHandler.Handle(e); err != nil { + hlog.Errorf("failed to handle event: %v", err) + } + }() + } + } + } + // Accept cases // Success or redirect if res.StatusCode >= http.StatusOK && res.StatusCode <= http.StatusTemporaryRedirect { diff --git a/src/common/const.go b/src/common/const.go index 914759909..18fb743f5 100644 --- a/src/common/const.go +++ b/src/common/const.go @@ -14,6 +14,8 @@ package common +type contextKey string + // const variables const ( DBAuth = "db_auth" @@ -136,4 +138,6 @@ const ( RobotTokenDuration = "robot_token_duration" OIDCCallbackPath = "/c/oidc/callback" + + ChartUploadCtxKey = contextKey("chart_upload") ) diff --git a/src/core/api/chart_repository.go b/src/core/api/chart_repository.go index 927bf2c09..1af17b587 100644 --- a/src/core/api/chart_repository.go +++ b/src/core/api/chart_repository.go @@ -2,6 +2,7 @@ package api import ( "bytes" + "context" "errors" "fmt" "io" @@ -297,8 +298,28 @@ func (cra *ChartRepositoryAPI) UploadChartVersion() { } } + // 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, cra.Ctx.Request) + chartController.ProxyTraffic(cra.Ctx.ResponseWriter, req) } // UploadChartProvFile handles POST /api/:repo/prov diff --git a/src/replication/operation/controller.go b/src/replication/operation/controller.go index a6085e16d..8a4918798 100644 --- a/src/replication/operation/controller.go +++ b/src/replication/operation/controller.go @@ -108,7 +108,7 @@ func (c *controller) createFlow(executionID int64, policy *model.Policy, resourc { Type: model.FilterTypeName, // TODO only filter the repo part? - Value: resource.Metadata.GetResourceName(), + Value: resource.Metadata.Repository.Name, }, { Type: model.FilterTypeTag, diff --git a/src/replication/operation/flow/stage.go b/src/replication/operation/flow/stage.go index 2baf50771..4a287b64e 100644 --- a/src/replication/operation/flow/stage.go +++ b/src/replication/operation/flow/stage.go @@ -118,7 +118,7 @@ func filterResources(resources []*model.Resource, filters []*model.Filter) ([]*m for _, filter := range filters { switch filter.Type { case model.FilterTypeResource: - resourceType, ok := filter.Value.(string) + resourceType, ok := filter.Value.(model.ResourceType) if !ok { return nil, fmt.Errorf("%v is not a valid string", filter.Value) } diff --git a/src/replication/operation/flow/stage_test.go b/src/replication/operation/flow/stage_test.go index 881521044..2c3bc425b 100644 --- a/src/replication/operation/flow/stage_test.go +++ b/src/replication/operation/flow/stage_test.go @@ -289,7 +289,7 @@ func TestFilterResources(t *testing.T) { filters := []*model.Filter{ { Type: model.FilterTypeResource, - Value: string(model.ResourceTypeChart), + Value: model.ResourceTypeChart, }, { Type: model.FilterTypeName,