harbor/src/replication/event/handler.go

197 lines
5.3 KiB
Go

// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package event
import (
"errors"
"fmt"
commonthttp "github.com/goharbor/harbor/src/common/http"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/replication/config"
"github.com/goharbor/harbor/src/replication/model"
"github.com/goharbor/harbor/src/replication/operation"
"github.com/goharbor/harbor/src/replication/policy"
"github.com/goharbor/harbor/src/replication/registry"
"github.com/goharbor/harbor/src/replication/util"
)
// Handler is the handler to handle event
type Handler interface {
Handle(event *Event) error
}
// NewHandler ...
func NewHandler(policyCtl policy.Controller, registryMgr registry.Manager, opCtl operation.Controller) Handler {
return &handler{
policyCtl: policyCtl,
registryMgr: registryMgr,
opCtl: opCtl,
}
}
type handler struct {
policyCtl policy.Controller
registryMgr registry.Manager
opCtl operation.Controller
}
func (h *handler) Handle(event *Event) error {
if event == nil || event.Resource == nil ||
event.Resource.Metadata == nil ||
len(event.Resource.Metadata.Artifacts) == 0 {
return errors.New("invalid event")
}
var policies []*model.Policy
var err error
switch event.Type {
case EventTypeArtifactPush, EventTypeChartUpload, EventTypeTagDelete,
EventTypeArtifactDelete, EventTypeChartDelete:
policies, err = h.getRelatedPolicies(event.Resource)
default:
return fmt.Errorf("unsupported event type %s", event.Type)
}
if err != nil {
return err
}
if len(policies) == 0 {
log.Debugf("no policy found for the event %v, do nothing", event)
return nil
}
for _, policy := range policies {
if err := PopulateRegistries(h.registryMgr, policy); err != nil {
return err
}
id, err := h.opCtl.StartReplication(policy, event.Resource, model.TriggerTypeEventBased)
if err != nil {
return err
}
log.Debugf("%s event received, the replication execution %d started", event.Type, id)
}
return nil
}
func (h *handler) getRelatedPolicies(resource *model.Resource) ([]*model.Policy, error) {
_, policies, err := h.policyCtl.List()
if err != nil {
return nil, err
}
result := []*model.Policy{}
for _, policy := range policies {
// disabled
if !policy.Enabled {
continue
}
// currently, the events are produced only by local Harbor,
// so they should only apply to the policies whose source registry is local Harbor
if !(policy.SrcRegistry == nil || policy.SrcRegistry.ID == 0) {
continue
}
// has no trigger
if policy.Trigger == nil {
continue
}
// trigger type isn't event based
if policy.Trigger.Type != model.TriggerTypeEventBased {
continue
}
// doesn't replicate deletion
if resource.Deleted && !policy.Deletion {
continue
}
// doesn't match the name filter
m, err := match(policy.Filters, resource)
if err != nil {
return nil, err
}
if !m {
continue
}
result = append(result, policy)
}
return result, nil
}
// TODO unify the match logic with other?
func match(filters []*model.Filter, resource *model.Resource) (bool, error) {
match := true
repository := resource.Metadata.Repository.Name
for _, filter := range filters {
if filter.Type != model.FilterTypeName {
continue
}
m, err := util.Match(filter.Value.(string), repository)
if err != nil {
return false, err
}
if !m {
match = false
break
}
}
return match, nil
}
// PopulateRegistries populates the source registry and destination registry properties for policy
func PopulateRegistries(registryMgr registry.Manager, policy *model.Policy) error {
if policy == nil {
return nil
}
registry, err := getRegistry(registryMgr, policy.SrcRegistry)
if err != nil {
return err
}
policy.SrcRegistry = registry
registry, err = getRegistry(registryMgr, policy.DestRegistry)
if err != nil {
return err
}
policy.DestRegistry = registry
return nil
}
func getRegistry(registryMgr registry.Manager, registry *model.Registry) (*model.Registry, error) {
if registry == nil || registry.ID == 0 {
return GetLocalRegistry(), nil
}
reg, err := registryMgr.Get(registry.ID)
if err != nil {
return nil, err
}
if reg == nil {
return nil, fmt.Errorf("registry %d not found", registry.ID)
}
return reg, nil
}
// GetLocalRegistry returns the info of the local Harbor registry
func GetLocalRegistry() *model.Registry {
return &model.Registry{
Type: model.RegistryTypeHarbor,
Name: "Local",
URL: config.Config.CoreURL,
TokenServiceURL: config.Config.TokenServiceURL,
Status: "healthy",
Credential: &model.Credential{
Type: model.CredentialTypeSecret,
// use secret to do the auth for the local Harbor
AccessSecret: config.Config.JobserviceSecret,
},
Insecure: !commonthttp.InternalTLSEnabled(),
}
}