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

mendersoftware / integration-test-runner / 1887464315

16 Jun 2025 02:47PM UTC coverage: 64.238% (+64.2%) from 0.0%
1887464315

push

gitlab-ci

web-flow
Merge pull request #385 from mzedel/qa-1017

QA-1017 - dependabot reviewers to CODEOWNERS

1904 of 2964 relevant lines covered (64.24%)

2.14 hits per line

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

73.78
/main_comment.go
1
package main
2

3
import (
4
        "errors"
5
        "fmt"
6
        "strconv"
7
        "strings"
8
        "time"
9

10
        "github.com/davecgh/go-spew/spew"
11
        "github.com/gin-gonic/gin"
12
        "github.com/google/go-github/v28/github"
13
        "github.com/sirupsen/logrus"
14

15
        gitlab "gitlab.com/gitlab-org/api/client-go"
16

17
        clientgithub "github.com/mendersoftware/integration-test-runner/client/github"
18
        clientgitlab "github.com/mendersoftware/integration-test-runner/client/gitlab"
19
)
20

21
// nolint: gocyclo
22
func processGitHubComment(
23
        ctx *gin.Context,
24
        comment *github.IssueCommentEvent,
25
        githubClient clientgithub.Client,
26
        conf *config,
27
) error {
11✔
28
        log := getCustomLoggerFromContext(ctx)
11✔
29

11✔
30
        // process created actions only, ignore the others
11✔
31
        action := comment.GetAction()
11✔
32
        if action != "created" {
12✔
33
                log.Infof("Ignoring action %s on comment", action)
1✔
34
                return nil
1✔
35
        }
1✔
36

37
        // accept commands only from organization members
38
        if !githubClient.IsOrganizationMember(ctx, conf.githubOrganization, comment.Sender.GetLogin()) {
13✔
39
                log.Warnf(
3✔
40
                        "%s commented, but he/she is not a member of our organization, ignoring",
3✔
41
                        comment.Sender.GetLogin(),
3✔
42
                )
3✔
43
                return nil
3✔
44
        }
3✔
45

46
        // but ignore comments from myself
47
        if comment.Sender.GetLogin() == githubBotName {
7✔
48
                log.Warnf("%s commented, probably giving instructions, ignoring", comment.Sender.GetLogin())
×
49
                return nil
×
50
        }
×
51

52
        // filter comments mentioning the bot
53
        commentBody := comment.Comment.GetBody()
7✔
54
        if !strings.Contains(commentBody, "@"+githubBotName) {
7✔
55
                log.Info("ignoring comment not mentioning me")
×
56
                return nil
×
57
        }
×
58

59
        // retrieve the pull request
60
        prLink := comment.Issue.GetPullRequestLinks().GetURL()
7✔
61
        if prLink == "" {
8✔
62
                log.Warnf("ignoring comment not on a pull request")
1✔
63
                return nil
1✔
64
        }
1✔
65

66
        prLinkParts := strings.Split(prLink, "/")
6✔
67
        prNumber, err := strconv.Atoi(prLinkParts[len(prLinkParts)-1])
6✔
68
        if err != nil {
7✔
69
                log.Errorf("Unable to retrieve the pull request: %s", err.Error())
1✔
70
                return err
1✔
71
        }
1✔
72

73
        pr, err := githubClient.GetPullRequest(
5✔
74
                ctx,
5✔
75
                conf.githubOrganization,
5✔
76
                comment.GetRepo().GetName(),
5✔
77
                prNumber,
5✔
78
        )
5✔
79
        if err != nil {
6✔
80
                log.Errorf("Unable to retrieve the pull request: %s", err.Error())
1✔
81
                return err
1✔
82
        }
1✔
83

84
        // extract the command and check it is valid
85
        switch {
4✔
86
        case strings.Contains(commentBody, commandStartIntegrationPipeline):
1✔
87
                prRequest := &github.PullRequestEvent{
1✔
88
                        Repo:        comment.GetRepo(),
1✔
89
                        Number:      github.Int(pr.GetNumber()),
1✔
90
                        PullRequest: pr,
1✔
91
                }
1✔
92
                build := getIntegrationBuild(log, conf, prRequest)
1✔
93

1✔
94
                _, err = syncProtectedBranch(log, prRequest, conf, integrationPipelinePath)
1✔
95
                if err != nil {
1✔
96
                        _ = say(ctx, "There was an error while syncing branches: {{.ErrorMessage}}",
×
97
                                struct {
×
98
                                        ErrorMessage string
×
99
                                }{
×
100
                                        ErrorMessage: err.Error(),
×
101
                                },
×
102
                                log,
×
103
                                conf,
×
104
                                prRequest)
×
105
                        return err
×
106

×
107
                }
×
108

109
                // start the build
110
                if err := triggerIntegrationBuild(log, conf, &build, prRequest, nil); err != nil {
1✔
111
                        log.Errorf("Could not start build: %s", err.Error())
×
112
                }
×
113
        case strings.Contains(commentBody, commandStartClientPipeline):
4✔
114
                buildOptions, err := parseBuildOptions(commentBody)
4✔
115
                // get the list of builds
4✔
116
                prRequest := &github.PullRequestEvent{
4✔
117
                        Repo:        comment.GetRepo(),
4✔
118
                        Number:      github.Int(pr.GetNumber()),
4✔
119
                        PullRequest: pr,
4✔
120
                }
4✔
121
                if err != nil {
5✔
122
                        _ = say(ctx, "There was an error while parsing arguments: {{.ErrorMessage}}",
1✔
123
                                struct {
1✔
124
                                        ErrorMessage string
1✔
125
                                }{
1✔
126
                                        ErrorMessage: err.Error(),
1✔
127
                                },
1✔
128
                                log,
1✔
129
                                conf,
1✔
130
                                prRequest)
1✔
131
                        return err
1✔
132
                }
1✔
133
                builds := parseClientPullRequest(log, conf, "opened", prRequest)
3✔
134
                log.Infof(
3✔
135
                        "%s:%d will trigger %d builds",
3✔
136
                        comment.GetRepo().GetName(),
3✔
137
                        pr.GetNumber(),
3✔
138
                        len(builds),
3✔
139
                )
3✔
140

3✔
141
                // start the builds
3✔
142
                for idx, build := range builds {
4✔
143
                        log.Infof("%d: "+spew.Sdump(build)+"\n", idx+1)
1✔
144
                        if build.repo == "meta-mender" && build.baseBranch == "master-next" {
1✔
145
                                log.Info("Skipping build targeting meta-mender:master-next")
×
146
                                continue
×
147
                        }
148
                        if err := triggerClientBuild(log, conf, &build, prRequest, buildOptions); err != nil {
1✔
149
                                log.Errorf("Could not start build: %s", err.Error())
×
150
                        }
×
151
                }
152
        case strings.Contains(commentBody, commandCherryPickBranch):
1✔
153
                log.Infof("Attempting to cherry-pick the changes in PR: %s/%d",
1✔
154
                        comment.GetRepo().GetName(),
1✔
155
                        pr.GetNumber(),
1✔
156
                )
1✔
157
                err = cherryPickPR(log, comment, pr, conf, commentBody, githubClient)
1✔
158
                if err != nil {
1✔
159
                        log.Error(err)
×
160
                }
×
161
        case strings.Contains(commentBody, commandConventionalCommit) &&
162
                strings.Contains(pr.GetUser().GetLogin(), "dependabot"):
1✔
163
                log.Infof(
1✔
164
                        "Attempting to make the PR: %s/%d and commit: %s a conventional commit",
1✔
165
                        comment.GetRepo().GetName(),
1✔
166
                        pr.GetNumber(),
1✔
167
                        pr.GetHead().GetSHA(),
1✔
168
                )
1✔
169
                err = conventionalComittifyDependabotPr(log, comment, pr, conf, commentBody, githubClient)
1✔
170
                if err != nil {
1✔
171
                        log.Error(err)
×
172
                }
×
173
        case strings.Contains(commentBody, commandSyncRepos):
×
174
                syncPRBranch(ctx, comment, pr, log, conf)
×
175
        default:
×
176
                log.Warnf("no command found: %s", commentBody)
×
177
                return nil
×
178
        }
179

180
        return nil
3✔
181
}
182

