242 lines
6.1 KiB
Go
242 lines
6.1 KiB
Go
// Copyright 2022 Woodpecker Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package sonar
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"reflect"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
DashboardURL = "/dashboard?id="
|
|
)
|
|
|
|
var netClient *http.Client
|
|
|
|
type (
|
|
Config struct {
|
|
Key string
|
|
Name string
|
|
HostURL string
|
|
Token string
|
|
|
|
Version string
|
|
Sources string
|
|
HttpTimeout int
|
|
Inclusions string
|
|
Exclusions string
|
|
SonarLogLevel string
|
|
ShowProfiling string
|
|
BranchAnalysis string
|
|
UsingProperties bool
|
|
|
|
Binaries string
|
|
Quality string
|
|
QualityEnabled bool
|
|
QualityTimeout int
|
|
ArtifactFile string
|
|
|
|
Logger *zap.SugaredLogger
|
|
}
|
|
|
|
Authentication struct {
|
|
Token string
|
|
}
|
|
|
|
Report struct {
|
|
ProjectKey string `toml:"projectKey"`
|
|
ServerURL string `toml:"serverUrl"`
|
|
DashboardURL string `toml:"dashboardUrl"`
|
|
CeTaskID string `toml:"ceTaskId"`
|
|
CeTaskURL string `toml:"ceTaskUrl"`
|
|
}
|
|
|
|
TaskResponse struct {
|
|
Task struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
ComponentID string `json:"componentId"`
|
|
ComponentKey string `json:"componentKey"`
|
|
ComponentName string `json:"componentName"`
|
|
AnalysisID string `json:"analysisId"`
|
|
Status string `json:"status"`
|
|
} `json:"task"`
|
|
}
|
|
|
|
ProjectStatusResponse struct {
|
|
ProjectStatus struct {
|
|
Status string `json:"status"`
|
|
} `json:"projectStatus"`
|
|
}
|
|
|
|
Project struct {
|
|
ProjectStatus Status `json:"projectStatus"`
|
|
}
|
|
|
|
Status struct {
|
|
Status string `json:"status"`
|
|
IgnoredConditions bool `json:"ignoredConditions"`
|
|
Conditions []Condition `json:"conditions"`
|
|
}
|
|
|
|
Condition 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"`
|
|
}
|
|
|
|
Testsuites struct {
|
|
XMLName xml.Name `xml:"testsuites"`
|
|
Text string `xml:",chardata"`
|
|
TestSuite []Testsuite `xml:"testsuite"`
|
|
}
|
|
|
|
Testsuite struct {
|
|
Text string `xml:",chardata"`
|
|
Package string `xml:"package,attr"`
|
|
Time int `xml:"time,attr"`
|
|
Tests int `xml:"tests,attr"`
|
|
Errors int `xml:"errors,attr"`
|
|
Name string `xml:"name,attr"`
|
|
TestCase []Testcase `xml:"testcase"`
|
|
}
|
|
|
|
Testcase struct {
|
|
Text string `xml:",chardata"`
|
|
Time int `xml:"time,attr"` // Actual Value Sonar
|
|
Name string `xml:"name,attr"` // Metric Key
|
|
Classname string `xml:"classname,attr"` // The metric Rule
|
|
Failure *Failure `xml:"failure"` // Sonar Failure - show results
|
|
}
|
|
|
|
Failure struct {
|
|
Text string `xml:",chardata"`
|
|
Message string `xml:"message,attr"`
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
netClient = &http.Client{
|
|
Timeout: time.Second * 10,
|
|
Transport: &http.Transport{
|
|
Dial: (&net.Dialer{
|
|
Timeout: 5 * time.Second,
|
|
}).Dial,
|
|
TLSHandshakeTimeout: 5 * time.Second,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (c *Config) GetProjectDashUrl() string {
|
|
return c.HostURL + DashboardURL + c.Name
|
|
}
|
|
|
|
func (a *Authentication) UrlToken() string {
|
|
return base64.StdEncoding.EncodeToString([]byte((a.Token + ":")))
|
|
}
|
|
|
|
func (a *Authentication) AddAuthHeader(r *http.Request) {
|
|
r.Header.Add("Authorization", "Basic "+a.UrlToken())
|
|
}
|
|
|
|
func GetSonarJobStatus(report *Report, auth *Authentication, config *Config) *TaskResponse {
|
|
task := &TaskResponse{}
|
|
sonarJsonRequest("GET", report.CeTaskURL, nil, auth, config, task)
|
|
|
|
return task
|
|
}
|
|
|
|
func WaitForSonarJob(report *Report, auth *Authentication, config *Config) *TaskResponse {
|
|
timeout := time.NewTimer(300 * time.Second)
|
|
tick := time.NewTicker(500 * time.Millisecond)
|
|
for {
|
|
select {
|
|
case <-timeout.C:
|
|
tick.Stop()
|
|
timeout.Stop()
|
|
config.Logger.Fatalw("WaitForSonarJob timed out")
|
|
case <-tick.C:
|
|
job := GetSonarJobStatus(report, auth, config)
|
|
|
|
if job.Task.Status == "SUCCESS" {
|
|
timeout.Stop()
|
|
tick.Stop()
|
|
return job
|
|
}
|
|
if job.Task.Status == "ERROR" {
|
|
timeout.Stop()
|
|
tick.Stop()
|
|
config.Logger.Fatalw("WaitForSonarJob task failed with status: ERROR")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func GetStatus(task *TaskResponse, report *Report, auth *Authentication, config *Config) (status string) {
|
|
reportRequest := url.Values{
|
|
"analysisId": {task.Task.AnalysisID},
|
|
}
|
|
project := &ProjectStatusResponse{}
|
|
|
|
url := report.ServerURL + "/api/qualitygates/project_status?" + reportRequest.Encode()
|
|
sonarJsonRequest("GET", url, nil, auth, config, project)
|
|
|
|
return project.ProjectStatus.Status
|
|
}
|
|
|
|
func sonarJsonRequest(reqType string, url string, body io.Reader, auth *Authentication, config *Config, targetPointer any) {
|
|
|
|
rv := reflect.ValueOf(targetPointer)
|
|
if rv.Kind() != reflect.Pointer || rv.IsNil() {
|
|
config.Logger.Fatalw("invalid parameter, not a pointer", "type", reflect.TypeOf(targetPointer))
|
|
}
|
|
|
|
req, err := http.NewRequest(reqType, url, body)
|
|
if err != nil {
|
|
config.Logger.Fatalw("failed to build request for url",
|
|
"httpRequestType", reqType,
|
|
"url", url,
|
|
"targetType", reflect.TypeOf(targetPointer).String(),
|
|
"error", err)
|
|
}
|
|
|
|
auth.AddAuthHeader(req)
|
|
resp, err := netClient.Do(req)
|
|
if err != nil {
|
|
config.Logger.Fatalw("failed getting data from server",
|
|
"targetType", reflect.TypeOf(targetPointer).String(),
|
|
"error", err)
|
|
}
|
|
|
|
buf, _ := io.ReadAll(resp.Body)
|
|
if err := json.Unmarshal(buf, targetPointer); err != nil {
|
|
config.Logger.Fatalw("failed to unmarshal response",
|
|
"targetType", reflect.TypeOf(targetPointer).String(),
|
|
"error", err)
|
|
}
|
|
}
|