diff --git a/backend/plugins/webhook/api/deployments.go b/backend/plugins/webhook/api/deployments.go
index f49591bb336..3fcc27829d3 100644
--- a/backend/plugins/webhook/api/deployments.go
+++ b/backend/plugins/webhook/api/deployments.go
@@ -20,12 +20,13 @@ package api
import (
"crypto/md5"
"fmt"
- "github.com/apache/incubator-devlake/core/dal"
- "github.com/apache/incubator-devlake/core/log"
"net/http"
"strings"
"time"
+ "github.com/apache/incubator-devlake/core/dal"
+ "github.com/apache/incubator-devlake/core/log"
+
"github.com/apache/incubator-devlake/helpers/dbhelper"
"github.com/go-playground/validator/v10"
@@ -37,44 +38,92 @@ import (
"github.com/apache/incubator-devlake/plugins/webhook/models"
)
-type WebhookDeployTaskRequest struct {
- DisplayTitle string `mapstructure:"display_title"`
- PipelineId string `mapstructure:"pipeline_id" validate:"required"`
- // RepoUrl should be unique string, fill url or other unique data
- RepoId string `mapstructure:"repo_id"`
- Result string `mapstructure:"result"`
- // start_time and end_time is more readable for users,
- // StartedDate and FinishedDate is same as columns in db.
- // So they all keep.
- CreatedDate *time.Time `mapstructure:"create_time"`
- // QueuedDate *time.Time `mapstructure:"queue_time"`
- StartedDate *time.Time `mapstructure:"start_time" validate:"required"`
- FinishedDate *time.Time `mapstructure:"end_time"`
- RepoUrl string `mapstructure:"repo_url"`
- Environment string `validate:"omitempty,oneof=PRODUCTION STAGING TESTING DEVELOPMENT"`
- Name string `mapstructure:"name"`
- RefName string `mapstructure:"ref_name"`
- CommitSha string `mapstructure:"commit_sha"`
- CommitMsg string `mapstructure:"commit_msg"`
+type WebhookDeploymentReq struct {
+ Id string `mapstructure:"id"`
+ DisplayTitle string `mapstructure:"displayTitle"`
+ PipelineId string `mapstructure:"pipelineId" validate:"required"`
+ Result string `mapstructure:"result"`
+ Environment string `validate:"omitempty,oneof=PRODUCTION STAGING TESTING DEVELOPMENT"`
+ Name string `mapstructure:"name"`
// DeploymentCommits is used for multiple commits in one deployment
- DeploymentCommits []DeploymentCommit `mapstructure:"deployment_commits" validate:"omitempty,dive"`
+ DeploymentCommits []WebhookDeploymentCommitReq `mapstructure:"deploymentCommits" validate:"omitempty,dive"`
+ CreatedDate *time.Time `mapstructure:"createdTime"`
+ // QueuedDate *time.Time `mapstructure:"queue_time"`
+ StartedDate *time.Time `mapstructure:"startedTime" validate:"required"`
+ FinishedDate *time.Time `mapstructure:"endedTime"`
}
-type DeploymentCommit struct {
- DisplayTitle string `mapstructure:"display_title"`
- RepoUrl string `mapstructure:"repo_url" validate:"required"`
- Name string `mapstructure:"name"`
- RefName string `mapstructure:"ref_name"`
- CommitSha string `mapstructure:"commit_sha" validate:"required"`
- CommitMsg string `mapstructure:"commit_msg"`
+type WebhookDeploymentCommitReq struct {
+ DisplayTitle string `mapstructure:"displayTitle"`
+ RepoId string `mapstructure:"repoId"`
+ RepoUrl string `mapstructure:"repoUrl" validate:"required"`
+ Name string `mapstructure:"name"`
+ RefName string `mapstructure:"refName"`
+ CommitSha string `mapstructure:"commitSha" validate:"required"`
+ CommitMsg string `mapstructure:"commitMsg"`
+ Result string `mapstructure:"result"`
+ Status string `mapstructure:"status"`
+ CreatedDate *time.Time `mapstructure:"createdTime"`
+ // QueuedDate *time.Time `mapstructure:"queue_time"`
+ StartedDate *time.Time `mapstructure:"startedTime"`
+ FinishedDate *time.Time `mapstructure:"endedTime"`
}
-func GenerateDeploymentCommitId(connectionId uint64, pipelineId string, repoUrl string, commitSha string) string {
- urlHash16 := fmt.Sprintf("%x", md5.Sum([]byte(repoUrl)))[:16]
- return fmt.Sprintf("%s:%d:%s:%s:%s", "webhook", connectionId, pipelineId, urlHash16, commitSha)
+// PostDeployments
+// @Summary create deployment by webhook
+// @Description Create deployment pipeline by webhook.
+// @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"}
+// @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/connections/:connectionId/deployments [POST]
+func PostDeployments(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
+ connection := &models.WebhookConnection{}
+ err := connectionHelper.First(connection, input.Params)
+ if err != nil {
+ return nil, err
+ }
+ // get request
+ request := &WebhookDeploymentReq{}
+ err = api.DecodeMapStruct(input.Body, request, true)
+ if err != nil {
+ return &plugin.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`)
+ }
+ txHelper := dbhelper.NewTxHelper(basicRes, &err)
+ defer txHelper.End()
+ tx := txHelper.Begin()
+ if err := CreateDeploymentAndDeploymentCommits(connection, request, tx, logger); err != nil {
+ logger.Error(err, "create deployments")
+ return nil, err
+ }
+
+ return &plugin.ApiResourceOutput{Body: nil, Status: http.StatusOK}, nil
}
-func CreateDeploymentAndDeploymentCommits(connection *models.WebhookConnection, request *WebhookDeployTaskRequest, tx dal.Transaction, logger log.Logger) errors.Error {
+func CreateDeploymentAndDeploymentCommits(connection *models.WebhookConnection, request *WebhookDeploymentReq, tx dal.Transaction, logger log.Logger) errors.Error {
+ // validation
+ if request == nil {
+ return errors.BadInput.New("request body is nil")
+ }
+ if len(request.DeploymentCommits) == 0 {
+ return errors.BadInput.New("deployment_commits is empty")
+ }
+ // set default values for optional fields
+ deploymentId := request.Id
+ if deploymentId == "" {
+ deploymentId = request.PipelineId
+ }
scopeId := fmt.Sprintf("%s:%d", "webhook", connection.ID)
if request.CreatedDate == nil {
request.CreatedDate = request.StartedDate
@@ -92,15 +141,11 @@ func CreateDeploymentAndDeploymentCommits(connection *models.WebhookConnection,
duration := float64(request.FinishedDate.Sub(*request.StartedDate).Milliseconds() / 1e3)
name := request.Name
if name == "" {
- if request.DeploymentCommits == nil {
- name = fmt.Sprintf(`deployment for %s`, request.CommitSha)
- } else {
- var commitShaList []string
- for _, commit := range request.DeploymentCommits {
- commitShaList = append(commitShaList, commit.CommitSha)
- }
- name = fmt.Sprintf(`deployment for %s`, strings.Join(commitShaList, ","))
+ var commitShaList []string
+ for _, commit := range request.DeploymentCommits {
+ commitShaList = append(commitShaList, commit.CommitSha)
}
+ name = fmt.Sprintf(`deploy %s to %s`, strings.Join(commitShaList, ","), request.Environment)
}
createdDate := time.Now()
if request.CreatedDate != nil {
@@ -108,131 +153,78 @@ func CreateDeploymentAndDeploymentCommits(connection *models.WebhookConnection,
} else if request.StartedDate != nil {
createdDate = *request.StartedDate
}
- dateInfo := devops.TaskDatesInfo{
- CreatedDate: createdDate,
- // QueuedDate: request.QueuedDate,
- StartedDate: request.StartedDate,
- FinishedDate: request.FinishedDate,
- }
+
+ // prepare deploymentCommits and deployment records
// queuedDuration := dateInfo.CalculateQueueDuration()
- if request.DeploymentCommits == nil {
- if request.CommitSha == "" || request.RepoUrl == "" {
- return errors.Convert(fmt.Errorf("commit_sha or repo_url is required"))
+ deploymentCommits := make([]*devops.CicdDeploymentCommit, len(request.DeploymentCommits))
+ for i, commit := range request.DeploymentCommits {
+ if commit.Result == "" {
+ commit.Result = devops.RESULT_SUCCESS
+ }
+ if commit.Status == "" {
+ commit.Status = devops.STATUS_DONE
+ }
+ if commit.Name == "" {
+ commit.Name = fmt.Sprintf(`deployment for %s`, commit.CommitSha)
+ }
+ if commit.CreatedDate == nil {
+ commit.CreatedDate = &createdDate
+ }
+ if commit.StartedDate == nil {
+ commit.StartedDate = request.StartedDate
+ }
+ if commit.FinishedDate == nil {
+ commit.FinishedDate = request.FinishedDate
}
// create a deployment_commit record
- deploymentCommit := &devops.CicdDeploymentCommit{
+ deploymentCommits[i] = &devops.CicdDeploymentCommit{
DomainEntity: domainlayer.DomainEntity{
- Id: GenerateDeploymentCommitId(connection.ID, request.PipelineId, request.RepoUrl, request.CommitSha),
+ Id: GenerateDeploymentCommitId(connection.ID, deploymentId, commit.RepoUrl, commit.CommitSha),
},
- CicdDeploymentId: request.PipelineId,
+ CicdDeploymentId: deploymentId,
CicdScopeId: scopeId,
- Name: name,
- DisplayTitle: request.DisplayTitle,
- Result: request.Result,
- Status: devops.STATUS_DONE,
- OriginalResult: request.Result,
- OriginalStatus: devops.STATUS_DONE,
- TaskDatesInfo: dateInfo,
- DurationSec: &duration,
- //QueuedDurationSec: queuedDuration,
- RepoId: request.RepoId,
- RepoUrl: request.RepoUrl,
+ Result: commit.Result,
+ Status: commit.Status,
+ OriginalResult: commit.Result,
+ OriginalStatus: commit.Status,
+ TaskDatesInfo: devops.TaskDatesInfo{
+ CreatedDate: *commit.CreatedDate,
+ StartedDate: commit.StartedDate,
+ FinishedDate: commit.FinishedDate,
+ },
+ DurationSec: &duration,
+ RepoId: commit.RepoId,
+ Name: commit.Name,
+ DisplayTitle: commit.DisplayTitle,
+ RepoUrl: commit.RepoUrl,
Environment: request.Environment,
OriginalEnvironment: request.Environment,
- RefName: request.RefName,
- CommitSha: request.CommitSha,
- CommitMsg: request.CommitMsg,
- }
- if err := tx.CreateOrUpdate(deploymentCommit); err != nil {
- logger.Error(err, "create deployment commit")
- return err
- }
- // create a deployment record
- if err := tx.CreateOrUpdate(deploymentCommit.ToDeploymentWithCustomDisplayTitle(request.DisplayTitle)); err != nil {
- logger.Error(err, "create deployment")
- return err
- }
- } else {
- for _, commit := range request.DeploymentCommits {
- // create a deployment_commit record
- deploymentCommit := &devops.CicdDeploymentCommit{
- DomainEntity: domainlayer.DomainEntity{
- Id: GenerateDeploymentCommitId(connection.ID, request.PipelineId, commit.RepoUrl, commit.CommitSha),
- },
- CicdDeploymentId: request.PipelineId,
- CicdScopeId: scopeId,
- Result: request.Result,
- Status: devops.STATUS_DONE,
- OriginalResult: request.Result,
- OriginalStatus: devops.STATUS_DONE,
- TaskDatesInfo: dateInfo,
- DurationSec: &duration,
- //QueuedDurationSec: queuedDuration,
- RepoId: request.RepoId,
- Name: fmt.Sprintf(`deployment for %s`, commit.CommitSha),
- DisplayTitle: commit.DisplayTitle,
- RepoUrl: commit.RepoUrl,
- Environment: request.Environment,
- OriginalEnvironment: request.Environment,
- RefName: commit.RefName,
- CommitSha: commit.CommitSha,
- CommitMsg: commit.CommitMsg,
- }
-
- if err := tx.CreateOrUpdate(deploymentCommit); err != nil {
- logger.Error(err, "create deployment commit")
- return err
- }
-
- // create a deployment record
- deploymentCommit.Name = name
- if err := tx.CreateOrUpdate(deploymentCommit.ToDeploymentWithCustomDisplayTitle(request.DisplayTitle)); err != nil {
- logger.Error(err, "create deployment")
- return err
- }
+ RefName: commit.RefName,
+ CommitSha: commit.CommitSha,
+ CommitMsg: commit.CommitMsg,
+ //QueuedDurationSec: queuedDuration,
}
}
- return nil
-}
-// PostDeploymentCicdTask
-// @Summary create deployment by webhook
-// @Description Create deployment pipeline by webhook.
-// @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"}
-// @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/connections/:connectionId/deployments [POST]
-func PostDeploymentCicdTask(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
- connection := &models.WebhookConnection{}
- err := connectionHelper.First(connection, input.Params)
- if err != nil {
- return nil, err
- }
- // get request
- request := &WebhookDeployTaskRequest{}
- err = api.DecodeMapStruct(input.Body, request, true)
- if err != nil {
- return &plugin.ApiResourceOutput{Body: err.Error(), Status: http.StatusBadRequest}, nil
+ if err := tx.CreateOrUpdate(deploymentCommits); err != nil {
+ logger.Error(err, "failed to save deployment commits")
+ return err
}
- // validate
- vld = validator.New()
- err = errors.Convert(vld.Struct(request))
- if err != nil {
- return nil, errors.BadInput.Wrap(vld.Struct(request), `input json error`)
- }
- txHelper := dbhelper.NewTxHelper(basicRes, &err)
- defer txHelper.End()
- tx := txHelper.Begin()
- if err := CreateDeploymentAndDeploymentCommits(connection, request, tx, logger); err != nil {
- logger.Error(err, "create deployments")
- return nil, err
+
+ // create a deployment record
+ deployment := deploymentCommits[0].ToDeploymentWithCustomDisplayTitle(request.DisplayTitle)
+ deployment.Name = name
+ deployment.CreatedDate = createdDate
+ deployment.StartedDate = request.StartedDate
+ deployment.FinishedDate = request.FinishedDate
+ if err := tx.CreateOrUpdate(deployment); err != nil {
+ logger.Error(err, "failed to save deployment")
+ return err
}
+ return nil
+}
- return &plugin.ApiResourceOutput{Body: nil, Status: http.StatusOK}, nil
+func GenerateDeploymentCommitId(connectionId uint64, deploymentId string, repoUrl string, commitSha string) string {
+ urlHash16 := fmt.Sprintf("%x", md5.Sum([]byte(repoUrl)))[:16]
+ return fmt.Sprintf("%s:%d:%s:%s:%s", "webhook", connectionId, deploymentId, urlHash16, commitSha)
}
diff --git a/backend/plugins/webhook/impl/impl.go b/backend/plugins/webhook/impl/impl.go
index 468a63841a6..6b1bac0afe0 100644
--- a/backend/plugins/webhook/impl/impl.go
+++ b/backend/plugins/webhook/impl/impl.go
@@ -88,7 +88,7 @@ func (p Webhook) ApiResources() map[string]map[string]plugin.ApiResourceHandler
"DELETE": api.DeleteConnection,
},
"connections/:connectionId/deployments": {
- "POST": api.PostDeploymentCicdTask,
+ "POST": api.PostDeployments,
},
"connections/:connectionId/issues": {
"POST": api.PostIssue,
@@ -97,7 +97,7 @@ func (p Webhook) ApiResources() map[string]map[string]plugin.ApiResourceHandler
"POST": api.CloseIssue,
},
":connectionId/deployments": {
- "POST": api.PostDeploymentCicdTask,
+ "POST": api.PostDeployments,
},
":connectionId/issues": {
"POST": api.PostIssue,