1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00
sap-jenkins-library/cmd/sonarExecuteScan.go
Christopher Fenner 27c6320474
feat(sonar): write flag for sonar execution to influx (#1534)
* write flag for sonar execution to influx

* add missing step metadata file

* first attempt to read influx from disk

* add missing import

* correct glob pattern

* use file path

* correct type names

* cleanup

* fix code climate issue

* fix typo

Co-authored-by: Stephan Aßmus <stephan.assmus@sap.com>

* add test case

Co-authored-by: Stephan Aßmus <stephan.assmus@sap.com>
2020-05-14 13:46:40 +02:00

257 lines
8.9 KiB
Go

package cmd
import (
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/SAP/jenkins-library/pkg/command"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
FileUtils "github.com/SAP/jenkins-library/pkg/piperutils"
SliceUtils "github.com/SAP/jenkins-library/pkg/piperutils"
StepResults "github.com/SAP/jenkins-library/pkg/piperutils"
SonarUtils "github.com/SAP/jenkins-library/pkg/sonar"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/pkg/errors"
)
type sonarSettings struct {
workingDir string
binary string
environment []string
options []string
}
func (s *sonarSettings) addEnvironment(element string) {
s.environment = append(s.environment, element)
}
func (s *sonarSettings) addOption(element string) { s.options = append(s.options, element) }
var sonar sonarSettings
var execLookPath = exec.LookPath
var fileUtilsExists = FileUtils.FileExists
var fileUtilsUnzip = FileUtils.Unzip
var osRename = os.Rename
func sonarExecuteScan(config sonarExecuteScanOptions, _ *telemetry.CustomData, influx *sonarExecuteScanInflux) {
runner := command.Command{}
// reroute command output to logging framework
runner.Stdout(log.Writer())
runner.Stderr(log.Writer())
client := piperhttp.Client{}
client.SetOptions(piperhttp.ClientOptions{TransportTimeout: 20 * time.Second})
sonar = sonarSettings{
workingDir: "./",
binary: "sonar-scanner",
environment: []string{},
options: []string{},
}
influx.step_data.fields.sonar = "false"
if err := runSonar(config, &client, &runner); err != nil {
log.Entry().WithError(err).Fatal("Execution failed")
}
influx.step_data.fields.sonar = "true"
}
func runSonar(config sonarExecuteScanOptions, client piperhttp.Downloader, runner execRunner) error {
if len(config.Host) > 0 {
sonar.addEnvironment("SONAR_HOST_URL=" + config.Host)
}
if len(config.Token) > 0 {
sonar.addEnvironment("SONAR_TOKEN=" + config.Token)
}
if len(config.Organization) > 0 {
sonar.addOption("sonar.organization=" + config.Organization)
}
if len(config.ProjectVersion) > 0 {
// handleArtifactVersion is reused from cmd/protecodeExecuteScan.go
sonar.addOption("sonar.projectVersion=" + handleArtifactVersion(config.ProjectVersion))
}
if err := handlePullRequest(config); err != nil {
return err
}
if err := loadSonarScanner(config.SonarScannerDownloadURL, client); err != nil {
return err
}
if err := loadCertificates(config.CustomTLSCertificateLinks, client, runner); err != nil {
return err
}
if len(config.Options) > 0 {
sonar.options = append(sonar.options, config.Options...)
}
sonar.options = SliceUtils.PrefixIfNeeded(SliceUtils.Trim(sonar.options), "-D")
log.Entry().
WithField("command", sonar.binary).
WithField("options", sonar.options).
WithField("environment", sonar.environment).
Debug("Executing sonar scan command")
// execute scan
runner.SetEnv(sonar.environment)
err := runner.RunExecutable(sonar.binary, sonar.options...)
if err != nil {
return err
}
// load task results
taskReport, err := SonarUtils.ReadTaskReport(sonar.workingDir)
if err != nil {
log.Entry().WithError(err).Warning("Unable to read Sonar task report file.")
} else {
// write links JSON
links := []StepResults.Path{
StepResults.Path{
Target: taskReport.DashboardURL,
Name: "Sonar Web UI",
},
}
StepResults.PersistReportsAndLinks("sonarExecuteScan", sonar.workingDir, nil, links)
}
return nil
}
func handlePullRequest(config sonarExecuteScanOptions) error {
if len(config.ChangeID) > 0 {
if config.LegacyPRHandling {
// see https://docs.sonarqube.org/display/PLUG/GitHub+Plugin
sonar.addOption("sonar.analysis.mode=preview")
sonar.addOption("sonar.github.pullRequest=" + config.ChangeID)
if len(config.GithubAPIURL) > 0 {
sonar.addOption("sonar.github.endpoint=" + config.GithubAPIURL)
}
if len(config.GithubToken) > 0 {
sonar.addOption("sonar.github.oauth=" + config.GithubToken)
}
if len(config.Owner) > 0 && len(config.Repository) > 0 {
sonar.addOption("sonar.github.repository=" + config.Owner + "/" + config.Repository)
}
if config.DisableInlineComments {
sonar.addOption("sonar.github.disableInlineComments=" + strconv.FormatBool(config.DisableInlineComments))
}
} else {
// see https://sonarcloud.io/documentation/analysis/pull-request/
provider := strings.ToLower(config.PullRequestProvider)
if provider == "github" {
if len(config.Owner) > 0 && len(config.Repository) > 0 {
sonar.addOption("sonar.pullrequest.github.repository=" + config.Owner + "/" + config.Repository)
}
} else {
return errors.New("Pull-Request provider '" + provider + "' is not supported!")
}
sonar.addOption("sonar.pullrequest.key=" + config.ChangeID)
sonar.addOption("sonar.pullrequest.base=" + config.ChangeTarget)
sonar.addOption("sonar.pullrequest.branch=" + config.ChangeBranch)
sonar.addOption("sonar.pullrequest.provider=" + provider)
}
} else if len(config.BranchName) > 0 {
sonar.addOption("sonar.branch.name=" + config.BranchName)
}
return nil
}
func loadSonarScanner(url string, client piperhttp.Downloader) error {
if scannerPath, err := execLookPath(sonar.binary); err == nil {
// using existing sonar-scanner
log.Entry().WithField("path", scannerPath).Debug("Using local sonar-scanner")
} else if len(url) != 0 {
// download sonar-scanner-cli into TEMP folder
log.Entry().WithField("url", url).Debug("Downloading sonar-scanner")
tmpFolder := getTempDir()
defer os.RemoveAll(tmpFolder) // clean up
archive := filepath.Join(tmpFolder, path.Base(url))
if err := client.DownloadFile(url, archive, nil, nil); err != nil {
return errors.Wrap(err, "Download of sonar-scanner failed")
}
// unzip sonar-scanner-cli
log.Entry().WithField("source", archive).WithField("target", tmpFolder).Debug("Extracting sonar-scanner")
if _, err := fileUtilsUnzip(archive, tmpFolder); err != nil {
return errors.Wrap(err, "Extraction of sonar-scanner failed")
}
// move sonar-scanner-cli to .sonar-scanner/
toolPath := ".sonar-scanner"
foldername := strings.ReplaceAll(strings.ReplaceAll(archive, ".zip", ""), "cli-", "")
log.Entry().WithField("source", foldername).WithField("target", toolPath).Debug("Moving sonar-scanner")
if err := osRename(foldername, toolPath); err != nil {
return errors.Wrap(err, "Moving of sonar-scanner failed")
}
// update binary path
sonar.binary = filepath.Join(getWorkingDir(), toolPath, "bin", sonar.binary)
log.Entry().Debug("Download completed")
}
return nil
}
func loadCertificates(certificateString string, client piperhttp.Downloader, runner execRunner) error {
trustStoreFile := filepath.Join(getWorkingDir(), ".certificates", "cacerts")
if exists, _ := fileUtilsExists(trustStoreFile); exists {
// use local existing trust store
sonar.addEnvironment("SONAR_SCANNER_OPTS=-Djavax.net.ssl.trustStore=" + trustStoreFile)
log.Entry().WithField("trust store", trustStoreFile).Info("Using local trust store")
} else
//TODO: certificate loading is deactivated due to the missing JAVA keytool
// see https://github.com/SAP/jenkins-library/issues/1072
if os.Getenv("PIPER_SONAR_LOAD_CERTIFICATES") == "true" && len(certificateString) > 0 {
// use local created trust store with downloaded certificates
keytoolOptions := []string{
"-import",
"-noprompt",
"-storepass", "changeit",
"-keystore", trustStoreFile,
}
tmpFolder := getTempDir()
defer os.RemoveAll(tmpFolder) // clean up
certificateList := strings.Split(certificateString, ",")
for _, certificate := range certificateList {
filename := path.Base(certificate) // decode?
target := filepath.Join(tmpFolder, filename)
log.Entry().WithField("source", certificate).WithField("target", target).Info("Downloading TLS certificate")
// download certificate
if err := client.DownloadFile(certificate, target, nil, nil); err != nil {
return errors.Wrapf(err, "Download of TLS certificate failed")
}
options := append(keytoolOptions, "-file", target)
options = append(options, "-alias", filename)
// add certificate to keystore
if err := runner.RunExecutable("keytool", options...); err != nil {
return errors.Wrap(err, "Adding certificate to keystore failed")
}
}
sonar.addEnvironment("SONAR_SCANNER_OPTS=-Djavax.net.ssl.trustStore=" + trustStoreFile)
log.Entry().WithField("trust store", trustStoreFile).Info("Using local trust store")
} else {
log.Entry().Debug("Download of TLS certificates skipped")
}
return nil
}
func getWorkingDir() string {
workingDir, err := os.Getwd()
if err != nil {
log.Entry().WithError(err).WithField("path", workingDir).Debug("Retrieving of work directory failed")
}
return workingDir
}
func getTempDir() string {
tmpFolder, err := ioutil.TempDir(".", "temp-")
if err != nil {
log.Entry().WithError(err).WithField("path", tmpFolder).Debug("Creating temp directory failed")
}
return tmpFolder
}