@@ -26,11 +26,13 @@ import (
2626
2727 "github.com/apache/incubator-devlake/core/dal"
2828 "github.com/apache/incubator-devlake/core/log"
29+ "github.com/apache/incubator-devlake/server/services"
2930
3031 "github.com/apache/incubator-devlake/helpers/dbhelper"
3132 "github.com/go-playground/validator/v10"
3233
3334 "github.com/apache/incubator-devlake/core/errors"
35+ coremodels "github.com/apache/incubator-devlake/core/models"
3436 "github.com/apache/incubator-devlake/core/models/domainlayer"
3537 "github.com/apache/incubator-devlake/core/models/domainlayer/devops"
3638 "github.com/apache/incubator-devlake/core/plugin"
@@ -109,6 +111,105 @@ func PostDeploymentsByName(input *plugin.ApiResourceInput) (*plugin.ApiResourceO
109111 return postDeployments (input , connection , err )
110112}
111113
114+ // PostDeploymentsByProjectName
115+ // @Summary create deployment by project name
116+ // @Description Create deployment pipeline by project name.<br/>
117+ // @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/>
118+ // @Description So we suggest request before task after deployment pipeline finish.
119+ // @Description Both cicd_pipeline and cicd_task will be created
120+ // @Tags plugins/webhook
121+ // @Param body body WebhookDeploymentReq true "json body"
122+ // @Success 200
123+ // @Failure 400 {string} errcode.Error "Bad Request"
124+ // @Failure 403 {string} errcode.Error "Forbidden"
125+ // @Failure 500 {string} errcode.Error "Internal Error"
126+ // @Router /projects/:projectName/deployments [POST]
127+ func PostDeploymentsByProjectName (input * plugin.ApiResourceInput ) (* plugin.ApiResourceOutput , errors.Error ) {
128+ // find or create the connection for this project
129+ connection , err , shouldReturn := getOrCreateConnection (input )
130+ if shouldReturn {
131+ return nil , err
132+ }
133+
134+ return postDeployments (input , connection , err )
135+ }
136+
137+ func getOrCreateConnection (input * plugin.ApiResourceInput ) (* models.WebhookConnection , errors.Error , bool ) {
138+ connection := & models.WebhookConnection {}
139+ projectName := input .Params ["projectName" ]
140+ webhookName := fmt .Sprintf ("%s_deployments" , projectName )
141+ err := findByProjectName (connection , input .Params , pluginName , webhookName )
142+ dal := basicRes .GetDal ()
143+ if err != nil {
144+ // if not found, we will attempt to create a new connection
145+ // Use direct comparison against the package sentinel; only treat other errors as fatal.
146+ if ! dal .IsErrorNotFound (err ) {
147+ logger .Error (err , "failed to find webhook connection for project" , "projectName" , projectName )
148+ return nil , err , true
149+ }
150+
151+ // create the connection
152+ logger .Debug ("creating webhook connection for project %s" , projectName )
153+ connection .Name = webhookName
154+
155+ // find the project and blueprint with which we will associate this connection
156+ projectOutput , err := services .GetProject (projectName )
157+ if err != nil {
158+ logger .Error (err , "failed to find project for webhook connection" , "projectName" , projectName )
159+ return nil , err , true
160+ }
161+
162+ if projectOutput == nil {
163+ logger .Error (err , "project not found for webhook connection" , "projectName" , projectName )
164+ return nil , errors .NotFound .New ("project not found: " + projectName ), true
165+ }
166+
167+ if projectOutput .Blueprint == nil {
168+ logger .Error (err , "unable to create webhook as the project has no blueprint" , "projectName" , projectName )
169+ return nil , errors .BadInput .New ("project has no blueprint: " + projectName ), true
170+ }
171+
172+ connectionInput := & plugin.ApiResourceInput {
173+ Params : map [string ]string {
174+ "plugin" : "webhook" ,
175+ },
176+ Body : map [string ]interface {}{
177+ "name" : webhookName ,
178+ },
179+ }
180+
181+ err = connectionHelper .Create (connection , connectionInput )
182+ if err != nil {
183+ logger .Error (err , "failed to create webhook connection for project" , "projectName" , projectName )
184+ return nil , err , true
185+ }
186+
187+ // get the blueprint
188+ blueprintId := projectOutput .Blueprint .ID
189+ blueprint , err := services .GetBlueprint (blueprintId , true )
190+
191+ if err != nil {
192+ logger .Error (err , "failed to find blueprint for webhook connection" , "blueprintId" , blueprintId )
193+ return nil , err , true
194+ }
195+
196+ // we need to associate this connection with the blueprint
197+ blueprintConnection := & coremodels.BlueprintConnection {
198+ BlueprintId : blueprint .ID ,
199+ PluginName : pluginName ,
200+ ConnectionId : connection .ID ,
201+ }
202+
203+ logger .Info ("adding blueprint connection for blueprint %d and connection %d" , blueprint .ID , connection .ID )
204+ err = dal .Create (blueprintConnection )
205+ if err != nil {
206+ logger .Error (err , "failed to create blueprint connection for project" , "projectName" , projectName )
207+ return nil , err , true
208+ }
209+ }
210+ return connection , err , false
211+ }
212+
112213func postDeployments (input * plugin.ApiResourceInput , connection * models.WebhookConnection , err errors.Error ) (* plugin.ApiResourceOutput , errors.Error ) {
113214 if err != nil {
114215 return nil , err
@@ -251,3 +352,38 @@ func GenerateDeploymentCommitId(connectionId uint64, deploymentId string, repoUr
251352 urlHash16 := fmt .Sprintf ("%x" , md5 .Sum ([]byte (repoUrl )))[:16 ]
252353 return fmt .Sprintf ("%s:%d:%s:%s:%s" , "webhook" , connectionId , deploymentId , urlHash16 , commitSha )
253354}
355+
356+ // findByProjectName finds the connection by project name and plugin name
357+ func findByProjectName (connection interface {}, params map [string ]string , pluginName string , webhookName string ) errors.Error {
358+ projectName := params ["projectName" ]
359+ if projectName == "" {
360+ return errors .BadInput .New ("missing projectName" )
361+ }
362+ if len (projectName ) > 100 {
363+ return errors .BadInput .New ("invalid projectName" )
364+ }
365+ if pluginName == "" {
366+ return errors .BadInput .New ("missing pluginName" )
367+ }
368+ // We need to join three tables: _tool_webhook_connections, _devlake_blueprint_connections, and _devlake_blueprints
369+ // to find the connection associated with the given project name and plugin name.
370+ // The SQL query would look something like this:
371+ // SELECT wc.*
372+ // FROM _tool_webhook_connections AS wc
373+ // JOIN _devlake_blueprint_connections AS bc ON wc.id = bc.connection_id AND bc.plugin_name = ?
374+ // JOIN _devlake_blueprints AS bp ON bc.blueprint_id = bp.id
375+ // WHERE bp.project_name = ? and _tool_webhook_connections.name = ?
376+ // LIMIT 1;
377+
378+ basicRes .GetLogger ().Debug ("finding project webhook connection for project %s and plugin %s" , projectName , pluginName )
379+ // Using DAL to construct the query
380+ clauses := []dal.Clause {dal .From (connection )}
381+ clauses = append (clauses ,
382+ dal .Join ("left join _devlake_blueprint_connections bc ON _tool_webhook_connections.id = bc.connection_id and bc.plugin_name = ?" , pluginName ),
383+ dal .Join ("left join _devlake_blueprints bp ON bc.blueprint_id = bp.id" ),
384+ dal .Where ("bp.project_name = ? and _tool_webhook_connections.name = ?" , projectName , webhookName ),
385+ )
386+
387+ dal := basicRes .GetDal ()
388+ return dal .First (connection , clauses ... )
389+ }
0 commit comments