You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

229 lines
5.8 KiB

package main
import (
"encoding/json"
"errors"
"fmt"
"github.com/sirupsen/logrus"
"io/ioutil"
"net/http"
"os/exec"
"regexp"
"strings"
"time"
)
type (
Config struct {
Key string
Name string
Host string
Token string
Version string
Branch string
Sources string
Timeout string
Inclusions string
Exclusions string
Level string
showProfiling string
branchAnalysis bool
Quality string
}
Plugin struct {
Config Config
}
)
type Job struct {
Task struct {
ID string `json:"id"`
Type string `json:"type"`
ComponentID string `json:"componentId"`
ComponentKey string `json:"componentKey"`
ComponentName string `json:"componentName"`
ComponentQualifier string `json:"componentQualifier"`
AnalysisID string `json:"analysisId"`
Status string `json:"status"`
SubmittedAt string `json:"submittedAt"`
SubmitterLogin string `json:"submitterLogin"`
StartedAt string `json:"startedAt"`
ExecutedAt string `json:"executedAt"`
ExecutionTimeMs int `json:"executionTimeMs"`
Logs bool `json:"logs"`
HasScannerContext bool `json:"hasScannerContext"`
Organization string `json:"organization"`
WarningCount int `json:"warningCount"`
Warnings []interface{} `json:"warnings"`
} `json:"task"`
}
func (p Plugin) Exec() error {
args := []string{
"-Dproject.settings=sonar-project.properties",
"-Dsonar.host.url=" + p.Config.Host,
"-Dsonar.login=" + p.Config.Token,
"-Dsonar.projectKey=" + p.Config.Key,
"-Dsonar.projectName=" + p.Config.Name,
}
if p.Config.branchAnalysis {
args = append(args, "-Dsonar.branch.name="+p.Config.Branch)
}
jobUrl := staticScan(args)
logrus.WithFields(logrus.Fields{
"job url": jobUrl,
}).Info("Job url")
job, err := waitForSonarJob(jobUrl, p.Config.Token)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Fatal("Unable to get Job state")
}
if job == nil {
return nil
}
qualityGatesResponse := getQualityGatesProjectStatusResponse(p.Config.Host, p.Config.Token, job.Task.AnalysisID)
projectStatus := checkProjectStatus(qualityGatesResponse)
if projectStatus != p.Config.Quality || projectStatus != "OK" {
logrus.WithFields(logrus.Fields{
"status": projectStatus,
}).Fatal("QualityGateData status failed")
}
return nil
}
func staticScan(args []string) string {
cmd := exec.Command("sonar-scanner", args...)
output, err := cmd.CombinedOutput()
if len(output) > 0 {
fmt.Printf("==> Code Analysis Result: %s\n", string(output))
}
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Fatal("Run command failed")
}
jobUrl := parseOutput(string(output))
return jobUrl
}
func getQualityGatesProjectStatusResponse(sonarUrl, token, analysisId string) []byte {
sonarUrl += "/api/qualitygates/project_status"
payload := strings.NewReader("analysisId=" + analysisId)
req, _ := http.NewRequest("POST", sonarUrl, payload)
req.SetBasicAuth(token, "")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Cache-Control", "no-cache")
res, err := http.DefaultClient.Do(req)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Fatal("Failed get status")
}
body, _ := ioutil.ReadAll(res.Body)
return body
}
func checkProjectStatus(b []byte) string {
type QualityGateData struct {
ProjectStatus struct {
Status string `json:"status"`
Conditions []struct {
Status string `json:"status"`
MetricKey string `json:"metricKey"`
Comparator string `json:"comparator"`
PeriodIndex int `json:"periodIndex"`
ErrorThreshold string `json:"errorThreshold"`
ActualValue string `json:"actualValue"`
} `json:"conditions"`
Periods []struct {
Index int `json:"index"`
Mode string `json:"mode"`
Date string `json:"date"`
Parameter string `json:"parameter"`
} `json:"periods"`
IgnoredConditions bool `json:"ignoredConditions"`
} `json:"projectStatus"`
}
data := QualityGateData{}
if err := json.Unmarshal(b, &data); err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Fatal("Failed")
}
logrus.WithFields(logrus.Fields{
"data": data,
}).Info("QualityGateData json data")
return data.ProjectStatus.Status
}
func parseOutput(outs string) string {
var jobId = regexp.MustCompile(`https?://.*/api/ce/task\?id=.*`)
result := jobId.FindStringSubmatch(outs)
return result[0]
}
func getSonarJob(b []byte) Job {
job := Job{}
if err := json.Unmarshal(b, &job); err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Fatal("Sonar Job unmarshal failed")
}
logrus.WithFields(logrus.Fields{
"data": job.Task.Status,
}).Info("Sonar job status")
return job
}
func getSonarJobResponse(jobUrl, token string) []byte {
req, _ := http.NewRequest("GET", jobUrl, nil)
req.SetBasicAuth(token, "")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Cache-Control", "no-cache")
res, err := http.DefaultClient.Do(req)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Fatal("Failed get sonar job status")
}
body, _ := ioutil.ReadAll(res.Body)
return body
}
func waitForSonarJob(jobUrl, token string) (*Job, error) {
timeout := time.After(60 * time.Second)
tick := time.Tick(2500 * time.Millisecond)
for {
select {
case <-timeout:
return nil, errors.New("timed out")
case <-tick:
sonarJobResponse := getSonarJobResponse(jobUrl, token)
job := getSonarJob(sonarJobResponse)
if job.Task.Status == "SUCCESS" {
return &job, nil
}
}
}
}