Retrieve actual data from SonarQube for comment

Signed-off-by: Steven Kriegler <61625851+justusbunsi@users.noreply.github.com>
This commit is contained in:
justusbunsi 2021-10-09 18:09:54 +02:00
parent c3566d9208
commit e608a8f969
No known key found for this signature in database
GPG key ID: 990B348ECAC9C7DB
7 changed files with 102 additions and 39 deletions

View file

@ -28,7 +28,7 @@ func (sdk *GiteaSdk) PostComment(repo settings.GiteaRepository, idx int, msg str
func New() *GiteaSdk { func New() *GiteaSdk {
client, err := gitea.NewClient(settings.Gitea.Url, gitea.SetToken(settings.Gitea.Token.Value)) client, err := gitea.NewClient(settings.Gitea.Url, gitea.SetToken(settings.Gitea.Token.Value))
if err != nil { if err != nil {
panic(fmt.Errorf("Cannot initialize Gitea client: %w", err)) panic(fmt.Errorf("cannot initialize Gitea client: %w", err))
} }
return &GiteaSdk{client} return &GiteaSdk{client}

View file

@ -0,0 +1,53 @@
package sonarqube_sdk
import (
"fmt"
"strings"
)
type period struct {
Value string `json:"value"`
}
type MeasuresComponentMeasure struct {
Metric string `json:"metric"`
Value string `json:"value"`
Period *period `json:"period,omitempty"`
}
type MeasuresComponentMetric struct {
Key string `json:"key"`
Name string `json:"name"`
}
type MeasuresComponent struct {
PullRequest string `json:"pullRequest"`
Measures []MeasuresComponentMeasure `json:"measures"`
}
type MeasuresResponse struct {
Component MeasuresComponent `json:"component"`
Metrics []MeasuresComponentMetric `json:"metrics"`
}
func (mr *MeasuresResponse) GetRenderedMarkdownTable() string {
metricsTranslations := map[string]string{}
for _, metric := range mr.Metrics {
metricsTranslations[metric.Key] = metric.Name
}
measures := make([]string, len(mr.Component.Measures))
for i, measure := range mr.Component.Measures {
value := measure.Value
if measure.Period != nil {
value = measure.Period.Value
}
measures[i] = fmt.Sprintf("| %s | %s |", metricsTranslations[measure.Metric], value)
}
table := `
| Metric | Current |
| -------- | -------- |
%s`
return fmt.Sprintf(table, strings.Join(measures, "\n"))
}

View file

@ -2,16 +2,16 @@ package sonarqube_sdk
import ( import (
"encoding/base64" "encoding/base64"
"encoding/json"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"gitea-sonarqube-pr-bot/internal/settings" "gitea-sonarqube-pr-bot/internal/settings"
) )
type SonarQubeSdkInterface interface { type SonarQubeSdkInterface interface {
GetMeasures(string, string) (string, error) GetMeasures(string, string) (*MeasuresResponse, error)
} }
type SonarQubeSdk struct { type SonarQubeSdk struct {
@ -20,20 +20,26 @@ type SonarQubeSdk struct {
token string token string
} }
func (sdk *SonarQubeSdk) GetMeasures(project string, branch string) (string, error) { func (sdk *SonarQubeSdk) GetMeasures(project string, branch string) (*MeasuresResponse, error) {
url := fmt.Sprintf("%s/api/measures/component?additionalFields=metrics&metricKeys=bugs,vulnerabilities,new_security_hotspots,violations&component=%s&pullRequest=%s", sdk.baseUrl, project, branch) url := fmt.Sprintf("%s/api/measures/component?additionalFields=metrics&metricKeys=bugs,vulnerabilities,new_security_hotspots,violations&component=%s&pullRequest=%s", sdk.baseUrl, project, branch)
log.Println(url) req, err := http.NewRequest(http.MethodGet, url, nil)
req, err := http.NewRequest("GET", url, nil)
if err != nil { if err != nil {
panic(fmt.Errorf("Cannot initialize Request: %w", err)) return nil, fmt.Errorf("cannot initialize Request: %w", err)
} }
req.Header.Add("Authorization", sdk.basicAuth()) req.Header.Add("Authorization", sdk.basicAuth())
resp, _ := sdk.client.Do(req) rawResp, _ := sdk.client.Do(req)
if rawResp.Body != nil {
defer rawResp.Body.Close()
}
defer resp.Body.Close() body, _ := io.ReadAll(rawResp.Body)
body, _ := io.ReadAll(resp.Body) response := &MeasuresResponse{}
err = json.Unmarshal(body, &response)
if err != nil {
return nil, fmt.Errorf("cannot parse response from SonarQube: %w", err)
}
return string(body), nil return response, nil
} }
func (sdk *SonarQubeSdk) basicAuth() string { func (sdk *SonarQubeSdk) basicAuth() string {

View file

@ -12,6 +12,7 @@ import (
giteaSdk "gitea-sonarqube-pr-bot/internal/clients/gitea_sdk" giteaSdk "gitea-sonarqube-pr-bot/internal/clients/gitea_sdk"
sqSdk "gitea-sonarqube-pr-bot/internal/clients/sonarqube_sdk" sqSdk "gitea-sonarqube-pr-bot/internal/clients/sonarqube_sdk"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )

View file

@ -20,34 +20,23 @@ type SonarQubeWebhookHandler struct {
sqSdk sqSdk.SonarQubeSdkInterface sqSdk sqSdk.SonarQubeSdkInterface
} }
func (h *SonarQubeWebhookHandler) composeGiteaComment(w *webhook.Webhook) string { func (h *SonarQubeWebhookHandler) composeGiteaComment(w *webhook.Webhook) (string, error) {
a, _ := h.sqSdk.GetMeasures(w.Project.Key, w.Branch.Name) m, err := h.sqSdk.GetMeasures(w.Project.Key, w.Branch.Name)
if err != nil {
log.Println(a) return "", err
status := ":white_check_mark:"
if w.QualityGate.Status != "OK" {
status = ":x:"
} }
measures := `| Metric | Current | message := make([]string, 5)
| -------- | -------- | message[0] = w.GetRenderedQualityGate()
| Bugs | 123 | message[1] = m.GetRenderedMarkdownTable()
| Code Smells | 1 | message[2] = fmt.Sprintf("See [SonarQube](%s) for details.", w.Branch.Url)
| Vulnerabilities | 1 | message[3] = "---"
` message[4] = "- If you want the bot to check again, post `/sqbot review`"
msg := `**Quality Gate**: %s return strings.Join(message, "\n\n"), nil
**Measures**
%s
See [SonarQube](https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1) for details.`
return fmt.Sprintf(msg, status, measures)
} }
func (_ *SonarQubeWebhookHandler) inProjectsMapping(p []settings.Project, n string) (bool, int) { func (*SonarQubeWebhookHandler) inProjectsMapping(p []settings.Project, n string) (bool, int) {
for idx, proj := range p { for idx, proj := range p {
if proj.SonarQube.Key == n { if proj.SonarQube.Key == n {
return true, idx return true, idx
@ -65,7 +54,11 @@ func (h *SonarQubeWebhookHandler) processData(w *webhook.Webhook, repo settings.
h.fetchDetails(w) h.fetchDetails(w)
comment := h.composeGiteaComment(w) comment, err := h.composeGiteaComment(w)
if err != nil {
log.Printf("Error composing Gitea comment: %s", err.Error())
return
}
h.giteaSdk.PostComment(repo, w.PRIndex, comment) h.giteaSdk.PostComment(repo, w.PRIndex, comment)
} }

View file

@ -6,6 +6,7 @@ import (
"net/http/httptest" "net/http/httptest"
"testing" "testing"
sqSDK "gitea-sonarqube-pr-bot/internal/clients/sonarqube_sdk"
"gitea-sonarqube-pr-bot/internal/settings" "gitea-sonarqube-pr-bot/internal/settings"
webhook "gitea-sonarqube-pr-bot/internal/webhooks/sonarqube" webhook "gitea-sonarqube-pr-bot/internal/webhooks/sonarqube"
@ -33,8 +34,8 @@ type SQSdkMock struct {
mock.Mock mock.Mock
} }
func (h *SQSdkMock) GetMeasures(project string, branch string) (string, error) { func (h *SQSdkMock) GetMeasures(project string, branch string) (*sqSDK.MeasuresResponse, error) {
return "", nil return &sqSDK.MeasuresResponse{}, nil
} }
func defaultMockPreparation(h *HandlerPartialMock) { func defaultMockPreparation(h *HandlerPartialMock) {

View file

@ -33,6 +33,15 @@ type Webhook struct {
PRIndex int PRIndex int
} }
func (w *Webhook) GetRenderedQualityGate() string {
status := ":white_check_mark:"
if w.QualityGate.Status != "OK" {
status = ":x:"
}
return fmt.Sprintf("**Quality Gate**: %s", status)
}
func New(raw []byte) (*Webhook, bool) { func New(raw []byte) (*Webhook, bool) {
v := viper.New() v := viper.New()
v.SetConfigType("json") v.SetConfigType("json")
@ -61,8 +70,8 @@ func parsePRIndex(w *Webhook) (int, error) {
re := regexp.MustCompile(`^PR-(\d+)$`) re := regexp.MustCompile(`^PR-(\d+)$`)
res := re.FindSubmatch([]byte(w.Branch.Name)) res := re.FindSubmatch([]byte(w.Branch.Name))
if len(res) != 2 { if len(res) != 2 {
return 0, fmt.Errorf("Branch name '%s' does not match regex '%s'.", w.Branch.Name, re.String()) return 0, fmt.Errorf("branch name '%s' does not match regex '%s'", w.Branch.Name, re.String())
} }
return strconv.Atoi(fmt.Sprintf("%s", res[1])) return strconv.Atoi(string(res[1]))
} }