• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / integration-test-runner / 1583018314

10 Dec 2024 11:26PM UTC coverage: 68.703%. Remained the same
1583018314

Pull #336

gitlab-ci

danielskinstad
ci: pin docker to v27.3

Fixes runc errors in the hetzner runner
Use dependency proxy

Ticket: QA-823

Signed-off-by: Daniel Skinstad Drabitzius <daniel.drabitzius@northern.tech>
Pull Request #336: ci: pin docker to v27.3

1732 of 2521 relevant lines covered (68.7%)

2.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

5.9
/entfork_sync.go
1
package main
2

3
import (
4
        "bytes"
5
        "context"
6
        "errors"
7
        "fmt"
8
        "regexp"
9
        "strconv"
10
        "strings"
11
        "text/template"
12
        "time"
13

14
        "github.com/google/go-github/v28/github"
15
        "github.com/sirupsen/logrus"
16

17
        "github.com/mendersoftware/integration-test-runner/git"
18
)
19

20
// syncIfOSHasEnterpriseRepo detects whether a commit has been merged to
21
// the Open Source edition of a repo, and then creates a PR-branch
22
// in the Enterprise edition, which is then used in order to open
23
// a PR to the Enterprise repo with the OS changes.
24
func syncIfOSHasEnterpriseRepo(
25
        log *logrus.Entry,
26
        conf *config,
27
        gpr *github.PullRequestEvent,
28
) error {
2✔
29

2✔
30
        repo := gpr.GetRepo()
2✔
31
        if repo == nil {
2✔
32
                return fmt.Errorf("syncIfOSHasEnterpriseRepo: Failed to get the repository information")
×
33
        }
×
34

35
        // Enterprise repo sentinel
36
        switch repo.GetName() {
2✔
37
        case "deployments":
×
38
        case "inventory":
×
39
        case "useradm":
×
40
        case "workflows":
1✔
41
        case "deviceauth":
×
42
        case "mender-server":
×
43
        default:
2✔
44
                log.Debugf(
2✔
45
                        "syncIfOSHasEnterpriseRepo: Repository without Enterprise fork detected: (%s). "+
2✔
46
                                "Not syncing",
2✔
47
                        repo.GetName(),
2✔
48
                )
2✔
49
                return nil
2✔
50
        }
51

52
        pr := gpr.GetPullRequest()
1✔
53
        if pr == nil {
1✔
54
                return errors.New("syncIfOSHasEnterpriseRepo: Failed to get the pull request")
×
55
        }
×
56

57
        // If the action is "closed" and the "merged" key is "true", the pull request was merged.
58
        // While webhooks are also triggered when a pull request is synchronized, Events API timelines
59
        // don't include pull request events with the "synchronize" action.
60
        if gpr.GetAction() == "closed" && pr.GetMerged() {
1✔
61

×
62
                // Only sync on Merges to master, release and feature branches.
×
63
                // Verify release branches.
×
64
                branch := pr.GetBase()
×
65
                if branch == nil {
×
66
                        return fmt.Errorf(
×
67
                                "syncIfOSHasEnterpriseRepo: Failed to get the base-branch of the PR: %v",
×
68
                                branch,
×
69
                        )
×
70
                }
×
71

72
                syncBranches := regexp.MustCompile(
×
73
                        `(main|master|[0-9]+\.[0-9]+\.x|` + featureBranchPrefix + `.+)`,
×
74
                )
×
75
                branchRef := branch.GetRef()
×
76
                if branchRef == "" {
×
77
                        return fmt.Errorf("Failed to get the branch-ref from the PR: %v", pr)
×
78
                }
×
79
                if !syncBranches.MatchString(branchRef) {
×
80

×
81
                        log.Debugf(
×
82
                                "syncIfOSHasEnterpriseRepo: Detected a merge into another branch than master or "+
×
83
                                        "a release branch: (%s), no syncing done",
×
84
                                branchRef,
×
85
                        )
×
86

×
87
                } else {
×
88

×
89
                        log.Infof("syncIfOSHasEnterpriseRepo: Merge to (%s) in an OS repository detected. "+
×
90
                                "Syncing the repositories...", branchRef)
×
91

×
92
                        PRNumber := strconv.Itoa(pr.GetNumber())
×
93
                        PRBranchName := "mergeostoent_" + PRNumber
×
94

×
95
                        merged, err := createPRBranchOnEnterprise(
×
96
                                log,
×
97
                                repo.GetName(),
×
98
                                branchRef,
×
99
                                PRNumber,
×
100
                                PRBranchName,
×
101
                                conf,
×
102
                        )
×
103
                        if err != nil {
×
104
                                return fmt.Errorf("syncIfOSHasEnterpriseRepo: Failed to create the PR branch on "+
×
105
                                        "the Enterprise repo due to error: %v", err)
×
106
                        }
×
107

108
                        // Get the link to the original PR, so that it can be linked to
109
                        // in the commit body
110
                        PRURL := pr.GetHTMLURL()
×
111
                        var assignees []string
×
112
                        if issuer := pr.GetUser().GetLogin(); issuer != "" {
×
113
                                assignees = append(assignees, issuer)
×
114
                        }
×
115

116
                        enterprisePR, err := createPullRequestFromTestBotFork(createPRArgs{
×
117
                                conf:        conf,
×
118
                                repo:        repo.GetName() + "-enterprise",
×
119
                                prBranch:    githubBotName + ":" + PRBranchName,
×
120
                                baseBranch:  branchRef,
×
121
                                message:     fmt.Sprintf("[Bot] %s", pr.GetTitle()),
×
122
                                messageBody: fmt.Sprintf("Original PR: %s\n\n%s", PRURL, pr.GetBody()),
×
123
                                assignees:   assignees,
×
124
                        })
×
125
                        if err != nil {
×
126
                                return fmt.Errorf(
×
127
                                        "syncIfOSHasEnterpriseRepo: Failed to create a PR with error: %v",
×
128
                                        err,
×
129
                                )
×
130
                        }
×
131

132
                        log.Infof("syncIfOSHasEnterpriseRepo: Created PR: %d on Enterprise/%s/%s",
×
133
                                enterprisePR.GetNumber(), repo.GetName(), branchRef)
×
134
                        log.Debugf("syncIfOSHasEnterpriseRepo: Created PR: id=%d,number=%d,title=%s",
×
135
                                pr.GetID(), pr.GetNumber(), pr.GetTitle())
×
136
                        log.Debug("Trying to @mention the user in the newly created PR")
×
137
                        userName := pr.GetMergedBy().GetLogin()
×
138
                        log.Debugf("userName: %s", userName)
×
139

×
140
                        if userName != "" {
×
141
                                err = commentToNotifyUser(log, commentArgs{
×
142
                                        pr:             enterprisePR,
×
143
                                        conf:           conf,
×
144
                                        mergeConflicts: !merged,
×
145
                                        repo:           repo.GetName() + "-enterprise",
×
146
                                        userName:       userName,
×
147
                                        prBranchName:   PRBranchName,
×
148
                                        branchName:     branchRef,
×
149
                                })
×
150
                                if err != nil {
×
151
                                        log.Errorf("syncIfOSHasEnterpriseRepo: %s", err.Error())
×
152
                                }
×
153
                        }
154

155
                }
156

157
        }
158

159
        return nil
1✔
160
}
161

