diff --git a/README.md b/README.md index daedcc4db..88ea7821b 100644 --- a/README.md +++ b/README.md @@ -3097,6 +3097,29 @@ evidenceDetails := evidenceService.EvidenceDetails{ } body, err = evideceManager.UploadEvidence(evidenceDetails) ``` + +#### Fetch Sonar Task Report + +```go +// Fetches the status of a SonarQube task by its task ID. +taskID := "your-task-id" +// sonarQubeURL is the URL of the SonarQube server. +sonarQubeURL := "http://localhost:9000" +// proxy is optional and can be used to specify a proxy server. +proxy := "" +body, err := evidenceManager.FetchSonarTaskStatus(taskID, sonarQubeURL, proxy) +``` +#### Fetch Sonar Analysis Report + +```go +// Retrieves the analysis report from SonarQube by analysis ID. +analysisID := "your-analysis-id" +// sonarQubeURL is the URL of the SonarQube server. +sonarQubeURL := "http://localhost:9000" +// proxy is optional and can be used to specify a proxy server. +proxy := "" +body, err := evidenceManager.GetSonarAnalysisReport(analysisID, sonarQubeURL, proxy) +``` ## Metadata APIs ### Creating Metadata Service Manager diff --git a/evidence/external/sonarqube/sonarqube.go b/evidence/external/sonarqube/sonarqube.go new file mode 100644 index 000000000..9000b906f --- /dev/null +++ b/evidence/external/sonarqube/sonarqube.go @@ -0,0 +1,145 @@ +package sonarqube + +import ( + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/log" + "io" + "net/http" + "net/url" + "os" + "time" +) + +const SonarAccessTokenKey = "JF_SONARQUBE_ACCESS_TOKEN" + +type SonarQube struct { + Proxy string + ServiceConfig +} + +type ServiceConfig struct { + url string + taskAPIPath string + projectStatusAPIPath string +} + +func NewSonarQubeEvidence(sonarQubeURL, proxy string) *SonarQube { + return &SonarQube{ + Proxy: proxy, + ServiceConfig: ServiceConfig{ + url: sonarQubeURL, + taskAPIPath: "/api/ce/task", + projectStatusAPIPath: "/api/qualitygates/project_status", + }, + } +} + +func (sqe *SonarQube) createQueryParam(params map[string]string, key, value string) map[string]string { + if params != nil { + params[key] = value + return params + } + return map[string]string{ + key: value, + } +} + +func createHttpClient(proxy string) *http.Client { + transport := &http.Transport{ + MaxIdleConns: 10, + IdleConnTimeout: 30 * time.Second, + DisableKeepAlives: false, + Proxy: http.ProxyFromEnvironment, + } + if proxy != "" { + proxyURL, err := url.Parse(proxy) + if err != nil { + + // Fallback to environment proxy or no proxy + log.Error("Failed to parse proxy URL: " + err.Error()) + } else { + transport.Proxy = http.ProxyURL(proxyURL) + } + } + return &http.Client{ + Timeout: 30 * time.Second, + Transport: transport, + } +} + +func (sqe *SonarQube) GetSonarAnalysis(analysisID string) ([]byte, error) { + log.Debug("Fetching quality gate analysis for analysisID" + analysisID) + queryParams := sqe.createQueryParam(nil, "analysisId", analysisID) + sonarServerURL := sqe.ServiceConfig.url + sqe.ServiceConfig.projectStatusAPIPath + + req, err := http.NewRequest("GET", sonarServerURL, nil) + if err != nil { + return nil, err + } + + q := req.URL.Query() + for key, value := range queryParams { + q.Add(key, value) + } + req.URL.RawQuery = q.Encode() + resp, bytes, err := sqe.sendRequestUsingSonarQubeToken(req, sqe.Proxy) + if err != nil { + return bytes, err + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + log.Error("Failed to close response body" + err.Error()) + } + }(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return body, nil +} + +func (sqe *SonarQube) CollectSonarQubePredicate(taskID string) ([]byte, error) { + queryParams := sqe.createQueryParam(nil, "id", taskID) + sonarServerURL := sqe.ServiceConfig.url + sqe.ServiceConfig.taskAPIPath + req, err := http.NewRequest(http.MethodGet, sonarServerURL, nil) + if err != nil { + return nil, err + } + q := req.URL.Query() + for key, value := range queryParams { + q.Add(key, value) + } + req.URL.RawQuery = q.Encode() + resp, bytes, err := sqe.sendRequestUsingSonarQubeToken(req, sqe.Proxy) + if err != nil { + return bytes, err + } + defer func() { + if cerr := resp.Body.Close(); cerr != nil { + log.Error("Failed to close response body" + cerr.Error()) + } + }() + if resp.StatusCode != http.StatusOK { + return nil, errorutils.CheckErrorf("Failed to get SonarQube task report. Status code: %d", resp.StatusCode) + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return body, nil +} + +func (sqe *SonarQube) sendRequestUsingSonarQubeToken(req *http.Request, proxy string) (*http.Response, []byte, error) { + sonarQubeToken := os.Getenv(SonarAccessTokenKey) + if sonarQubeToken == "" { + return nil, nil, errorutils.CheckErrorf("Sonar access token not found in environment variable " + SonarAccessTokenKey) + } + req.Header.Set("Authorization", "Bearer "+sonarQubeToken) + httpClient := createHttpClient(proxy) + resp, err := httpClient.Do(req) + if err != nil { + return nil, nil, err + } + return resp, nil, nil +} diff --git a/evidence/manager.go b/evidence/manager.go index e4a8ce45c..0b59d2de9 100644 --- a/evidence/manager.go +++ b/evidence/manager.go @@ -2,6 +2,7 @@ package evidence import ( "github.com/jfrog/jfrog-client-go/config" + "github.com/jfrog/jfrog-client-go/evidence/external/sonarqube" "github.com/jfrog/jfrog-client-go/evidence/services" "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" ) @@ -39,3 +40,13 @@ func (esm *EvidenceServicesManager) UploadEvidence(evidenceDetails services.Evid evidenceService := services.NewEvidenceService(esm.config.GetServiceDetails(), esm.client) return evidenceService.UploadEvidence(evidenceDetails) } + +func (esm *EvidenceServicesManager) FetchSonarTaskStatus(taskID, sonarQubeURL, proxy string) ([]byte, error) { + sonarTaskStatus := sonarqube.NewSonarQubeEvidence(sonarQubeURL, proxy) + return sonarTaskStatus.CollectSonarQubePredicate(taskID) +} + +func (esm *EvidenceServicesManager) GetSonarAnalysisReport(analysisID, sonarQubeURL, proxy string) ([]byte, error) { + sonarAnalysisReport := sonarqube.NewSonarQubeEvidence(sonarQubeURL, proxy) + return sonarAnalysisReport.GetSonarAnalysis(analysisID) +}