From eeeddba634d9c90b0136dc854d9a999ef454b890 Mon Sep 17 00:00:00 2001 From: carmithersh Date: Wed, 19 Mar 2025 15:09:58 +0200 Subject: [PATCH 1/3] Support for sonar selfhosted evidence --- examples/sonar-scan-example/README.md | 2 + examples/sonar-scan-example/main.go | 177 +++---------------- examples/sonar-scan-example/sonar-helper.go | 181 ++++++++++++++++++++ sonar-project.properties | 6 +- 4 files changed, 209 insertions(+), 157 deletions(-) create mode 100644 examples/sonar-scan-example/sonar-helper.go diff --git a/examples/sonar-scan-example/README.md b/examples/sonar-scan-example/README.md index d2770de..2195412 100644 --- a/examples/sonar-scan-example/README.md +++ b/examples/sonar-scan-example/README.md @@ -10,6 +10,8 @@ should be checked using a policy. ## Environment variables - `SONAR_TOKEN` - The sonar server token. +- `SONAR_TYPE` - Should be Either SAAS or SELFHOSTED. +- `SONAR_HOST` - The sonar server host name, for example sonar.myconpany.org. required for SELFHOSTED type, if not provided for SAAS type sonarcloud.io is used as default. ## Arguments `--reportTaskFile=` - The path to the sonar report task file. diff --git a/examples/sonar-scan-example/main.go b/examples/sonar-scan-example/main.go index cc26d14..82ed003 100644 --- a/examples/sonar-scan-example/main.go +++ b/examples/sonar-scan-example/main.go @@ -3,12 +3,8 @@ import ( "context" "encoding/json" "os" - "fmt" - "io" "log" - "net/http" "time" - "bufio" "strings" "strconv" ) @@ -71,8 +67,7 @@ type SonarAnalysis struct { const ( DEFAULT_HTTP_TIMEOUT = 10 * time.Second - ANALYSIS_URL = "https://sonarcloud.io/api/qualitygates/project_status?analysisId=$analysisId" - LOG_FILE_LOCATION = "sonar-scan.log" + LOG_FILE_LOCATION = "sonar-scan.log" ) func main() { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) @@ -90,11 +85,17 @@ func main() { logger.Println("Sonar token not found, set SONAR_TOKEN variable") os.Exit(1) } -///home/runner/work/Evidence-Examples/Evidence-Examples/.scannerwork/report-task.txt + sonar_type := os.Getenv("SONAR_TYPE") + if sonar_type != "SELFHOSTED" && sonar_type != "SAAS" { + logger.Println("Wrong Sonar type, set SONAR_TYPE variable to either SAAS or SELFHOSTED") + os.Exit(1) + } + + //home/runner/work/Evidence-Examples/Evidence-Examples/.scannerwork/report-task.txt //get the sonar report file location or details to .scannerwork/.report-task.txt reportTaskFile := ".scannerwork/.report-task.txt" failOnAnalysisFailure := false - maxRetries := 0 + maxRetries := 1 waitTime := 5 if len(os.Args) > 0 { // loop over all args @@ -127,95 +128,30 @@ func main() { logger.Println("maxRetries:", maxRetries) logger.Println("WaitTime:", waitTime) } - // fmt.Println("reportTaskFile: ", reportTaskFile) - // Open the reportTaskFile - file, err := os.Open(reportTaskFile) - if err != nil { - logger.Println("Error opening file:", reportTaskFile, "error:", err) - os.Exit(1) - } - defer file.Close() - ceTaskUrl:="" + response := SonarResponse{} + defaultSonarHost := "sonarcloud.io" + sonarHost := os.Getenv("SONAR_HOST") - // Read the file line by line - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Text() - // Skip empty lines and comments - if len(line) == 0 || strings.HasPrefix(line, "#") { - continue - } - // Split the line into key and value - parts := strings.SplitN(line, "=", 2) - if len(parts) == 2 { - key := strings.TrimSpace(parts[0]) - value := strings.TrimSpace(parts[1]) - if key == "ceTaskUrl" { - ceTaskUrl = value - break - } - } - } - if err := scanner.Err(); err != nil { - logger.Println("Error reading file:", reportTaskFile , "error", err) - os.Exit(1) - } - if ceTaskUrl == "" { - fmt.Printf("ceTaskUrl Key not found") - os.Exit(1) - } - // Add a reusable HTTP client - var client = &http.Client{ - Timeout: DEFAULT_HTTP_TIMEOUT, - Transport: &http.Transport{ - MaxIdleConns: 100, - IdleConnTimeout: 10 * time.Second, - DisableCompression: true, - }, - } - logger.Println("ceTaskUrl", ceTaskUrl) - // get the report task - retries := 0 + if sonar_type == "SAAS" { + if sonarHost == "" { + sonarHost = defaultSonarHost + } + logger.Println("Running sonar analysis extraction for SAAS, host:", sonarHost) - var taskResponse SonarTaskResponse - for retries < maxRetries { - taskResponse, err = getReport(ctx, client, logger, ceTaskUrl, sonar_token ) - if err != nil { - logger.Println("Error getting sonar report task", err) + }else if sonar_type == "SELFHOSTED" { + if sonarHost == "" { + logger.Println("Sonar host not found, set SONAR_HOST variable") os.Exit(1) } - if taskResponse.Task.Status == "SUCCESS" { - logger.Println("Sonar analysis task completed successfully after ", retries, " retries") - break - } - if taskResponse.Task.Status == "PENDING" || taskResponse.Task.Status == "IN_PROGRESS" { - logger.Println("Sonar analysis task is still in progress, waiting for ", waitTime, " seconds before retrying") - time.Sleep(time.Duration(waitTime) * time.Second) - retries++ - } - } - if (taskResponse.Task.Status != "SUCCESS") { - logger.Println("Sonar analysis task after ", maxRetries, " retries is ", taskResponse.Task.Status, "exiting") - os.Exit(1) + logger.Println("Running sonar analysis extraction for " , sonar_type, " server", sonarHost) } + response, err = runReport(ctx, logger, sonarHost, sonar_token, reportTaskFile, failOnAnalysisFailure, maxRetries, waitTime) - logger.Println("taskResponse.Task.AnalysisId", taskResponse.Task.AnalysisId) - // get the analysis content - analysis , err := getAnalysis(ctx, client, logger, sonar_token, taskResponse.Task.AnalysisId) if err != nil { - logger.Println("Error getting sonar analysis report: ", err) - os.Exit(1) - } - if analysis.ProjectStatus.Status != "OK" && failOnAnalysisFailure { - logger.Println("Sonar analysis failed, exiting according to failOnAnalysisFailure argument") + logger.Println("Error in generating report predicate:", err) os.Exit(1) } - response := SonarResponse{ - Task: taskResponse.Task, - Analysis: analysis.ProjectStatus, - } - // marshal the response to JSON jsonBytes, err := json.Marshal(response) if err != nil { @@ -225,70 +161,3 @@ func main() { // return response to caller through stdout os.Stdout.Write(jsonBytes) } - - -func getReport(ctx context.Context , client *http.Client, logger *log.Logger, ceTaskUrl string, sonar_token string) (SonarTaskResponse, error) { - // Make the HTTP GET request - logger.Println("getReport ceTaskUrl:",ceTaskUrl) - req, err := http.NewRequestWithContext(ctx, "GET", ceTaskUrl, nil) - req.Header.Set("Authorization", "Bearer " + sonar_token) - resp, err := client.Do(req) - if err != nil { - return SonarTaskResponse{}, fmt.Errorf("Error making the request, url:",ceTaskUrl, "error", err) - } - - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - if err != nil { - logger.Println("getReport error getting response body error:", err) - return SonarTaskResponse{}, fmt.Errorf("getReport error getting response body error, url:",ceTaskUrl, "error", err) - } - - if resp.StatusCode != http.StatusOK { - return SonarTaskResponse{}, fmt.Errorf("getReport error getting response from", ceTaskUrl, " returned ", resp.StatusCode, " response body ", body) - } - - logger.Println("getReport resp.StatusCode:", resp.StatusCode) - - var taskResponse SonarTaskResponse - err = json.Unmarshal(body, &taskResponse) - if err != nil { - logger.Println("getReport error Unmarshal response body ", string(body)) - return SonarTaskResponse{}, fmt.Errorf("error unmarshal report response for report", ceTaskUrl, "error", err) - } - logger.Println("getReport taskResponse:", taskResponse) - return taskResponse, nil -} - -func getAnalysis(ctx context.Context, client *http.Client, logger *log.Logger, sonar_token string, analysisId string) (SonarAnalysis, error) { - - analysisUrl := strings.Replace(ANALYSIS_URL, "$analysisId", analysisId, 1) - logger.Println("analysisId", analysisId) - //logger.Println("analysisUrl", analysisUrl) - // Make the HTTP GET request - req, err := http.NewRequestWithContext(ctx, "GET", analysisUrl , nil) - req.Header.Set("Authorization", "Bearer " + sonar_token) - resp, err := client.Do(req) - if err != nil { - return SonarAnalysis{}, fmt.Errorf("getAnalysis, Error making the request, url:",analysisUrl, "error", err) - } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - if err != nil { - logger.Println("getAnalysis error getting response body error:", err) - return SonarAnalysis{}, fmt.Errorf("getAnalysis error getting response body error, url:",analysisUrl, "error", err) - } - - if resp.StatusCode != http.StatusOK { - return SonarAnalysis{}, fmt.Errorf("getAnalysis, error getting response from", analysisUrl, " returned ", resp.StatusCode, " response body ", body) - } - - var analysisResponse SonarAnalysis - err = json.Unmarshal(body, &analysisResponse) - if err != nil { - log.Println("getAnalysis, get temp credentials response body ", string(body)) - return SonarAnalysis{}, fmt.Errorf("getAnalysis, error unmarshal analysis response", analysisUrl, "error", err) - } - - return analysisResponse, nil -} \ No newline at end of file diff --git a/examples/sonar-scan-example/sonar-helper.go b/examples/sonar-scan-example/sonar-helper.go new file mode 100644 index 0000000..ca4a2f1 --- /dev/null +++ b/examples/sonar-scan-example/sonar-helper.go @@ -0,0 +1,181 @@ +package main +import ( + "context" + "encoding/json" + "os" + "fmt" + "io" + "log" + "net/http" + "time" + "bufio" + "strings" + ) + +const ( + SELFHOSTED_ANALYSIS_URL = "https://$sonarhost/api/qualitygates/project_status?analysisId=$analysisId" + +) +func runReport(ctx context.Context, logger *log.Logger, sonarHost string , sonar_token string, reportTaskFile string, failOnAnalysisFailure bool, maxRetries int, waitTime int) (SonarResponse, error) { + logger.Println("Running sonar analysis extraction") + + // fmt.Println("reportTaskFile: ", reportTaskFile) + // Open the reportTaskFile + file, err := os.Open(reportTaskFile) + if err != nil { + logger.Println("Error opening file:", reportTaskFile, "error:", err) + return SonarResponse{}, fmt.Errorf("Error opening file:", reportTaskFile, "error:", err) + } + defer file.Close() + ceTaskUrl:="" + + // Read the file line by line + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + // Skip empty lines and comments + if len(line) == 0 || strings.HasPrefix(line, "#") { + continue + } + // Split the line into key and value + parts := strings.SplitN(line, "=", 2) + if len(parts) == 2 { + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + if key == "ceTaskUrl" { + ceTaskUrl = value + break + } + } + } + if err := scanner.Err(); err != nil { + logger.Println("Error reading file:", reportTaskFile , "error", err) + return SonarResponse{}, fmt.Errorf("Error reading file:", reportTaskFile , "error", err) + } + if ceTaskUrl == "" { + fmt.Printf("ceTaskUrl Key not found") + return SonarResponse{}, fmt.Errorf("ceTaskUrl Key not found") + } + // Add a reusable HTTP client + var client = &http.Client{ + Timeout: DEFAULT_HTTP_TIMEOUT, + Transport: &http.Transport{ + MaxIdleConns: 100, + IdleConnTimeout: 10 * time.Second, + DisableCompression: true, + }, + } + logger.Println("ceTaskUrl", ceTaskUrl) + // get the report task + retries := 0 + + var taskResponse SonarTaskResponse + for retries < maxRetries { + taskResponse, err = getReport(ctx, client, logger, ceTaskUrl, sonar_token ) + if err != nil { + logger.Println("Error getting sonar report task", err) + return SonarResponse{}, fmt.Errorf("Error getting sonar report task", err) + } + if taskResponse.Task.Status == "SUCCESS" { + logger.Println("Sonar analysis task completed successfully after ", retries, " retries") + break + } + if taskResponse.Task.Status == "PENDING" || taskResponse.Task.Status == "IN_PROGRESS" { + logger.Println("Sonar analysis task is still in progress, waiting for ", waitTime, " seconds before retrying") + time.Sleep(time.Duration(waitTime) * time.Second) + retries++ + } + } + if (taskResponse.Task.Status != "SUCCESS") { + logger.Println("Sonar analysis task after ", maxRetries, " retries is ", taskResponse.Task.Status, "exiting") + return SonarResponse{}, fmt.Errorf("Sonar analysis task after ", maxRetries, " retries is ", taskResponse.Task.Status, "exiting") + } + + logger.Println("taskResponse.Task.AnalysisId", taskResponse.Task.AnalysisId) + // get the analysis content + analysis , err := getAnalysis(ctx, client, logger, sonarHost, sonar_token, taskResponse.Task.AnalysisId) + if err != nil { + logger.Println("Error getting sonar analysis report: ", err) + return SonarResponse{}, fmt.Errorf("Error getting sonar analysis report: ", err) + } + if analysis.ProjectStatus.Status != "OK" && failOnAnalysisFailure { + logger.Println("Sonar analysis failed, exiting according to failOnAnalysisFailure argument") + return SonarResponse{}, fmt.Errorf("Sonar analysis failed, exiting according to failOnAnalysisFailure argument") + } + + response := SonarResponse{ + Task: taskResponse.Task, + Analysis: analysis.ProjectStatus, + } + + return response, nil + } + + +func getReport(ctx context.Context , client *http.Client, logger *log.Logger, ceTaskUrl string, sonar_token string) (SonarTaskResponse, error) { + // Make the HTTP GET request + logger.Println("getReport ceTaskUrl:",ceTaskUrl) + req, err := http.NewRequestWithContext(ctx, "GET", ceTaskUrl, nil) + req.Header.Set("Authorization", "Bearer " + sonar_token) + resp, err := client.Do(req) + if err != nil { + return SonarTaskResponse{}, fmt.Errorf("Error making the request, url:",ceTaskUrl, "error", err) + } + + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + logger.Println("getReport error getting response body error:", err) + return SonarTaskResponse{}, fmt.Errorf("getReport error getting response body error, url:",ceTaskUrl, "error", err) + } + + if resp.StatusCode != http.StatusOK { + return SonarTaskResponse{}, fmt.Errorf("getReport error getting response from", ceTaskUrl, " returned ", resp.StatusCode, " response body ", body) + } + + logger.Println("getReport resp.StatusCode:", resp.StatusCode) + + var taskResponse SonarTaskResponse + err = json.Unmarshal(body, &taskResponse) + if err != nil { + logger.Println("getReport error Unmarshal response body ", string(body)) + return SonarTaskResponse{}, fmt.Errorf("error unmarshal report response for report", ceTaskUrl, "error", err) + } + logger.Println("getReport taskResponse:", taskResponse) + return taskResponse, nil +} + +func getAnalysis(ctx context.Context, client *http.Client, logger *log.Logger, sonarHost string, sonar_token string, analysisId string) (SonarAnalysis, error) { + + analysisUrl := strings.Replace(SELFHOSTED_ANALYSIS_URL, "$analysisId", analysisId, 1) + analysisUrl = strings.Replace(analysisUrl, "$sonarhost", sonarHost, 1) + logger.Println("analysisId", analysisId) + logger.Println("sonarhost", sonarHost) + //logger.Println("analysisUrl", analysisUrl) + // Make the HTTP GET request + req, err := http.NewRequestWithContext(ctx, "GET", analysisUrl , nil) + req.Header.Set("Authorization", "Bearer " + sonar_token) + resp, err := client.Do(req) + if err != nil { + return SonarAnalysis{}, fmt.Errorf("getAnalysis, Error making the request, url:",analysisUrl, "error", err) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + logger.Println("getAnalysis error getting response body error:", err) + return SonarAnalysis{}, fmt.Errorf("getAnalysis error getting response body error, url:",analysisUrl, "error", err) + } + + if resp.StatusCode != http.StatusOK { + return SonarAnalysis{}, fmt.Errorf("getAnalysis, error getting response from", analysisUrl, " returned ", resp.StatusCode, " response body ", body) + } + + var analysisResponse SonarAnalysis + err = json.Unmarshal(body, &analysisResponse) + if err != nil { + log.Println("getAnalysis, get temp credentials response body ", string(body)) + return SonarAnalysis{}, fmt.Errorf("getAnalysis, error unmarshal analysis response", analysisUrl, "error", err) + } + + return analysisResponse, nil +} \ No newline at end of file diff --git a/sonar-project.properties b/sonar-project.properties index 16b4962..d78ff30 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,9 +1,9 @@ # Required metadata -sonar.projectKey=test-evidence +sonar.projectKey=evidence_test sonar.organization=my-evidence-test-org - +# sonar.host.url= # Project details -sonar.projectName=test-evidence-project +sonar.projectName=evidence_test sonar.projectVersion=1.0 # Path to source directories From 2c89285b9e677fa42f7decfc7a13daebd042438f Mon Sep 17 00:00:00 2001 From: carmithersh Date: Wed, 19 Mar 2025 15:15:55 +0200 Subject: [PATCH 2/3] Support for sonar selfhosted evidence, defaulting to SAAS --- examples/sonar-scan-example/main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/sonar-scan-example/main.go b/examples/sonar-scan-example/main.go index 82ed003..10cfc74 100644 --- a/examples/sonar-scan-example/main.go +++ b/examples/sonar-scan-example/main.go @@ -86,7 +86,9 @@ func main() { os.Exit(1) } sonar_type := os.Getenv("SONAR_TYPE") - if sonar_type != "SELFHOSTED" && sonar_type != "SAAS" { + if sonar_type == "" { + sonar_type = "SAAS" + } else if sonar_type != "SELFHOSTED" && sonar_type != "SAAS" { logger.Println("Wrong Sonar type, set SONAR_TYPE variable to either SAAS or SELFHOSTED") os.Exit(1) } From e45f67995c89c3d58b86dff69d6a115cabf2e428 Mon Sep 17 00:00:00 2001 From: carmithersh Date: Wed, 19 Mar 2025 15:16:27 +0200 Subject: [PATCH 3/3] Support for sonar selfhosted evidence, defaulting to SAAS --- examples/sonar-scan-example/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sonar-scan-example/README.md b/examples/sonar-scan-example/README.md index 2195412..929b4e5 100644 --- a/examples/sonar-scan-example/README.md +++ b/examples/sonar-scan-example/README.md @@ -10,7 +10,7 @@ should be checked using a policy. ## Environment variables - `SONAR_TOKEN` - The sonar server token. -- `SONAR_TYPE` - Should be Either SAAS or SELFHOSTED. +- `SONAR_TYPE` - Should be Either SAAS or SELFHOSTED, defaulting to SAAS. - `SONAR_HOST` - The sonar server host name, for example sonar.myconpany.org. required for SELFHOSTED type, if not provided for SAAS type sonarcloud.io is used as default. ## Arguments