162
// createPRBranchOnEnterprise creates a new branch in the Enterprise repository
163
// starting at the branch in which to sync, with the name 'PRBranchName'
164
// and merges this with the OS equivalent of 'branchName'.
165
func createPRBranchOnEnterprise(
166
        log *logrus.Entry,
167
        repo, branchName, PRNumber, PRBranchName string,
168
        conf *config,
169
) (merged bool, err error) {
×
170

×
171
        state, err := git.Commands(
×
172
                git.Command("init", "."),
×
173
                git.Command("remote", "add", "opensource",
×
174
                        getRemoteURLGitHub(conf.githubProtocol, conf.githubOrganization, repo)),
×
175
                git.Command("remote", "add", "enterprise",
×
176
                        getRemoteURLGitHub(conf.githubProtocol, conf.githubOrganization, repo+"-enterprise")),
×
177
                git.Command("remote", "add", githubBotName,
×
178
                        getRemoteURLGitHub(conf.githubProtocol, githubBotName, repo+"-enterprise")),
×
179
                git.Command("config", "--add", "user.name", githubBotName),
×
180
                git.Command("config", "--add", "user.email", "mender@northern.tech"),
×
181
                git.Command("fetch", "opensource", branchName),
×
182
                git.Command("fetch", "enterprise", branchName+":"+PRBranchName),
×
183
                git.Command("checkout", PRBranchName),
×
184
        )
×
185
        defer state.Cleanup()
×
186
        if err != nil {
×
187
                return false, err
×
188
        }
×
189

190
        // Merge the OS branch into the PR branch
191
        mergeMsg := fmt.Sprintf(
×
192
                "Merge OS base branch: (%s) including PR: (%s) into Enterprise: (%[1]s)",
×
193
                branchName,
×
194
                PRNumber,
×
195
        )
×
196
        log.Debug("Trying to " + mergeMsg)
×
197
        gitcmd := git.Command("merge", "-m", mergeMsg, "opensource/"+branchName)
×
198
        gitcmd.Dir = state.Dir
×
199
        out, err := gitcmd.CombinedOutput()
×
200
        merged = true
×
201
        if err != nil {
×
202
                merged = false
×
203
                if strings.Contains(string(out), "Automatic merge failed") {
×
204
                        msg := "Merge conflict detected. Still pushing the Enterprise PR branch, " +
×
205
                                "and creating the PR, so that the user can manually resolve, " +
×
206
                                "and re-push to the PR once these are fixed"
×
207
                        log.Warn(msg)
×
208
                } else {
×
209
                        return false, fmt.Errorf("%v returned error: %s: %s", gitcmd.Args, out, err.Error())
×
210
                }
×
211
        }
212

213
        if !merged {
×
214
                // In case of a failed merge, reset PRBranchName to opensource/branchName
×
215
                // and push this branch to enterprise
×
216
                gitcmd = git.Command("reset", "--hard", "opensource/"+branchName)
×
217
                gitcmd.Dir = state.Dir
×
218
                out, err = gitcmd.CombinedOutput()
×
219
                if err != nil {
×
220
                        return merged, fmt.Errorf("%v returned error: %s: %s", gitcmd.Args, out, err.Error())
×
221
                }
×
222
        }
223

224
        // Push the branch to the bot's own fork
225
        gitcmd = git.Command("push", "--set-upstream", githubBotName, PRBranchName)
×
226
        gitcmd.Dir = state.Dir
×
227
        out, err = gitcmd.CombinedOutput()
×
228
        if err != nil {
×
229
                return merged, fmt.Errorf("%v returned error: %s: %s", gitcmd.Args, out, err.Error())
×
230
        }
×
231

232
        if merged {
×
233
                log.Infof("Merged branch: opensource/%s/%s into enterprise/%[1]s/%s in the Enterprise repo",
×
234
                        repo, branchName, PRBranchName)
×
235
        } else {
×
236
                msg := "Failed to merge opensource/%s/%s into enterprise/%[1]s/%s in the Enterprise " +
×
237
                        "repo. Therefore pushed opensource/%[1]s/%[2]s to %s so that " +
×
238
                        "merging can be done by a human locally"
×
239
                log.Infof(msg, repo, branchName, PRBranchName, PRBranchName)
×
240
        }
×
241

242
        return merged, nil
×
243
}
244

