-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathrun.go
207 lines (168 loc) · 5.43 KB
/
run.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package server
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/avast/retry-go"
scm2 "github.com/herlon214/sonarqube-pr-issues/pkg/scm"
sonarqube2 "github.com/herlon214/sonarqube-pr-issues/pkg/sonarqube"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"io"
"net/http"
"os"
"time"
)
var RunCmd = &cobra.Command{
Use: "run",
Short: "Starts the webhook server",
Run: Run,
TraverseChildren: true,
}
func Run(cmd *cobra.Command, args []string) {
// Context
ctx := context.Background()
// Environment
if serverPort <= 0 {
logrus.Panicln("A valid --port is required")
return
}
apiKey := os.Getenv("SONAR_API_KEY")
if apiKey == "" {
logrus.Panicln("SONAR_API_KEY environment variable is missing")
return
}
sonarRootURL := os.Getenv("SONAR_ROOT_URL")
if sonarRootURL == "" {
logrus.Panicln("SONAR_ROOT_URL environment variable is missing")
return
}
ghToken := os.Getenv("GH_TOKEN")
if ghToken == "" {
logrus.Panicln("GH_TOKEN environment variable is missing")
return
}
webhookSecret := os.Getenv("WEBHOOK_SECRET")
if webhookSecret == "" {
logrus.Panicln("WEBHOOK_SECRET environment variable is missing")
return
}
// Sonarqube
sonar := sonarqube2.New(sonarRootURL, apiKey)
var gh scm2.SCM = scm2.NewGithub(ctx, sonar, ghToken)
// Process queue
queue := make(chan func() error, 0)
for i := 0; i < workers; i++ {
go ProcessQueue(queue)
}
// Listen
http.HandleFunc("/webhook", WebhookHandler(webhookSecret, sonar, gh, queue))
logrus.Infoln("Listening on port", serverPort)
if err := http.ListenAndServe(fmt.Sprintf(":%d", serverPort), nil); err != nil {
panic(err)
}
}
func WebhookHandler(webhookSecret string, sonar *sonarqube2.Sonarqube, gh scm2.SCM, queue chan<- func() error) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
// Read webhook secret
reqSecret := req.Header.Get("X-Sonar-Webhook-HMAC-SHA256")
if reqSecret == "" {
w.WriteHeader(http.StatusUnauthorized)
return
}
// Read request body
body, err := io.ReadAll(req.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// Generate hmac hash
h := hmac.New(sha256.New, []byte(webhookSecret))
// Write Data to it
h.Write(body)
// Get result and encode as hexadecimal string
sha := hex.EncodeToString(h.Sum(nil))
// Compare hashes
if sha != reqSecret {
w.WriteHeader(http.StatusUnauthorized)
return
}
// Unmarshal data
var webhook sonarqube2.WebhookData
err = json.Unmarshal(body, &webhook)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// Add event to queue
logrus.Infoln("Adding to the queue", webhook.Project.Key, "->", webhook.Branch.Name)
queue <- func() error {
logrus.Infoln("Processing", webhook.Project.Key, "->", webhook.Branch.Name)
if err := PublishIssues(context.Background(), sonar, gh, webhook.Project.Key, webhook.Branch.Name, webhook.Branch.Type); err != nil {
return err
}
logrus.Infoln("Issues published for", webhook.Project.Key, webhook.Branch.Name)
return nil
}
w.WriteHeader(http.StatusOK)
}
}
// ProcessQueue is made to process the webhooks in background
// if the request takes more than 10s Sonarqube shows the message 'Server Unreachable'
func ProcessQueue(queue <-chan func() error) {
for fn := range queue {
err := retry.Do(fn, retry.Delay(time.Minute), retry.DelayType(retry.FixedDelay), retry.Attempts(5))
if err != nil {
logrus.WithError(err).Errorln("Failed to process webhook")
}
}
}
// PublishIssues publishes the issues in the PR for the given project branch
func PublishIssues(ctx context.Context, sonar *sonarqube2.Sonarqube, projectScm scm2.SCM, project string, branch string, branchType string) error {
// Find PR
var pr *sonarqube2.PullRequest
var err error
if branchType == sonarqube2.BRANCH_TYPE_PULL_REQUEST {
pr, err = sonar.FindPRForKey(project, branch)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to find PR for key %s of the project %s", branch, project))
}
} else {
pr, err = sonar.FindPRForBranch(project, branch)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to find PR for branch %s of the project %s", branch, project))
}
}
// List issues
issues, err := sonar.ListIssuesForPR(project, pr.Key)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to list issues for the given PR branch %s of the project %s", branch, project))
}
// Filter issues
issues = issues.FilterByStatus("OPEN").FilterOutByTag(sonarqube2.TAG_PUBLISHED)
// No issues found
if len(issues.Issues) == 0 {
return nil
}
// Publish review
err = projectScm.PublishIssuesReviewFor(ctx, issues.Issues, pr, requestChanges)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("Failed to publish issues review for branch %s of the project %s", branch, project))
}
// Tag published issues
bulkActionRes, err := sonar.TagIssues(issues.Issues, sonarqube2.TAG_PUBLISHED)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to mark issues as published for branch %s of the project %s", branch, project))
}
logrus.Infoln("--------------------------")
logrus.Infoln("Mark as published result:")
logrus.Infoln(bulkActionRes.Success, "issues marked")
logrus.Infoln(bulkActionRes.Ignored, "issues ignored")
logrus.Infoln(bulkActionRes.Failures, "issues failed")
logrus.Infoln("--------------------------")
return nil
}