// 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...) }