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:
chwetion
2022-01-18 10:25:28 +08:00
committed by GitHub
parent 12f392cd92
commit 438a9fe7a5
4 changed files with 141 additions and 0 deletions

View File

@@ -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

View File

@@ -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"`

View File

@@ -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)
}

View File

@@ -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"))
})
})