Skip to content

Commit b83d05d

Browse files
authored
fix(api): clean duplicate hooks (#5094)
Signed-off-by: francois samin <[email protected]>
1 parent e7198f8 commit b83d05d

File tree

3 files changed

+159
-1
lines changed

3 files changed

+159
-1
lines changed

engine/api/api.go

+8
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,14 @@ func (a *API) Serve(ctx context.Context) error {
699699
return migrate.CleanDuplicateNodes(ctx, a.DBConnectionFactory.GetDBMap())
700700
}})
701701

702+
migrate.Add(ctx, sdk.Migration{Name: "ListDuplicateHooks", Release: "0.44.0", Blocker: false, Automatic: true, ExecFunc: func(ctx context.Context) error {
703+
return migrate.CleanDuplicateHooks(ctx, a.DBConnectionFactory.GetDBMap(), a.Cache, true)
704+
}})
705+
706+
migrate.Add(ctx, sdk.Migration{Name: "CleanDuplicateHooks", Release: "0.44.0", Blocker: false, Automatic: false, ExecFunc: func(ctx context.Context) error {
707+
return migrate.CleanDuplicateHooks(ctx, a.DBConnectionFactory.GetDBMap(), a.Cache, false)
708+
}})
709+
702710
isFreshInstall, errF := version.IsFreshInstall(a.mustDB())
703711
if errF != nil {
704712
return sdk.WrapError(errF, "Unable to check if it's a fresh installation of CDS")
+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package migrate
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
7+
"github.com/ovh/cds/engine/api/project"
8+
9+
"github.com/go-gorp/gorp"
10+
"github.com/ovh/cds/engine/api/cache"
11+
"github.com/ovh/cds/engine/api/workflow"
12+
"github.com/ovh/cds/sdk"
13+
"github.com/ovh/cds/sdk/log"
14+
)
15+
16+
func CleanDuplicateHooks(ctx context.Context, db *gorp.DbMap, store cache.Store, dryrun bool) error {
17+
var ids []int64
18+
19+
if _, err := db.Select(&ids, "select id from workflow"); err != nil {
20+
return sdk.WrapError(err, "unable to select workflow")
21+
}
22+
23+
var mError = new(sdk.MultiError)
24+
for _, id := range ids {
25+
if err := cleanDuplicateHooks(ctx, db, store, id, dryrun); err != nil {
26+
mError.Append(err)
27+
log.Error(ctx, "migrate.CleanDuplicateHooks> unable to clean workflow %d: %v", id, err)
28+
}
29+
}
30+
31+
if mError.IsEmpty() {
32+
return nil
33+
}
34+
return mError
35+
}
36+
37+
func cleanDuplicateHooks(ctx context.Context, db *gorp.DbMap, store cache.Store, workflowID int64, dryrun bool) error {
38+
tx, err := db.Begin()
39+
if err != nil {
40+
return sdk.WithStack(err)
41+
}
42+
43+
defer tx.Rollback() // nolint
44+
45+
projectID, err := tx.SelectInt("SELECT project_id FROM workflow WHERE id = $1", workflowID)
46+
if err != nil {
47+
if err == sql.ErrNoRows {
48+
return nil
49+
}
50+
return sdk.WithStack(err)
51+
}
52+
53+
proj, err := project.LoadByID(tx, store, projectID,
54+
project.LoadOptions.WithApplicationWithDeploymentStrategies,
55+
project.LoadOptions.WithPipelines,
56+
project.LoadOptions.WithEnvironments,
57+
project.LoadOptions.WithIntegrations)
58+
if err != nil {
59+
return sdk.WrapError(err, "unable to load project %d", projectID)
60+
}
61+
62+
w, err := workflow.LoadAndLockByID(ctx, tx, store, *proj, workflowID, workflow.LoadOptions{})
63+
if err != nil {
64+
if sdk.ErrorIs(err, sdk.ErrNotFound) {
65+
return nil
66+
}
67+
return err
68+
}
69+
70+
if w.FromRepository != "" {
71+
return nil
72+
}
73+
74+
if w.FromTemplate != "" {
75+
return nil
76+
}
77+
78+
nbHooks := len(w.WorkflowData.Node.Hooks)
79+
if nbHooks < 2 {
80+
return nil
81+
}
82+
83+
var hookDoublons = []struct {
84+
x int
85+
y int
86+
}{}
87+
88+
for i, h1 := range w.WorkflowData.Node.Hooks {
89+
for j, h2 := range w.WorkflowData.Node.Hooks {
90+
if i != j && i < j && h1.Ref() == h2.Ref() {
91+
hookDoublons = append(hookDoublons, struct{ x, y int }{i, j})
92+
}
93+
}
94+
}
95+
96+
if len(hookDoublons) == 0 {
97+
return nil
98+
}
99+
100+
var idxToRemove []int64
101+
for _, doublon := range hookDoublons {
102+
h1 := w.WorkflowData.Node.Hooks[doublon.x]
103+
h2 := w.WorkflowData.Node.Hooks[doublon.y]
104+
if h1.ID < h2.ID {
105+
idxToRemove = append(idxToRemove, int64(doublon.y))
106+
} else {
107+
idxToRemove = append(idxToRemove, int64(doublon.x))
108+
}
109+
}
110+
111+
var newHooks []sdk.NodeHook
112+
for i, h := range w.WorkflowData.Node.Hooks {
113+
if !sdk.IsInInt64Array(int64(i), idxToRemove) {
114+
newHooks = append(newHooks, h)
115+
}
116+
}
117+
w.WorkflowData.Node.Hooks = newHooks
118+
119+
if err := workflow.Update(ctx, tx, store, *proj, w, workflow.UpdateOptions{DisableHookManagement: dryrun}); err != nil {
120+
return err
121+
}
122+
123+
if dryrun {
124+
log.Info(ctx, "migrate.cleanDuplicateHooks> workflow %s/%s (%d) should been cleaned", proj.Name, w.Name, w.ID)
125+
} else {
126+
if err := tx.Commit(); err != nil {
127+
return err
128+
}
129+
log.Info(ctx, "migrate.cleanDuplicateHooks> workflow %s/%s (%d) has been cleaned", proj.Name, w.Name, w.ID)
130+
}
131+
132+
return nil
133+
}

engine/api/workflow/dao.go

+18-1
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,24 @@ func Load(ctx context.Context, db gorp.SqlExecutor, store cache.Store, proj sdk.
362362
return res, nil
363363
}
364364

365-
// LoadByID loads a workflow for a given user (ie. checking permissions)
365+
// LoadAndLockByID loads a workflow
366+
func LoadAndLockByID(ctx context.Context, db gorp.SqlExecutor, store cache.Store, proj sdk.Project, id int64, opts LoadOptions) (*sdk.Workflow, error) {
367+
query := `
368+
select *
369+
from workflow
370+
where id = $1 for update skip locked`
371+
res, err := load(ctx, db, proj, opts, query, id)
372+
if err != nil {
373+
return nil, sdk.WrapError(err, "Unable to load workflow %d", id)
374+
}
375+
376+
if err := IsValid(context.TODO(), store, db, res, proj, opts); err != nil {
377+
return nil, sdk.WrapError(err, "Unable to valid workflow")
378+
}
379+
return res, nil
380+
}
381+
382+
// LoadByID loads a workflow
366383
func LoadByID(ctx context.Context, db gorp.SqlExecutor, store cache.Store, proj sdk.Project, id int64, opts LoadOptions) (*sdk.Workflow, error) {
367384
query := `
368385
select *

0 commit comments

Comments
 (0)