183
func protectBranch(conf *config, branchName string, pipelinePath string) error {
1✔
184
        // https://docs.gitlab.com/ee/api/protected_branches.html#protect-repository-branches
1✔
185
        allow_force_push := false
1✔
186
        opt := &gitlab.ProtectRepositoryBranchesOptions{
1✔
187
                Name:           &branchName,
1✔
188
                AllowForcePush: &allow_force_push,
1✔
189
        }
1✔
190

1✔
191
        client, err := clientgitlab.NewGitLabClient(
1✔
192
                conf.gitlabToken,
1✔
193
                conf.gitlabBaseURL,
1✔
194
                conf.dryRunMode,
1✔
195
        )
1✔
196

1✔
197
        if err != nil {
1✔
198
                return err
×
199
        }
×
200

201
        _, err = client.ProtectRepositoryBranches(pipelinePath, opt)
1✔
202
        if err != nil {
1✔
203
                return fmt.Errorf("%v returned error: %s", err, err.Error())
×
204
        }
×
205
        return nil
1✔
206
}
207

208
func syncProtectedBranch(
209
        log *logrus.Entry,
210
        pr *github.PullRequestEvent,
211
        conf *config,
212
        pipelinePath string,
213
) (string, error) {
1✔
214
        prBranchName := "pr_" + strconv.Itoa(pr.GetNumber()) + "_protected"
1✔
215

1✔
216
        // check if we have a protected branch and try to delete it
1✔
217
        response, err := deletePRBranch(pr, conf, prBranchName, log)
1✔
218
        if err != nil {
1✔
219
                // Don't return error if the branch doesn't exist
×
220
                if response.StatusCode != 404 {
×
221
                        return "", fmt.Errorf("Got response: %d. Failed to delete PR branch: %s",
×
222
                                response.StatusCode, err.Error())
×
223
                }
×
224
        }
225
        // Arbitrary sleep to ensure the branch and protection
226
        // is fully deleted before we sync
227
        time.Sleep(time.Duration(5) * time.Second)
1✔
228
        if err := syncBranch(prBranchName, log, pr, conf); err != nil {
1✔
229
                mainErrMsg := "There was an error syncing branches"
×
230
                return "", fmt.Errorf("%v returned error: %s: %s", err, mainErrMsg, err.Error())
×
231
        }
×
232
        // Arbitrary sleep to ensure the branch is
233
        // created before we protect it
234
        time.Sleep(time.Duration(5) * time.Second)
1✔
235
        if err := protectBranch(conf, prBranchName, pipelinePath); err != nil {
1✔
236
                return "", fmt.Errorf("%v returned error: %s", err, err.Error())
×
237
        }
×
238
        return prBranchName, nil
1✔
239
}
240

