Feat: add Harbor webhook trigger (#3065)

* Feat: add harbor webhook trigger (#3029)

Signed-off-by: kingram <kingram@163.com>

* resolve comments

Signed-off-by: kingram <kingram@163.com>

* fix: add comments

Signed-off-by: kingram <kingram@163.com>
This commit is contained in:
Kingram
2022-01-10 21:26:43 +08:00
committed by GitHub
parent ece47b5961
commit ceb95229cd
6 changed files with 172 additions and 10 deletions
+7
View File
@@ -322,6 +322,8 @@ const (
PayloadTypeDockerhub = "dockerhub"
// PayloadTypeACR is the payload type acr
PayloadTypeACR = "acr"
// PayloadTypeHarbor is the payload type harbor
PayloadTypeHarbor = "harbor"
// ComponentTypeWebservice is the component type webservice
ComponentTypeWebservice = "webservice"
@@ -331,6 +333,11 @@ const (
ComponentTypeTask = "task"
)
const (
// HarborEventTypePushArtifact is the event type PUSH_ARTIFACT
HarborEventTypePushArtifact = "PUSH_ARTIFACT"
)
// TableName return custom table name
func (w *ApplicationTrigger) TableName() string {
return tableNamePrefix + "trigger"
+31 -1
View File
@@ -346,7 +346,7 @@ type CreateApplicationTriggerRequest struct {
Description string `json:"description" optional:"true"`
WorkflowName string `json:"workflowName"`
Type string `json:"type" validate:"oneof=webhook"`
PayloadType string `json:"payloadType" validate:"oneof=custom acr"`
PayloadType string `json:"payloadType" validate:"checkpayloadtype"`
ComponentName string `json:"componentName,omitempty" optional:"true"`
}
@@ -400,6 +400,36 @@ type ACRRepository struct {
RepoType string `json:"repo_type"`
}
// HandleApplicationHarborReq handles application trigger harbor request
type HandleApplicationHarborReq struct {
Type string `json:"type"`
OccurAt int64 `json:"occur_at"`
Operator string `json:"operator"`
EventData EventData `json:"event_data"`
}
// Resources is the image info of harbor
type Resources struct {
Digest string `json:"digest"`
Tag string `json:"tag"`
ResourceURL string `json:"resource_url"`
}
// Repository is the repository of harbor
type Repository struct {
DateCreated int64 `json:"date_created"`
Name string `json:"name"`
Namespace string `json:"namespace"`
RepoFullName string `json:"repo_full_name"`
RepoType string `json:"repo_type"`
}
// EventData is the event info of harbor
type EventData struct {
Resources []Resources `json:"resources"`
Repository Repository `json:"repository"`
}
// EnvBinding application env binding
type EnvBinding struct {
Name string `json:"name" validate:"checkname"`
@@ -370,9 +370,6 @@ func (c *applicationUsecaseImpl) CreateApplication(ctx context.Context, req apis
// CreateApplicationTrigger create application trigger
func (c *applicationUsecaseImpl) CreateApplicationTrigger(ctx context.Context, app *model.Application, req apisv1.CreateApplicationTriggerRequest) (*apisv1.ApplicationTriggerBase, error) {
if (req.PayloadType == model.PayloadTypeACR || req.PayloadType == model.PayloadTypeDockerhub) && req.ComponentName == "" {
return nil, bcode.ErrApplicationComponetNotExist
}
trigger := &model.ApplicationTrigger{
AppPrimaryKey: app.Name,
WorkflowName: req.WorkflowName,
+80
View File
@@ -60,6 +60,7 @@ func NewWebhookUsecase(ds datastore.DataStore,
func registerHandlers() {
new(customHandlerImpl).install()
new(acrHandlerImpl).install()
new(harborHandlerImpl).install()
}
type webhookHandler interface {
@@ -132,6 +133,11 @@ func (c *webhookUsecaseImpl) HandleApplicationWebhook(ctx context.Context, token
if err != nil {
return nil, err
}
case model.PayloadTypeHarbor:
handler, err = c.newHarborHandler(req)
if err != nil {
return nil, err
}
default:
return nil, bcode.ErrInvalidWebhookPayloadType
}
@@ -252,3 +258,77 @@ func parseTimeString(t string) time.Time {
}
return parsedTime
}
type harborHandlerImpl struct {
req apisv1.HandleApplicationHarborReq
w *webhookUsecaseImpl
}
func (c *webhookUsecaseImpl) newHarborHandler(req *restful.Request) (webhookHandler, error) {
var harborReq apisv1.HandleApplicationHarborReq
if err := req.ReadEntity(&harborReq); err != nil {
return nil, bcode.ErrInvalidWebhookPayloadBody
}
if harborReq.Type != model.HarborEventTypePushArtifact {
return nil, bcode.ErrInvalidWebhookPayloadBody
}
return &harborHandlerImpl{
req: harborReq,
w: c,
}, nil
}
func (c *harborHandlerImpl) install() {
WebhookHandlers = append(WebhookHandlers, model.PayloadTypeHarbor)
}
func (c *harborHandlerImpl) handle(ctx context.Context, webhookTrigger *model.ApplicationTrigger, app *model.Application) (*apisv1.ApplicationDeployResponse, error) {
resources := c.req.EventData.Resources
if len(resources) < 1 {
return nil, bcode.ErrInvalidWebhookPayloadBody
}
imageURL := resources[0].ResourceURL
digest := resources[0].Digest
tag := resources[0].Tag
comp := &model.ApplicationComponent{
AppPrimaryKey: webhookTrigger.AppPrimaryKey,
}
comps, err := c.w.ds.List(ctx, comp, &datastore.ListOptions{})
if err != nil {
return nil, err
}
if len(comps) == 0 {
return nil, bcode.ErrApplicationComponetNotExist
}
// use the first component as the target component
component := comps[0].(*model.ApplicationComponent)
harborReq := c.req
if err := c.w.patchComponentProperties(ctx, component, &runtime.RawExtension{
Raw: []byte(fmt.Sprintf(`{"image": "%s"}`, imageURL)),
}); err != nil {
return nil, err
}
return c.w.applicationUsecase.Deploy(ctx, app, apisv1.ApplicationDeployRequest{
WorkflowName: webhookTrigger.WorkflowName,
Note: "triggered by webhook harbor",
TriggerType: apisv1.TriggerTypeWebhook,
Force: true,
ImageInfo: &model.ImageInfo{
Type: model.PayloadTypeHarbor,
Resource: &model.ImageResource{
Digest: digest,
Tag: tag,
URL: imageURL,
CreateTime: time.Unix(harborReq.OccurAt, 0),
},
Repository: &model.ImageRepository{
Name: harborReq.EventData.Repository.Name,
Namespace: harborReq.EventData.Repository.Namespace,
FullName: harborReq.EventData.Repository.RepoFullName,
Type: harborReq.EventData.Repository.RepoType,
CreateTime: time.Unix(harborReq.EventData.Repository.DateCreated, 0),
},
},
})
}
+38 -6
View File
@@ -154,12 +154,6 @@ var _ = Describe("Test application usecase function", func() {
Expect(revision.CodeInfo.User).Should(Equal("test-user"))
By("Test HandleApplicationWebhook function with ACR payload")
_, err = appUsecase.CreateApplicationTrigger(context.TODO(), appModel, apisv1.CreateApplicationTriggerRequest{
Name: "test-acr",
PayloadType: "acr",
Type: "webhook",
})
Expect(err).Should(Equal(bcode.ErrApplicationComponetNotExist))
acrTrigger, err := appUsecase.CreateApplicationTrigger(context.TODO(), appModel, apisv1.CreateApplicationTriggerRequest{
Name: "test-acr",
PayloadType: "acr",
@@ -191,5 +185,43 @@ var _ = Describe("Test application usecase function", func() {
comp, err = appUsecase.GetApplicationComponent(context.TODO(), appModel, "component-name-webhook")
Expect(err).Should(BeNil())
Expect((*comp.Properties)["image"]).Should(Equal("registry.test-region.aliyuncs.com/test-namespace/test-repo:test-tag"))
By("Test HandleApplicationWebhook function with harbor payload")
harborTrigger, err := appUsecase.CreateApplicationTrigger(context.TODO(), appModel, apisv1.CreateApplicationTriggerRequest{
Name: "test-harbor",
PayloadType: "harbor",
Type: "webhook",
ComponentName: "component-name-webhook",
})
Expect(err).Should(BeNil())
harborBody := apisv1.HandleApplicationHarborReq{
Type: model.HarborEventTypePushArtifact,
EventData: apisv1.EventData{
Resources: []apisv1.Resources{
{
Digest: "test-digest",
Tag: "test-tag",
ResourceURL: "harbor.server/test-pro/test-repo:test-tag",
},
},
Repository: apisv1.Repository{
Name: "test-repo",
Namespace: "test-namespace",
RepoFullName: "test-pro/test-repo",
RepoType: "public",
},
},
}
body, err = json.Marshal(harborBody)
Expect(err).Should(BeNil())
httpreq, err = http.NewRequest("post", "/", bytes.NewBuffer(body))
httpreq.Header.Add(restful.HEADER_ContentType, "application/json")
Expect(err).Should(BeNil())
_, err = webhookUsecase.HandleApplicationWebhook(context.TODO(), harborTrigger.Token, restful.NewRequest(httpreq))
Expect(err).Should(BeNil())
comp, err = appUsecase.GetApplicationComponent(context.TODO(), appModel, "component-name-webhook")
Expect(err).Should(BeNil())
Expect((*comp.Properties)["image"]).Should(Equal("harbor.server/test-pro/test-repo:test-tag"))
})
})
+16
View File
@@ -20,6 +20,8 @@ import (
"regexp"
"github.com/go-playground/validator/v10"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase"
)
var validate = validator.New()
@@ -38,6 +40,20 @@ func init() {
if err := validate.RegisterValidation("checkalias", ValidateAlias); err != nil {
panic(err)
}
if err := validate.RegisterValidation("checkpayloadtype", ValidatePayloadType); err != nil {
panic(err)
}
}
// ValidatePayloadType check PayloadType
func ValidatePayloadType(fl validator.FieldLevel) bool {
value := fl.Field().String()
for _, v := range usecase.WebhookHandlers {
if v == value {
return true
}
}
return false
}
// ValidateName custom check name field