245
type createPRArgs struct {
246
        conf        *config
247
        repo        string
248
        prBranch    string
249
        baseBranch  string
250
        message     string
251
        messageBody string
252
        assignees   []string
253
}
254

255
func createPullRequestFromTestBotFork(args createPRArgs) (*github.PullRequest, error) {
×
256

×
257
        ctx, cancel := context.WithTimeout(context.TODO(), time.Second*30)
×
258
        defer cancel()
×
259

×
260
        newPR := &github.NewPullRequest{
×
261
                Title:               github.String(args.message),
×
262
                Head:                github.String(args.prBranch),
×
263
                Base:                github.String(args.baseBranch),
×
264
                Body:                github.String(args.messageBody),
×
265
                MaintainerCanModify: github.Bool(true),
×
266
        }
×
267

×
268
        pr, err := githubClient.CreatePullRequest(
×
269
                ctx,
×
270
                args.conf.githubOrganization,
×
271
                args.repo,
×
272
                newPR,
×
273
        )
×
274
        if err != nil {
×
275
                return nil, fmt.Errorf("Failed to create the PR for: (%s) %v", args.repo, err)
×
276
        }
×
277

278
        if len(args.assignees) > 0 {
×
279
                err = githubClient.AssignPullRequest(
×
280
                        ctx, args.conf.githubOrganization,
×
281
                        args.repo, pr.GetNumber(),
×
282
                        args.assignees,
×
283
                )
×
284
                if err != nil {
×
285
                        logrus.Warnf("failed to assign users %v to PR: %s", args.assignees, err)
×
286
                }
×
287
        }
288

289
        return pr, nil
×
290
}
291

