mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
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:
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user