You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by kl...@apache.org on 2022/10/13 01:28:03 UTC

[incubator-devlake] branch release-v0.14 updated: Hotfix/webhook 0.14 (#3408)

This is an automated email from the ASF dual-hosted git repository.

klesh pushed a commit to branch release-v0.14
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/release-v0.14 by this push:
     new c48388f2 Hotfix/webhook 0.14 (#3408)
c48388f2 is described below

commit c48388f29914658a3834d2dbc2aec760c7274189
Author: Likyh <l...@likyh.com>
AuthorDate: Thu Oct 13 09:27:58 2022 +0800

    Hotfix/webhook 0.14 (#3408)
    
    * feat: new deploy task webhook (#3379)
    
    * feat: new deploy task webhook
    
    * fix: fix for some bugs
    
    * fix: use new url in config-ui's webhook page
    
    * fix: update some validator
    
    * fix: change some wording
    
    Co-authored-by: linyh <ya...@meri.co>
    
    * fix: display curl in webhook page
    
    Co-authored-by: linyh <ya...@meri.co>
---
 .../connections/incoming-webhook/add-modal.jsx     |  24 ++---
 .../pages/connections/incoming-webhook/index.jsx   |   7 +-
 .../incoming-webhook/view-or-edit-modal.jsx        |  17 +--
 plugins/webhook/api/cicd_pipeline.go               | 115 +++++++++++++++++++++
 plugins/webhook/api/connection.go                  |  10 +-
 plugins/webhook/impl/impl.go                       |   3 +
 6 files changed, 144 insertions(+), 32 deletions(-)

diff --git a/config-ui/src/pages/connections/incoming-webhook/add-modal.jsx b/config-ui/src/pages/connections/incoming-webhook/add-modal.jsx
index f160ce1b..7030feac 100644
--- a/config-ui/src/pages/connections/incoming-webhook/add-modal.jsx
+++ b/config-ui/src/pages/connections/incoming-webhook/add-modal.jsx
@@ -54,8 +54,11 @@ export const AddModal = ({ onSubmit, onCancel }) => {
     setRecord({
       postIssuesEndpoint: `${postUrlPrefix}${res.postIssuesEndpoint}`,
       closeIssuesEndpoint: `${postUrlPrefix}${res.closeIssuesEndpoint}`,
-      postPipelineTaskEndpoint: `${postUrlPrefix}${res.postPipelineTaskEndpoint}`,
-      closePipelineEndpoint: `${postUrlPrefix}${res.closePipelineEndpoint}`
+      postDeploymentsCurl: `curl ${postUrlPrefix}${res.postPipelineDeployTaskEndpoint} -X 'POST' -d "{
+  \\"repo_url\\":\\"$CIRCLE_REPOSITORY_URL\\",
+  \\"commit_sha\\":\\"$CIRCLE_SHA1\\",
+  \\"start_time\\":\\"$start_time\\"
+}"`
     })
   }
 
@@ -106,7 +109,7 @@ export const AddModal = ({ onSubmit, onCancel }) => {
                 DevLake.
               </p>
               <h3>Incident</h3>
-              <p>Send incident opened and reopened events</p>
+              <p>POST to register an incident</p>
               <div className='block'>
                 <span>{record.postIssuesEndpoint}</span>
                 <CopyToClipboard
@@ -121,7 +124,7 @@ export const AddModal = ({ onSubmit, onCancel }) => {
                   <CopyIcon width={16} height={16} />
                 </CopyToClipboard>
               </div>
-              <p>Send incident resolved events</p>
+              <p>POST to close a registered incident</p>
               <div className='block'>
                 <span>{record.closeIssuesEndpoint}</span>
                 <CopyToClipboard
@@ -137,17 +140,10 @@ export const AddModal = ({ onSubmit, onCancel }) => {
                 </CopyToClipboard>
               </div>
               <h3>Deployment</h3>
-              <p>Trigger after the "deployment" jobs/builds finished</p>
+              <p>POST to register a deployment</p>
               <div className='block'>
-                <span>{record.postPipelineTaskEndpoint}</span>
-                <CopyToClipboard text={record.postPipelineTaskEndpoint}>
-                  <CopyIcon width={16} height={16} />
-                </CopyToClipboard>
-              </div>
-              <p>Trigger after all CI jobs/builds finished</p>
-              <div className='block'>
-                <span>{record.closePipelineEndpoint}</span>
-                <CopyToClipboard text={record.closePipelineEndpoint}>
+                <span style={{ flex: '1 0' }}>{record.postDeploymentsCurl}</span>
+                <CopyToClipboard text={record.postDeploymentsCurl}>
                   <CopyIcon width={16} height={16} />
                 </CopyToClipboard>
               </div>
diff --git a/config-ui/src/pages/connections/incoming-webhook/index.jsx b/config-ui/src/pages/connections/incoming-webhook/index.jsx
index 1d399dec..f10cd75f 100644
--- a/config-ui/src/pages/connections/incoming-webhook/index.jsx
+++ b/config-ui/src/pages/connections/incoming-webhook/index.jsx
@@ -53,8 +53,11 @@ export const IncomingWebhook = () => {
             ...r,
             postIssuesEndpoint: `${postUrlPrefix}${r.postIssuesEndpoint}`,
             closeIssuesEndpoint: `${postUrlPrefix}${r.closeIssuesEndpoint}`,
-            postPipelineTaskEndpoint: `${postUrlPrefix}${r.postPipelineTaskEndpoint}`,
-            closePipelineEndpoint: `${postUrlPrefix}${r.closePipelineEndpoint}`
+            postDeploymentsCurl: `curl ${postUrlPrefix}${r.postPipelineDeployTaskEndpoint} -X 'POST' -d "{
+  \\"repo_url\\":\\"$CIRCLE_REPOSITORY_URL\\",
+  \\"commit_sha\\":\\"$CIRCLE_SHA1\\",
+  \\"start_time\\":\\"$start_time\\"
+}"`
           }
         : existingRecord
     )
diff --git a/config-ui/src/pages/connections/incoming-webhook/view-or-edit-modal.jsx b/config-ui/src/pages/connections/incoming-webhook/view-or-edit-modal.jsx
index 874aab9c..fd63ffa6 100644
--- a/config-ui/src/pages/connections/incoming-webhook/view-or-edit-modal.jsx
+++ b/config-ui/src/pages/connections/incoming-webhook/view-or-edit-modal.jsx
@@ -86,7 +86,7 @@ export const ViewOrEditModal = ({ record, onSubmit, onCancel }) => {
             and CI tool for Deployments by making a POST to DevLake.
           </p>
           <h3>Incident</h3>
-          <p>Send incident opened and reopened events</p>
+          <p>POST to register an incident </p>
           <div className='block'>
             <span>{record.postIssuesEndpoint}</span>
             <CopyToClipboard
@@ -101,7 +101,7 @@ export const ViewOrEditModal = ({ record, onSubmit, onCancel }) => {
               <CopyIcon width={16} height={16} />
             </CopyToClipboard>
           </div>
-          <p>Send incident resolved events</p>
+          <p>POST to close a registered incident</p>
           <div className='block'>
             <span>{record.closeIssuesEndpoint}</span>
             <CopyToClipboard
@@ -117,17 +117,10 @@ export const ViewOrEditModal = ({ record, onSubmit, onCancel }) => {
             </CopyToClipboard>
           </div>
           <h3>Deployment</h3>
-          <p>Trigger after the "deployment" jobs/builds finished</p>
+          <p>POST to register a deployment</p>
           <div className='block'>
-            <span>{record.postPipelineTaskEndpoint}</span>
-            <CopyToClipboard text={record.postPipelineTaskEndpoint}>
-              <CopyIcon width={16} height={16} />
-            </CopyToClipboard>
-          </div>
-          <p>Trigger after all CI jobs/builds finished</p>
-          <div className='block'>
-            <span>{record.closePipelineEndpoint}</span>
-            <CopyToClipboard text={record.closePipelineEndpoint}>
+            <span style={{ flex: '1 0' }}>{record.postDeploymentsCurl}</span>
+            <CopyToClipboard text={record.postDeploymentsCurl}>
               <CopyIcon width={16} height={16} />
             </CopyToClipboard>
           </div>
diff --git a/plugins/webhook/api/cicd_pipeline.go b/plugins/webhook/api/cicd_pipeline.go
index 0de18e85..d8727746 100644
--- a/plugins/webhook/api/cicd_pipeline.go
+++ b/plugins/webhook/api/cicd_pipeline.go
@@ -18,6 +18,7 @@ limitations under the License.
 package api
 
 import (
+	"crypto/md5"
 	"fmt"
 	"net/http"
 	"reflect"
@@ -251,3 +252,117 @@ func getTypeAndResultFromTasks(domainTasks []devops.CICDTask) (pipelineType, res
 	}
 	return
 }
+
+type WebhookDeployTaskRequest struct {
+	// RepoUrl should be unique string, fill url or other unique data
+	RepoUrl   string `mapstructure:"repo_url" validate:"required"`
+	CommitSha string `mapstructure:"commit_sha" validate:"required"`
+	// start_time and end_time is more readable for users,
+	// StartedDate and FinishedDate is same as columns in db.
+	// So they all keep.
+	StartedDate  *time.Time `mapstructure:"start_time" validate:"required_with=FinishedDate"`
+	FinishedDate *time.Time `mapstructure:"end_time"`
+	Environment  string     `validate:"omitempty,oneof=PRODUCTION STAGING TESTING DEVELOPMENT"`
+}
+
+// PostCicdTask
+// @Summary create deployment pipeline by webhook
+// @Description Create deployment pipeline by webhook.<br/>
+// @Description example1: {"repo_url":"devlake","commit_sha":"015e3d3b480e417aede5a1293bd61de9b0fd051d","start_time":"2020-01-01T12:00:00+00:00","end_time":"2020-01-01T12:59:59+00:00","environment":"PRODUCTION"}<br/>
+// @Description So we suggest request before task after deployment pipeline finish.
+// @Description Both cicd_pipeline and cicd_task will be created
+// @Tags plugins/webhook
+// @Param body body WebhookDeployTaskRequest true "json body"
+// @Success 200
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 403  {string} errcode.Error "Forbidden"
+// @Failure 500  {string} errcode.Error "Internal Error"
+// @Router /plugins/webhook/:connectionId/deployments [POST]
+func PostDeploymentCicdTask(input *core.ApiResourceInput) (*core.ApiResourceOutput, errors.Error) {
+	connection := &models.WebhookConnection{}
+	err := connectionHelper.First(connection, input.Params)
+	if err != nil {
+		return nil, err
+	}
+	// get request
+	request := &WebhookDeployTaskRequest{}
+	err = helper.DecodeMapStruct(input.Body, request)
+	if err != nil {
+		return &core.ApiResourceOutput{Body: err.Error(), Status: http.StatusBadRequest}, nil
+	}
+	// validate
+	vld = validator.New()
+	err = errors.Convert(vld.Struct(request))
+	if err != nil {
+		return nil, errors.BadInput.Wrap(vld.Struct(request), `input json error`)
+	}
+
+	db := basicRes.GetDal()
+	urlHash16 := fmt.Sprintf("%x", md5.Sum([]byte(request.RepoUrl)))[:16]
+	pipelineId := fmt.Sprintf("%s:%d:%s:%s:%s", "webhook", connection.ID, `pipeline`, urlHash16, request.CommitSha)
+
+	taskId := fmt.Sprintf("%s:%d:%s:%s", "webhook", connection.ID, urlHash16, request.CommitSha)
+	domainCicdTask := &devops.CICDTask{
+		DomainEntity: domainlayer.DomainEntity{
+			Id: taskId,
+		},
+		PipelineId:  pipelineId,
+		Name:        fmt.Sprintf(`deployment for %s`, request.CommitSha),
+		Result:      devops.SUCCESS,
+		Status:      devops.DONE,
+		Type:        devops.DEPLOYMENT,
+		Environment: request.Environment,
+	}
+	now := time.Now()
+	if request.StartedDate != nil {
+		domainCicdTask.StartedDate = *request.StartedDate
+		if request.FinishedDate != nil {
+			domainCicdTask.FinishedDate = request.FinishedDate
+		} else {
+			domainCicdTask.FinishedDate = &now
+		}
+		domainCicdTask.DurationSec = uint64(domainCicdTask.FinishedDate.Sub(domainCicdTask.StartedDate).Seconds())
+	} else {
+		domainCicdTask.StartedDate = now
+	}
+	if domainCicdTask.Environment == `` {
+		domainCicdTask.Environment = devops.PRODUCTION
+	}
+
+	domainPipeline := &devops.CICDPipeline{
+		DomainEntity: domainlayer.DomainEntity{
+			Id: pipelineId,
+		},
+		Name:         fmt.Sprintf(`pipeline for %s`, request.CommitSha),
+		Result:       devops.SUCCESS,
+		Status:       devops.DONE,
+		Type:         devops.DEPLOYMENT,
+		CreatedDate:  domainCicdTask.StartedDate,
+		FinishedDate: domainCicdTask.FinishedDate,
+		DurationSec:  domainCicdTask.DurationSec,
+		Environment:  domainCicdTask.Environment,
+	}
+
+	domainPipelineCommit := &devops.CiCDPipelineCommit{
+		PipelineId: pipelineId,
+		CommitSha:  request.CommitSha,
+		Branch:     ``,
+		RepoId:     request.RepoUrl,
+	}
+
+	// save
+	err = db.CreateOrUpdate(domainCicdTask)
+	if err != nil {
+		return nil, err
+	}
+	err = db.CreateOrUpdate(domainPipeline)
+	if err != nil {
+		return nil, err
+	}
+	err = db.CreateOrUpdate(domainPipelineCommit)
+	if err != nil {
+		return nil, err
+	}
+
+	return &core.ApiResourceOutput{Body: nil, Status: http.StatusOK}, nil
+}
diff --git a/plugins/webhook/api/connection.go b/plugins/webhook/api/connection.go
index 652085ce..0cedf27d 100644
--- a/plugins/webhook/api/connection.go
+++ b/plugins/webhook/api/connection.go
@@ -83,10 +83,11 @@ func DeleteConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, er
 
 type WebhookConnectionResponse struct {
 	models.WebhookConnection
-	PostIssuesEndpoint       string `json:"postIssuesEndpoint"`
-	CloseIssuesEndpoint      string `json:"closeIssuesEndpoint"`
-	PostPipelineTaskEndpoint string `json:"postPipelineTaskEndpoint"`
-	ClosePipelineEndpoint    string `json:"closePipelineEndpoint"`
+	PostIssuesEndpoint             string `json:"postIssuesEndpoint"`
+	CloseIssuesEndpoint            string `json:"closeIssuesEndpoint"`
+	PostPipelineTaskEndpoint       string `json:"postPipelineTaskEndpoint"`
+	PostPipelineDeployTaskEndpoint string `json:"postPipelineDeployTaskEndpoint"`
+	ClosePipelineEndpoint          string `json:"closePipelineEndpoint"`
 }
 
 // ListConnections
@@ -130,6 +131,7 @@ func formatConnection(connection *models.WebhookConnection) *WebhookConnectionRe
 	response.PostIssuesEndpoint = fmt.Sprintf(`/plugins/webhook/%d/issues`, connection.ID)
 	response.CloseIssuesEndpoint = fmt.Sprintf(`/plugins/webhook/%d/issue/:boardKey/:issueKey/close`, connection.ID)
 	response.PostPipelineTaskEndpoint = fmt.Sprintf(`/plugins/webhook/%d/cicd_tasks`, connection.ID)
+	response.PostPipelineDeployTaskEndpoint = fmt.Sprintf(`/plugins/webhook/%d/deployments`, connection.ID)
 	response.ClosePipelineEndpoint = fmt.Sprintf(`/plugins/webhook/%d/cicd_pipeline/:pipelineName/finish`, connection.ID)
 	return response
 }
diff --git a/plugins/webhook/impl/impl.go b/plugins/webhook/impl/impl.go
index a939f560..4301509a 100644
--- a/plugins/webhook/impl/impl.go
+++ b/plugins/webhook/impl/impl.go
@@ -69,6 +69,9 @@ func (plugin Webhook) ApiResources() map[string]map[string]core.ApiResourceHandl
 		":connectionId/cicd_pipeline/:pipelineName/finish": {
 			"POST": api.PostPipelineFinish,
 		},
+		":connectionId/deployments": {
+			"POST": api.PostDeploymentCicdTask,
+		},
 		":connectionId/issues": {
 			"POST": api.PostIssue,
 		},