2021-10-10 16:43:37 +02:00
|
|
|
package sonarqube
|
2021-07-12 16:58:48 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/base64"
|
2021-10-09 18:09:54 +02:00
|
|
|
"encoding/json"
|
2021-10-03 17:57:22 +02:00
|
|
|
"fmt"
|
2021-07-12 16:58:48 +02:00
|
|
|
"io"
|
2021-10-10 19:01:48 +02:00
|
|
|
"log"
|
2021-10-03 17:57:22 +02:00
|
|
|
"net/http"
|
2021-10-10 19:01:48 +02:00
|
|
|
"regexp"
|
|
|
|
"strconv"
|
2021-10-10 19:39:33 +02:00
|
|
|
"strings"
|
2021-07-12 16:58:48 +02:00
|
|
|
|
2021-10-10 19:39:33 +02:00
|
|
|
"gitea-sonarqube-pr-bot/internal/actions"
|
2021-07-12 16:58:48 +02:00
|
|
|
"gitea-sonarqube-pr-bot/internal/settings"
|
|
|
|
)
|
|
|
|
|
2021-10-10 19:01:48 +02:00
|
|
|
func ParsePRIndex(name string) (int, error) {
|
|
|
|
re := regexp.MustCompile(`^PR-(\d+)$`)
|
|
|
|
res := re.FindSubmatch([]byte(name))
|
|
|
|
if len(res) != 2 {
|
|
|
|
return 0, fmt.Errorf("branch name '%s' does not match regex '%s'", name, re.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
return strconv.Atoi(string(res[1]))
|
|
|
|
}
|
|
|
|
|
|
|
|
func PRNameFromIndex(index int64) string {
|
|
|
|
return fmt.Sprintf("PR-%d", index)
|
|
|
|
}
|
|
|
|
|
2021-10-10 19:39:33 +02:00
|
|
|
func GetRenderedQualityGate(qg string) string {
|
|
|
|
status := ":white_check_mark:"
|
|
|
|
if qg != "OK" {
|
|
|
|
status = ":x:"
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("**Quality Gate**: %s", status)
|
|
|
|
}
|
|
|
|
|
2021-07-12 16:58:48 +02:00
|
|
|
type SonarQubeSdkInterface interface {
|
2021-10-09 18:09:54 +02:00
|
|
|
GetMeasures(string, string) (*MeasuresResponse, error)
|
2021-10-10 19:01:48 +02:00
|
|
|
GetPullRequestUrl(string, int64) string
|
|
|
|
GetPullRequest(string, int64) (*PullRequest, error)
|
2021-10-10 19:39:33 +02:00
|
|
|
ComposeGiteaComment(*CommentComposeData) (string, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type CommentComposeData struct {
|
|
|
|
Key string
|
|
|
|
PRName string
|
|
|
|
Url string
|
|
|
|
QualityGate string
|
2021-07-12 16:58:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type SonarQubeSdk struct {
|
2021-10-03 17:57:22 +02:00
|
|
|
client *http.Client
|
2021-07-12 16:58:48 +02:00
|
|
|
baseUrl string
|
2021-10-03 17:57:22 +02:00
|
|
|
token string
|
2021-07-12 16:58:48 +02:00
|
|
|
}
|
|
|
|
|
2021-10-10 19:01:48 +02:00
|
|
|
func (sdk *SonarQubeSdk) GetPullRequestUrl(project string, index int64) string {
|
|
|
|
return fmt.Sprintf("%s/dashboard?id=%s&pullRequest=%s", sdk.baseUrl, project, PRNameFromIndex(index))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sdk *SonarQubeSdk) fetchPullRequests(project string) *PullsResponse {
|
|
|
|
url := fmt.Sprintf("%s/api/project_pull_requests/list?project=%s", sdk.baseUrl, project)
|
|
|
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Cannot initialize Request: %s", err.Error())
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
req.Header.Add("Authorization", sdk.basicAuth())
|
|
|
|
rawResp, _ := sdk.client.Do(req)
|
|
|
|
if rawResp.Body != nil {
|
|
|
|
defer rawResp.Body.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
body, _ := io.ReadAll(rawResp.Body)
|
|
|
|
response := &PullsResponse{}
|
|
|
|
err = json.Unmarshal(body, &response)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("cannot parse response from SonarQube: %s", err.Error())
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return response
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sdk *SonarQubeSdk) GetPullRequest(project string, index int64) (*PullRequest, error) {
|
|
|
|
response := sdk.fetchPullRequests(project)
|
|
|
|
if response == nil {
|
|
|
|
return nil, fmt.Errorf("unable to retrieve pull requests from SonarQube")
|
|
|
|
}
|
|
|
|
|
|
|
|
name := PRNameFromIndex(index)
|
|
|
|
pr := response.GetPullRequest(name)
|
|
|
|
if pr == nil {
|
|
|
|
return nil, fmt.Errorf("no pull request found with name '%s'", name)
|
|
|
|
}
|
|
|
|
|
|
|
|
return pr, nil
|
|
|
|
}
|
|
|
|
|
2021-10-09 18:09:54 +02:00
|
|
|
func (sdk *SonarQubeSdk) GetMeasures(project string, branch string) (*MeasuresResponse, error) {
|
2021-10-12 13:10:35 +02:00
|
|
|
url := fmt.Sprintf("%s/api/measures/component?additionalFields=metrics&metricKeys=%s&component=%s&pullRequest=%s", sdk.baseUrl, settings.SonarQube.GetMetricsList(), project, branch)
|
2021-10-09 18:09:54 +02:00
|
|
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
2021-07-12 16:58:48 +02:00
|
|
|
if err != nil {
|
2021-10-09 18:09:54 +02:00
|
|
|
return nil, fmt.Errorf("cannot initialize Request: %w", err)
|
2021-07-12 16:58:48 +02:00
|
|
|
}
|
|
|
|
req.Header.Add("Authorization", sdk.basicAuth())
|
2021-10-09 18:09:54 +02:00
|
|
|
rawResp, _ := sdk.client.Do(req)
|
|
|
|
if rawResp.Body != nil {
|
|
|
|
defer rawResp.Body.Close()
|
|
|
|
}
|
2021-07-12 16:58:48 +02:00
|
|
|
|
2021-10-09 18:09:54 +02:00
|
|
|
body, _ := io.ReadAll(rawResp.Body)
|
|
|
|
response := &MeasuresResponse{}
|
|
|
|
err = json.Unmarshal(body, &response)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot parse response from SonarQube: %w", err)
|
|
|
|
}
|
2021-07-12 16:58:48 +02:00
|
|
|
|
2021-10-09 18:09:54 +02:00
|
|
|
return response, nil
|
2021-07-12 16:58:48 +02:00
|
|
|
}
|
|
|
|
|
2021-10-10 19:39:33 +02:00
|
|
|
func (sdk *SonarQubeSdk) ComposeGiteaComment(data *CommentComposeData) (string, error) {
|
|
|
|
m, err := sdk.GetMeasures(data.Key, data.PRName)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error composing Gitea comment: %s", err.Error())
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
message := make([]string, 5)
|
|
|
|
message[0] = GetRenderedQualityGate(data.QualityGate)
|
|
|
|
message[1] = m.GetRenderedMarkdownTable()
|
2021-10-12 14:09:24 +02:00
|
|
|
message[2] = fmt.Sprintf(`See <a href="%s" target="_blank" rel="nofollow">SonarQube</a> for details.`, data.Url)
|
2021-10-10 19:39:33 +02:00
|
|
|
message[3] = "---"
|
|
|
|
message[4] = fmt.Sprintf("- If you want the bot to check again, post `%s`", actions.ActionReview)
|
|
|
|
|
|
|
|
return strings.Join(message, "\n\n"), nil
|
|
|
|
}
|
|
|
|
|
2021-07-12 16:58:48 +02:00
|
|
|
func (sdk *SonarQubeSdk) basicAuth() string {
|
|
|
|
auth := []byte(fmt.Sprintf("%s:", sdk.token))
|
|
|
|
return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString(auth))
|
|
|
|
}
|
|
|
|
|
|
|
|
func New() *SonarQubeSdk {
|
|
|
|
return &SonarQubeSdk{
|
2021-10-03 17:57:22 +02:00
|
|
|
client: &http.Client{},
|
2021-07-12 16:58:48 +02:00
|
|
|
baseUrl: settings.SonarQube.Url,
|
2021-10-03 17:57:22 +02:00
|
|
|
token: settings.SonarQube.Token.Value,
|
2021-07-12 16:58:48 +02:00
|
|
|
}
|
|
|
|
}
|