292
type commentArgs struct {
293
        pr             *github.PullRequest
294
        conf           *config
295
        mergeConflicts bool
296
        repo           string
297
        userName       string
298
        prBranchName   string
299
        branchName     string
300
}
301

302
func commentToNotifyUser(log *logrus.Entry, args commentArgs) error {
×
303

×
304
        // Post a comment, and @mention the user
×
305
        var commentBody string
×
306
        if !args.mergeConflicts {
×
307
                commentBody = fmt.Sprintf(
×
308
                        "@%s I have created a PR for you, ready to merge as soon as tests are passed",
×
309
                        args.userName,
×
310
                )
×
311
        } else {
×
312
                // nolint:lll
×
313
                tmplString := `
×
314
@{{.UserName}} I have created a PR for you.
×
315

×
316
Unfortunately, a merge conflict was detected. This means that the conflict will have to be resolved manually by you, human. Then pushed to the PR-branch, once all conflicts are resolved.
×
317
This can be done by following:
×
318

×
319
<details>
×
320
    <summary><small>this</small> recipe</summary><p>
×
321

×
322
1. Make sure that the '{{.GitHubBotName}}' remote is present in your repository, or else add it with:
×
323
    1. {{.BackQuote}}git remote add {{.GitHubBotName}} git@github.com:{{.GitHubBotName}}/{{.Repo}}.git{{.BackQuote}}
×
324

×
325
2. Fetch the remote branches
×
326
    1. {{.BackQuote}}git fetch origin {{.BranchName}}:localtmp{{.BackQuote}}
×
327
    2. {{.BackQuote}}git fetch {{.GitHubBotName}} {{.PRBranchName}}{{.BackQuote}}
×
328

×
329
3. Checkout the localtmp branch
×
330
    1. {{.BackQuote}}git checkout localtmp{{.BackQuote}}
×
331

×
332
4. Merge the branch into the PR branch
×
333
    1. {{.BackQuote}}git merge {{.GitHubBotName}}/{{.PRBranchName}}{{.BackQuote}}
×
334

×
335
5. Resolve all conflicts
×
336

×
337
6. Commit the merged changes
×
338

×
339
7. Push to the PR branch
×
340
    1. {{.BackQuote}}git push {{.GitHubBotName}} localtmp:{{.PRBranchName}}{{.BackQuote}}
×
341

×
342
 </p></details>
×
343
`
×
344
                tmpl, err := template.New("Main").Parse(tmplString)
×
345
                if err != nil {
×
346
                        log.Error("The text template should never fail to render!")
×
347
                }
×
348
                var buf bytes.Buffer
×
349
                if err := tmpl.Execute(&buf, struct {
×
350
                        UserName      string
×
351
                        Repo          string
×
352
                        PRBranchName  string
×
353
                        BranchName    string
×
354
                        BackQuote     string
×
355
                        GitHubBotName string
×
356
                }{
×
357
                        UserName:      args.userName,
×
358
                        Repo:          args.repo,
×
359
                        PRBranchName:  args.prBranchName,
×
360
                        BranchName:    args.branchName,
×
361
                        BackQuote:     "`",
×
362
                        GitHubBotName: githubBotName,
×
363
                }); err != nil {
×
364
                        log.Errorf(
×
365
                                "Failed to execute the merge-conflict PR template string. Error: %s",
×
366
                                err.Error(),
×
367
                        )
×
368
                }
×
369
                commentBody = buf.String()
×
370
        }
371
        comment := github.IssueComment{
×
372
                Body: &commentBody,
×
373
        }
×
374

×
375
        return githubClient.CreateComment(
×
376
                context.Background(),
×
377
                args.conf.githubOrganization,
×
378
                args.repo,
×
379
                args.pr.GetNumber(),
×
380
                &comment,
×
381
        )
×
382
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc