plugin-sonar/pkg/plugin/plugin.go

241 lines
6.4 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 plugin
import (
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"git.kle.li/woodpecker/plugin-lib/common"
"git.kle.li/woodpecker/plugin-sonar/pkg/sonar"
"github.com/pelletier/go-toml"
"go.uber.org/zap"
)
type (
Plugin struct {
Config *sonar.Config
BuildData *common.BuildData
Logger *zap.SugaredLogger
}
args struct {
list []string
logger *zap.SugaredLogger
}
)
func (p *Plugin) AnalyzeBranch() bool {
return p.Config.BranchAnalysis == "true" || (p.Config.BranchAnalysis == "auto" && !p.SourceBranchIsDefault())
}
func (p *Plugin) SourceBranchIsDefault() bool {
return p.BuildData.SourceBranch() == p.BuildData.Metadata.Repo.Branch || len(p.BuildData.SourceBranch()) == 0
}
func (p *Plugin) getAuth() *sonar.Authentication {
return &sonar.Authentication{
Token: p.Config.Token,
}
}
func (p *Plugin) Exec() {
p.prepareVersion()
p.runScanner()
report := p.staticScanToReport()
p.Logger.Infow("job url", "url", report.CeTaskURL)
task := sonar.WaitForSonarJob(report, p.getAuth(), p.Config)
p.Logger.Infow("Job finished", "report", report)
status := sonar.GetStatus(task, report, p.getAuth(), p.Config)
p.Logger.Infow("status received", "status", status, "dashboardURL", p.Config.GetProjectDashUrl())
p.Logger.Infow("Woodpecker SonarQube Plugin",
"qualityEnabled", p.Config.QualityEnabled)
if p.Config.QualityEnabled {
p.Logger.Info("QualityGate ENABLED")
if status != p.Config.Quality {
p.Logger.Fatal("QualityGate status FAILED",
"targetQuality", p.Config.Quality,
"qualityResult", status)
} else {
p.Logger.Infow("QualityGate PASSED", "status", status)
}
}
if !p.Config.QualityEnabled {
p.Logger.Info("QualityGate DISABLED only displaying information")
if status != p.Config.Quality {
p.Logger.Infow("QualityGate status FAILED", "status", status)
} else {
p.Logger.Infow("QualityGate status PASSED", "status", status)
}
}
}
func (p *Plugin) runScanner() {
scannerArgs := p.generateScannerArgs()
p.Logger.Debugw("Prepping env SONAR_USER_HOME")
os.Setenv("SONAR_USER_HOME", ".sonar")
p.Logger.Infow("executing sonar-scanner", "options", strings.Join(scannerArgs, " "))
cmd := exec.Command("sonar-scanner", scannerArgs...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
p.Logger.Fatalw("executing sonarscanner failed", "error", err)
}
}
func (p *Plugin) generateScannerArgs() []string {
args := &args{
list: []string{
"-Dsonar.host.url=" + p.Config.HostURL,
"-Dsonar.login=" + p.Config.Token,
},
logger: p.Logger,
}
if !p.Config.UsingProperties {
argsParameter := []string{
"-Dsonar.projectKey=" + p.Config.Key,
"-Dsonar.sources=" + p.Config.Sources,
"-Dsonar.ws.timeout=" + strconv.Itoa(p.Config.HttpTimeout),
"-Dsonar.log.level=" + p.Config.SonarLogLevel,
"-Dsonar.showProfiling=" + p.Config.ShowProfiling,
"-Dsonar.scm.provider=" + p.BuildData.AdditionalData.SCM,
}
args.append(argsParameter...)
args.appendIfNotEmpty("-Dsonar.projectName=", p.Config.Name)
args.appendIfNotEmpty("-Dsonar.projectVersion=", p.Config.Version)
args.appendIfNotEmpty("-Dsonar.java.binaries=", p.Config.Binaries)
args.appendIfNotEmpty("-Dsonar.exclusions=", p.Config.Exclusions)
args.appendIfNotEmpty("-Dsonar.inclusions=", p.Config.Inclusions)
}
p.Logger.Debugw("argsbuilder",
"analyze", p.AnalyzeBranch(),
"isPr", p.BuildData.IsPR(),
"isTag", p.BuildData.IsTag(),
"source", p.BuildData.SourceBranch(),
"target", p.BuildData.TargetBranch(),
"prKey", p.BuildData.PullRequest())
if p.AnalyzeBranch() && !p.BuildData.IsPR() {
args.appendMustIfNotEmpty("-Dsonar.branch.name=", p.BuildData.TargetBranch())
}
if p.AnalyzeBranch() && p.BuildData.IsPR() {
args.appendMustIfNotEmpty("-Dsonar.pullrequest.branch=", p.BuildData.SourceBranch())
args.appendMustIfNotEmpty("-Dsonar.pullrequest.base=", p.BuildData.TargetBranch())
args.appendMustIfNotEmpty("-Dsonar.pullrequest.key=", p.BuildData.PullRequest())
}
args.appendIfNotEmpty("-Dsonar.qualitygate.wait=", strconv.FormatBool(p.Config.QualityEnabled))
args.appendIfNotEmpty("-Dsonar.qualitygate.timeout=", strconv.Itoa(p.Config.QualityTimeout))
validArg := regexp.MustCompile(".*=.+")
for _, argument := range args.list {
if !validArg.Match([]byte(argument)) {
p.Logger.Fatalf("verifying generateScannerArgs failed: %s does not have a value assigned", argument)
}
}
return args.list
}
func (p *Plugin) staticScanToReport() *sonar.Report {
var reportLocation = ".scannerwork/report-task.txt"
fileData, err := os.ReadFile(reportLocation)
if err != nil {
p.Logger.Fatalw("failed to access report-task file",
"filepath", reportLocation,
"error", err)
}
replacer := regexp.MustCompile("(?m)^(.*?)=(.*)($)")
data := replacer.ReplaceAllString(string(fileData), "$1=\"$2\"")
report := sonar.Report{}
if err = toml.Unmarshal([]byte(data), &report); err != nil {
p.Logger.Fatalw("failed to unmarshal task-report toml to Report", "error", err)
}
return &report
}
func (p *Plugin) prepareVersion() {
if len(p.Config.Version) > 0 {
return
}
if p.BuildData.IsTag() {
p.Config.Version = p.BuildData.Tag()
p.Logger.Infow("Version unset updating it",
"action", "tag",
"version", p.Config.Version)
return
}
if p.BuildData.IsPR() {
p.Config.Version = p.BuildData.SourceBranch()
p.Logger.Infow("Version unset updating it",
"action", "pull request",
"version", p.Config.Version)
return
}
p.Config.Version = p.BuildData.TargetBranch()
}
func (a *args) appendMustIfNotEmpty(prefix string, value string) {
if len(value) > 0 {
a.append(prefix + value)
} else {
a.logger.Fatalw("error appending argument, value empty",
"prefix", prefix)
}
}
func (a *args) appendIfNotEmpty(prefix string, value string) {
if len(value) > 0 {
a.append(prefix + value)
}
}
func (a *args) append(elems ...string) {
a.list = append(a.list, elems...)
}