241
func syncPRBranch(
242
        ctx *gin.Context,
243
        comment *github.IssueCommentEvent,
244
        pr *github.PullRequest,
245
        log *logrus.Entry,
246
        conf *config,
247
) {
×
248
        prEvent := &github.PullRequestEvent{
×
249
                Repo:        comment.GetRepo(),
×
250
                Number:      github.Int(pr.GetNumber()),
×
251
                PullRequest: pr,
×
252
        }
×
253
        if _, err := syncPullRequestBranch(log, prEvent, conf); err != nil {
×
254
                mainErrMsg := "There was an error syncing branches"
×
255
                log.Errorf(mainErrMsg+": %s", err.Error())
×
256
                msg := mainErrMsg + ", " + msgDetailsKubernetesLog
×
257
                postGitHubMessage(ctx, prEvent, log, msg)
×
258
        }
×
259
}
260

261
// parsing `start client pipeline --pr mender-connect/pull/88/head --pr deviceconnect/pull/12/head
262
// --pr mender/3.1.x --fast sugar pretty please`
263
//
264
//        BuildOptions {
265
//                Fast: true,
266
//                PullRequests: map[string]string{
267
//                        "mender-connect": "pull/88/head",
268
//                        "deviceconnect": "pull/12/head",
269
//                }
270
//        }
271
func parseBuildOptions(commentBody string) (*BuildOptions, error) {
16✔
272
        buildOptions := NewBuildOptions()
16✔
273
        var err error
16✔
274
        words := strings.Fields(commentBody)
16✔
275
        tokensCount := len(words)
16✔
276
        for id, word := range words {
135✔
277
                if word == "--pr" && id < (tokensCount-1) {
151✔
278
                        userInput := strings.TrimSpace(words[id+1])
32✔
279
                        userInputParts := strings.Split(userInput, "/")
32✔
280

32✔
281
                        if len(userInput) > 0 {
64✔
282
                                var revision string
32✔
283
                                switch len(userInputParts) {
32✔
284
                                case 2: // we can have both deviceauth/1 and mender/3.1.x syntax
12✔
285
                                        // repo/<pr_number> syntax
12✔
286
                                        if _, err := strconv.Atoi(userInputParts[1]); err == nil {
15✔
287
                                                revision = "pull/" + userInputParts[1] + "/head"
3✔
288
                                        } else {
13✔
289
                                                // feature branch
10✔
290
                                                revision = userInputParts[1]
10✔
291
                                        }
10✔
292
                                case 3: // deviceconnect/pull/12 syntax
2✔
293
                                        revision = strings.Join(userInputParts[1:], "/") + "/head"
2✔
294
                                case 4: // deviceauth/pull/1/head syntax
13✔
295
                                        revision = strings.Join(userInputParts[1:], "/")
13✔
296
                                default:
6✔
297
                                        err = errors.New(
6✔
298
                                                "parse error near '" + userInput + "', I need, e.g.: start client" +
6✔
299
                                                        " pipeline --pr somerepo/pull/12/head --pr somerepo/1.0.x ",
6✔
300
                                        )
6✔
301
                                }
302
                                buildOptions.PullRequests[userInputParts[0]] = revision
32✔
303
                        }
304
                } else if word == "--fast" {
89✔
305
                        buildOptions.Fast = true
1✔
306
                }
1✔
307
        }
308

309
        return buildOptions, err
16✔
310
}
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