2016-02-01 12:59:10 +01:00
|
|
|
/*
|
|
|
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
|
|
|
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.
|
|
|
|
*/
|
2016-02-26 11:54:14 +01:00
|
|
|
|
2016-02-01 12:59:10 +01:00
|
|
|
package service
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2016-03-01 11:21:56 +01:00
|
|
|
"regexp"
|
2016-02-01 12:59:10 +01:00
|
|
|
"strings"
|
|
|
|
|
2016-05-25 09:25:16 +02:00
|
|
|
"github.com/vmware/harbor/api"
|
2016-02-01 12:59:10 +01:00
|
|
|
"github.com/vmware/harbor/dao"
|
|
|
|
"github.com/vmware/harbor/models"
|
2016-05-25 09:24:44 +02:00
|
|
|
"github.com/vmware/harbor/service/cache"
|
2016-03-25 02:31:50 +01:00
|
|
|
"github.com/vmware/harbor/utils/log"
|
2016-02-01 12:59:10 +01:00
|
|
|
|
|
|
|
"github.com/astaxie/beego"
|
|
|
|
)
|
|
|
|
|
2016-02-26 11:35:55 +01:00
|
|
|
// NotificationHandler handles request on /service/notifications/, which listens to registry's events.
|
2016-02-01 12:59:10 +01:00
|
|
|
type NotificationHandler struct {
|
|
|
|
beego.Controller
|
2016-04-21 18:28:59 +02:00
|
|
|
}
|
|
|
|
|
2016-03-01 11:21:56 +01:00
|
|
|
const manifestPattern = `^application/vnd.docker.distribution.manifest.v\d\+json`
|
2016-02-01 12:59:10 +01:00
|
|
|
|
2016-02-26 11:35:55 +01:00
|
|
|
// Post handles POST request, and records audit log or refreshes cache based on event.
|
2016-02-01 12:59:10 +01:00
|
|
|
func (n *NotificationHandler) Post() {
|
|
|
|
var notification models.Notification
|
|
|
|
err := json.Unmarshal(n.Ctx.Input.CopyBody(1<<32), ¬ification)
|
|
|
|
|
|
|
|
if err != nil {
|
2016-06-28 11:33:39 +02:00
|
|
|
log.Errorf("failed to decode notification: %v", err)
|
2016-02-01 12:59:10 +01:00
|
|
|
return
|
|
|
|
}
|
2016-06-28 11:33:39 +02:00
|
|
|
|
|
|
|
events, err := filterEvents(¬ification)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("failed to filter events: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, event := range events {
|
|
|
|
repository := event.Target.Repository
|
|
|
|
|
|
|
|
project := ""
|
|
|
|
if strings.Contains(repository, "/") {
|
|
|
|
project = repository[0:strings.LastIndex(repository, "/")]
|
2016-03-01 11:21:56 +01:00
|
|
|
}
|
2016-05-27 09:04:20 +02:00
|
|
|
|
2016-06-28 11:33:39 +02:00
|
|
|
tag := event.Target.Tag
|
|
|
|
action := event.Action
|
|
|
|
|
|
|
|
user := event.Actor.Name
|
|
|
|
if len(user) == 0 {
|
|
|
|
user = "anonymous"
|
|
|
|
}
|
2016-05-27 09:04:20 +02:00
|
|
|
|
2016-06-28 15:39:38 +02:00
|
|
|
go func() {
|
|
|
|
if err := dao.AccessLog(user, project, repository, tag, action); err != nil {
|
|
|
|
log.Errorf("failed to add access log: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
2016-06-28 11:33:39 +02:00
|
|
|
if action == "push" || action == "delete" {
|
|
|
|
go func() {
|
|
|
|
if err := cache.RefreshCatalogCache(); err != nil {
|
|
|
|
log.Errorf("failed to refresh cache: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
operation := ""
|
2016-02-01 12:59:10 +01:00
|
|
|
if action == "push" {
|
2016-06-28 11:33:39 +02:00
|
|
|
operation = models.RepOpTransfer
|
|
|
|
} else {
|
|
|
|
operation = models.RepOpDelete
|
2016-02-01 12:59:10 +01:00
|
|
|
}
|
2016-06-28 11:33:39 +02:00
|
|
|
|
|
|
|
go api.TriggerReplicationByRepository(repository, []string{tag}, operation)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func filterEvents(notification *models.Notification) ([]*models.Event, error) {
|
|
|
|
events := []*models.Event{}
|
|
|
|
|
|
|
|
for _, event := range notification.Events {
|
|
|
|
|
|
|
|
//delete
|
|
|
|
// TODO add tag field
|
|
|
|
if event.Action == "delete" {
|
|
|
|
events = append(events, &event)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
isManifest, err := regexp.MatchString(manifestPattern, event.Target.MediaType)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("failed to match the media type against pattern: %v", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isManifest {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
//pull and push manifest by docker-client
|
|
|
|
if strings.HasPrefix(event.Request.UserAgent, "docker") && (event.Action == "pull" || event.Action == "push") {
|
|
|
|
events = append(events, &event)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
//push manifest by docker-client or job-service
|
|
|
|
if strings.ToLower(strings.TrimSpace(event.Request.UserAgent)) == "harbor-registry-client" && event.Action == "push" {
|
|
|
|
events = append(events, &event)
|
|
|
|
continue
|
2016-02-01 12:59:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-28 11:33:39 +02:00
|
|
|
return events, nil
|
2016-02-01 12:59:10 +01:00
|
|
|
}
|
|
|
|
|
2016-02-26 11:35:55 +01:00
|
|
|
// Render returns nil as it won't render any template.
|
2016-02-01 12:59:10 +01:00
|
|
|
func (n *NotificationHandler) Render() error {
|
|
|
|
return nil
|
|
|
|
}
|