mirror of
https://github.com/kubevela/kubevela.git
synced 2026-04-28 21:46:58 +00:00
Feat: add JFrog webhook trigger (#3104)
* add jfrog webhook to update application image Signed-off-by: chwetion <chwetion@foxmail.com> * edit jfrog default request header Signed-off-by: chwetion <chwetion@foxmail.com> Co-authored-by: chwetion <chwetion@foxmail.com>
This commit is contained in:
@@ -324,6 +324,8 @@ const (
|
||||
PayloadTypeACR = "acr"
|
||||
// PayloadTypeHarbor is the payload type harbor
|
||||
PayloadTypeHarbor = "harbor"
|
||||
// PayloadTypeJFrog is the payload type jfrog
|
||||
PayloadTypeJFrog = "jfrog"
|
||||
|
||||
// ComponentTypeWebservice is the component type webservice
|
||||
ComponentTypeWebservice = "webservice"
|
||||
@@ -336,6 +338,10 @@ const (
|
||||
const (
|
||||
// HarborEventTypePushArtifact is the event type PUSH_ARTIFACT
|
||||
HarborEventTypePushArtifact = "PUSH_ARTIFACT"
|
||||
// JFrogEventTypePush is push event type of jfrog webhook
|
||||
JFrogEventTypePush = "pushed"
|
||||
// JFrogDomainDocker is webhook domain of jfrog docker
|
||||
JFrogDomainDocker = "docker"
|
||||
)
|
||||
|
||||
// TableName return custom table name
|
||||
|
||||
@@ -464,6 +464,24 @@ type DockerHubRepository struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// HandleApplicationTriggerJFrogRequest application trigger JFrog webhook request
|
||||
type HandleApplicationTriggerJFrogRequest struct {
|
||||
Domain string `json:"domain"`
|
||||
EventType string `json:"event_type"`
|
||||
Data JFrogWebhookData `json:"data"`
|
||||
}
|
||||
|
||||
// JFrogWebhookData is the data of JFrog webhook request
|
||||
type JFrogWebhookData struct {
|
||||
URL string
|
||||
ImageName string `json:"image_name"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
RepoKey string `json:"repo_key"`
|
||||
Digest string `json:"sha256"`
|
||||
Tag string `json:"tag"`
|
||||
}
|
||||
|
||||
// EnvBinding application env binding
|
||||
type EnvBinding struct {
|
||||
Name string `json:"name" validate:"checkname"`
|
||||
|
||||
@@ -62,6 +62,7 @@ func registerHandlers() {
|
||||
new(acrHandlerImpl).install()
|
||||
new(dockerHubHandlerImpl).install()
|
||||
new(harborHandlerImpl).install()
|
||||
new(jfrogHandlerImpl).install()
|
||||
}
|
||||
|
||||
type webhookHandler interface {
|
||||
@@ -160,6 +161,11 @@ func (c *webhookUsecaseImpl) HandleApplicationWebhook(ctx context.Context, token
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case model.PayloadTypeJFrog:
|
||||
handler, err = c.newJFrogHandler(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, bcode.ErrInvalidWebhookPayloadType
|
||||
}
|
||||
@@ -423,3 +429,74 @@ func (c *harborHandlerImpl) handle(ctx context.Context, webhookTrigger *model.Ap
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type jfrogHandlerImpl struct {
|
||||
req apisv1.HandleApplicationTriggerJFrogRequest
|
||||
w *webhookUsecaseImpl
|
||||
}
|
||||
|
||||
func (c *webhookUsecaseImpl) newJFrogHandler(req *restful.Request) (webhookHandler, error) {
|
||||
var jfrogReq apisv1.HandleApplicationTriggerJFrogRequest
|
||||
if err := req.ReadEntity(&jfrogReq); err != nil {
|
||||
return nil, bcode.ErrInvalidWebhookPayloadBody
|
||||
}
|
||||
if jfrogReq.Domain != model.JFrogDomainDocker || jfrogReq.EventType != model.JFrogEventTypePush {
|
||||
return nil, bcode.ErrInvalidWebhookPayloadBody
|
||||
}
|
||||
// jfrog should use request header to give URL, it is not exist in request body
|
||||
jfrogReq.Data.URL = req.HeaderParameter("X-JFrogURL")
|
||||
return &jfrogHandlerImpl{
|
||||
req: jfrogReq,
|
||||
w: c,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (j *jfrogHandlerImpl) handle(ctx context.Context, webhookTrigger *model.ApplicationTrigger, app *model.Application) (interface{}, error) {
|
||||
jfrogReq := j.req
|
||||
comp := &model.ApplicationComponent{
|
||||
AppPrimaryKey: webhookTrigger.AppPrimaryKey,
|
||||
}
|
||||
comps, err := j.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)
|
||||
image := fmt.Sprintf("%s/%s:%s", jfrogReq.Data.RepoKey, jfrogReq.Data.ImageName, jfrogReq.Data.Tag)
|
||||
if jfrogReq.Data.URL != "" {
|
||||
image = fmt.Sprintf("%s/%s", jfrogReq.Data.URL, image)
|
||||
}
|
||||
if err := j.w.patchComponentProperties(ctx, component, &runtime.RawExtension{
|
||||
Raw: []byte(fmt.Sprintf(`{"image": "%s"}`, image)),
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return j.w.applicationUsecase.Deploy(ctx, app, apisv1.ApplicationDeployRequest{
|
||||
WorkflowName: webhookTrigger.WorkflowName,
|
||||
Note: "triggered by webhook jfrog",
|
||||
TriggerType: apisv1.TriggerTypeWebhook,
|
||||
Force: true,
|
||||
ImageInfo: &model.ImageInfo{
|
||||
Type: model.PayloadTypeHarbor,
|
||||
Resource: &model.ImageResource{
|
||||
Digest: jfrogReq.Data.Digest,
|
||||
Tag: jfrogReq.Data.Tag,
|
||||
URL: image,
|
||||
},
|
||||
Repository: &model.ImageRepository{
|
||||
Name: jfrogReq.Data.ImageName,
|
||||
Namespace: jfrogReq.Data.RepoKey,
|
||||
FullName: fmt.Sprintf("%s/%s", jfrogReq.Data.RepoKey, jfrogReq.Data.ImageName),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (j *jfrogHandlerImpl) install() {
|
||||
WebhookHandlers = append(WebhookHandlers, model.PayloadTypeJFrog)
|
||||
}
|
||||
|
||||
@@ -256,5 +256,45 @@ 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("docker.io/test-namespace/test-repo:test-tag"))
|
||||
|
||||
By("Test HandleApplicationWebhook function with jfrog payload without header of X-JFrogURL")
|
||||
jfrogTrigger, err := appUsecase.CreateApplicationTrigger(context.TODO(), appModel, apisv1.CreateApplicationTriggerRequest{
|
||||
Name: "test-jfrog",
|
||||
PayloadType: "jfrog",
|
||||
Type: "webhook",
|
||||
ComponentName: "component-name-webhook",
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
jfrogBody := apisv1.HandleApplicationTriggerJFrogRequest{
|
||||
Domain: "docker",
|
||||
EventType: "pushed",
|
||||
Data: apisv1.JFrogWebhookData{
|
||||
ImageName: "test-image",
|
||||
RepoKey: "test-repo",
|
||||
Digest: "test-digest",
|
||||
Tag: "test-tag",
|
||||
},
|
||||
}
|
||||
body, err = json.Marshal(jfrogBody)
|
||||
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(), jfrogTrigger.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("test-repo/test-image:test-tag"))
|
||||
|
||||
By("Test HandleApplicationWebhook function with jfrog payload with header of X-JFrogURL")
|
||||
httpreq, err = http.NewRequest("post", "/", bytes.NewBuffer(body))
|
||||
Expect(err).Should(BeNil())
|
||||
httpreq.Header.Add(restful.HEADER_ContentType, "application/json")
|
||||
httpreq.Header.Add("X-JFrogURL", "test-addr")
|
||||
_, err = webhookUsecase.HandleApplicationWebhook(context.TODO(), jfrogTrigger.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("test-addr/test-repo/test-image:test-tag"))
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user