From 7417babfe7eeae2f0ac16e0e392841c483a3f8e1 Mon Sep 17 00:00:00 2001 From: Joaquim Alvino de Mesquita Neto Date: Tue, 28 Sep 2021 07:30:29 -0300 Subject: [PATCH] Add git repository info as sonarqube property defaults (#8) Changed default values of sonarqube properties sonar.projectKey and sonar.projectName to be derived from git. If no git repository exists, the property defaults to null, using the default values provided by sonarqube gradle extension itself. Sonarqube property sonar.branch.name was added to provide support to multibranch analysis. It uses the git branch provided by atlas-github, and if it detects the branch name as a PR branch (with name starting with "PR-"), then it tries to find this branch "real" name using atlas-github exposed github api. Finally if it does not finds anything, it unsets the property. Co-authored-by: Joaquim Neto --- Jenkinsfile | 25 +++- build.gradle | 13 ++ ...onarScannerBeginTaskIntegrationSpec.groovy | 68 ++++++++- .../dotnetsonar/DotNetSonarqubePlugin.groovy | 132 +++++++----------- .../tasks/SonarScannerBegin.groovy | 2 +- 5 files changed, 154 insertions(+), 86 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index d9a77bd..d84cc9e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,6 +1,27 @@ #!groovy @Library('github.com/wooga/atlas-jenkins-pipeline@1.x') _ -withCredentials([string(credentialsId: 'atlas_plugins_sonar_token', variable: 'sonar_token')]) { - buildGradlePlugin platforms: ['macos','windows', 'linux'], sonarToken: sonar_token +withCredentials([usernamePassword(credentialsId: 'github_integration', passwordVariable: 'githubPassword', usernameVariable: 'githubUser'), + usernamePassword(credentialsId: 'github_integration_2', passwordVariable: 'githubPassword2', usernameVariable: 'githubUser2'), + usernamePassword(credentialsId: 'github_integration_3', passwordVariable: 'githubPassword3', usernameVariable: 'githubUser3'), + string(credentialsId: 'atlas_plugins_sonar_token', variable: 'sonar_token')]) { + def testEnvironment = [ + 'macos': + [ + "ATLAS_GITHUB_INTEGRATION_USER=${githubUser}", + "ATLAS_GITHUB_INTEGRATION_PASSWORD=${githubPassword}" + ], + 'windows': + [ + "ATLAS_GITHUB_INTEGRATION_USER=${githubUser2}", + "ATLAS_GITHUB_INTEGRATION_PASSWORD=${githubPassword2}" + ], + 'linux': + [ + "ATLAS_GITHUB_INTEGRATION_USER=${githubUser3}", + "ATLAS_GITHUB_INTEGRATION_PASSWORD=${githubPassword3}" + ] + ] + + buildGradlePlugin platforms: ['macos','windows', 'linux'], sonarToken: sonar_token, testEnvironment: testEnvironment } diff --git a/build.gradle b/build.gradle index 49ef133..79e4820 100644 --- a/build.gradle +++ b/build.gradle @@ -50,4 +50,17 @@ github { dependencies { api 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.2.0' + implementation 'gradle.plugin.net.wooga.gradle:atlas-github:2.+' + testImplementation 'com.wooga.spock.extensions:spock-github-extension:0.2.0' + testImplementation 'org.ajoberstar.grgit:grgit-core:4.1.0' +} + +configurations.all { + resolutionStrategy { + force 'org.codehaus.groovy:groovy-all:2.5.12' + force 'org.codehaus.groovy:groovy-macro:2.5.12' + force 'org.codehaus.groovy:groovy-nio:2.5.12' + force 'org.codehaus.groovy:groovy-sql:2.5.12' + force 'org.codehaus.groovy:groovy-xml:2.5.12' + } } diff --git a/src/integrationTest/groovy/wooga/gradle/dotnetsonar/tasks/SonarScannerBeginTaskIntegrationSpec.groovy b/src/integrationTest/groovy/wooga/gradle/dotnetsonar/tasks/SonarScannerBeginTaskIntegrationSpec.groovy index 7b2fa70..b24c601 100644 --- a/src/integrationTest/groovy/wooga/gradle/dotnetsonar/tasks/SonarScannerBeginTaskIntegrationSpec.groovy +++ b/src/integrationTest/groovy/wooga/gradle/dotnetsonar/tasks/SonarScannerBeginTaskIntegrationSpec.groovy @@ -1,8 +1,12 @@ package wooga.gradle.dotnetsonar.tasks +import com.wooga.spock.extensions.github.GithubRepository +import com.wooga.spock.extensions.github.Repository +import com.wooga.spock.extensions.github.api.RateLimitHandlerWait +import com.wooga.spock.extensions.github.api.TravisBuildNumberPostFix +import org.ajoberstar.grgit.Grgit import org.gradle.process.internal.ExecException import spock.lang.Unroll -import wooga.gradle.dotnetsonar.SonarScannerExtension import wooga.gradle.dotnetsonar.tasks.utils.PluginIntegrationSpec import wooga.gradle.dotnetsonar.utils.FakeExecutable @@ -11,12 +15,68 @@ import static wooga.gradle.dotnetsonar.utils.SpecUtils.rootCause class SonarScannerBeginTaskIntegrationSpec extends PluginIntegrationSpec { + @GithubRepository( + usernameEnv = "ATLAS_GITHUB_INTEGRATION_USER", + tokenEnv = "ATLAS_GITHUB_INTEGRATION_PASSWORD", + repositoryPostFixProvider = [TravisBuildNumberPostFix.class], + rateLimitHandler = RateLimitHandlerWait.class, + resetAfterTestCase = true + ) + def "task executes sonar scanner tool begin command with git-based properties"(Repository testRepo) { + given: "a sonar scanner executable" + def fakeSonarScannerExec = argReflectingFakeExecutable("sonarscanner", 0) + and: "a set up sonar scanner extension" + buildFile << forceAddObjectsToExtension(fakeSonarScannerExec) + and: "a set up github extension" + buildFile << """ + github { + repositoryName = "${testRepo.fullName}" + username = "${testRepo.userName}" + token = "${testRepo.token}" + } + """ + and: "a pull request open for this repository" + def prBranchName = "realbranch" + testRepo.createBranch(prBranchName) + testRepo.commit("commitmsg", prBranchName) + def pr = testRepo.createPullRequest("Test", prBranchName, testRepo.defaultBranch.name, "description") + + and: "a setup PR git repository" + def git = Grgit.init(dir: projectDir) + git.commit(message: "any message") + git.checkout(branch: "PR-${pr.number}", createBranch: true) + + when: + def result = runTasksSuccessfully("sonarScannerBegin") + + then: + def execResults = FakeExecutable.lastExecutionResults(result) + def companyName = testRepo.fullName.split("/")[0] + execResults.args.contains("-n:${testRepo.name}".toString()) + execResults.args.contains("-k:${companyName}_${testRepo.name}".toString()) + execResults.args.contains("-d:sonar.branch.name=${prBranchName}".toString()) + } def "task executes sonar scanner tool begin command with extension properties"() { given: "a sonar scanner executable" def fakeSonarScannerExec = argReflectingFakeExecutable("sonarscanner", 0) and: "a set up sonar scanner extension" buildFile << forceAddObjectsToExtension(fakeSonarScannerExec) + + and: "a configured github extension" + def companyName = "company" + def repoName = "repo" + buildFile << """ + github { + repositoryName = "${companyName}/${repoName}" + } + """ + and: "a git repository on branch" + def branchName = "branch" + def git = Grgit.init(dir: projectDir) + git.commit(message: "any message") + git.checkout(branch: branchName, createBranch: true) + and: "a configured sonarqube extension" def projectVersion = "0.0.1" buildFile << """ @@ -28,12 +88,16 @@ class SonarScannerBeginTaskIntegrationSpec extends PluginIntegrationSpec { } } """ + when: "running the sonarScannerBegin task" def result = runTasksSuccessfully("sonarScannerBegin") + then: def execResults = FakeExecutable.lastExecutionResults(result) - execResults.args.contains("-k:${moduleName}".toString()) + execResults.args.contains("-n:${repoName}".toString()) + execResults.args.contains("-k:${companyName}_${repoName}".toString()) execResults.args.contains("-v:${projectVersion}".toString()) + execResults.args.contains("-d:sonar.branch.name=${branchName}".toString()) execResults.args.contains("-d:sonar.exclusions=src") !execResults.args.contains("-d:sonar.sources=") !execResults.args.contains("-d:sonar.prop=") diff --git a/src/main/groovy/wooga/gradle/dotnetsonar/DotNetSonarqubePlugin.groovy b/src/main/groovy/wooga/gradle/dotnetsonar/DotNetSonarqubePlugin.groovy index 9be4cbc..8efbb3c 100644 --- a/src/main/groovy/wooga/gradle/dotnetsonar/DotNetSonarqubePlugin.groovy +++ b/src/main/groovy/wooga/gradle/dotnetsonar/DotNetSonarqubePlugin.groovy @@ -2,12 +2,17 @@ package wooga.gradle.dotnetsonar import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.UncheckedIOException +import org.gradle.api.provider.Provider import org.sonarqube.gradle.ActionBroadcast import org.sonarqube.gradle.SonarQubeExtension import org.sonarqube.gradle.SonarQubeProperties import wooga.gradle.dotnetsonar.tasks.BuildSolution import wooga.gradle.dotnetsonar.tasks.SonarScannerBegin import wooga.gradle.dotnetsonar.tasks.SonarScannerEnd +import wooga.gradle.github.base.GithubBasePlugin +import wooga.gradle.github.base.GithubPluginExtension +import wooga.gradle.github.base.internal.DefaultGithubPluginExtension import static SonarScannerExtension.* @@ -15,11 +20,14 @@ class DotNetSonarqubePlugin implements Plugin { @Override void apply(Project project) { + project.plugins.apply(GithubBasePlugin) + def githubExt = project.extensions.findByType(DefaultGithubPluginExtension) + def actionBroadcast = new ActionBroadcast() def sonarScannerExt = project.extensions.create(SONARSCANNER_EXTENSION_NAME, SonarScannerExtension, project, actionBroadcast) def sonarQubeExt = project.extensions.create(SonarQubeExtension.SONARQUBE_EXTENSION_NAME, SonarQubeExtension, actionBroadcast) sonarQubeExt.properties { it -> - defaultSonarProperties(project, it) + defaultSonarProperties(project, githubExt, it) } project.tasks.register(MS_BUILD_TASK_NAME, BuildSolution).configure { buildTask -> @@ -46,94 +54,56 @@ class DotNetSonarqubePlugin implements Plugin { } } - static final void defaultSonarProperties(Project project, SonarQubeProperties properties) { + static final void defaultSonarProperties(Project project, GithubPluginExtension githubExt, + SonarQubeProperties properties) { + def companyNameProvider = githubExt.repositoryName.map{String fullRepoName -> fullRepoName.split("/")[0]} + def repoNameProvider = githubExt.repositoryName.map{String fullRepoName -> fullRepoName.split("/")[1]} + def keyProvider = companyNameProvider.map { comp -> + return repoNameProvider.map {repoName -> "${comp}_${repoName}"}.getOrNull() + } + def branchProvider = localBranchProviderWithPR(project, githubExt).map { it.trim().isEmpty() ? null : it } properties.with { property("sonar.login", System.getenv('SONAR_TOKEN')) property("sonar.host.url", System.getenv('SONAR_HOST')) - //would be better if this was associated to github repository, see atlas-plugins - property("sonar.projectKey", project.rootProject.name) - property("sonar.projectName", project.rootProject.name) - //property("sonar.sources", ".") + property("sonar.projectKey", keyProvider.getOrNull()) + property("sonar.projectName", repoNameProvider.getOrNull()) + property("sonar.branch.name", branchProvider.getOrNull()) if(project.version != null) { property("sonar.version", project.version.toString()) } } } -} -/* - steps: - 1. Create solution if not exists already //unity task - 2. Setup unity code coverage //unity task - 3. download dotnet-sonarscanner if not exists //done(ish) - 3. SonarBegin - 4. Build C# solution //done(ish) - 5. SonarEnd -* */ + static Provider localBranchProviderWithPR(Project project, GithubPluginExtension githubExt) { + def clientProvider = emptyProviderForException(project, githubExt.clientProvider, UncheckedIOException) + return githubExt.branchName.map {currentBranch -> + return clientProvider.map {client -> + def repository = client.getRepository(githubExt.repositoryName.get()) + if (currentBranch.toUpperCase().startsWith("PR-")) { + def maybePrNumber = currentBranch.replace("PR-", "").trim() + if (maybePrNumber.isNumber()) { + def prNumber = Integer.valueOf(maybePrNumber) + return repository.getPullRequest(prNumber).head.ref + } + return null + } + }.getOrElse(currentBranch) + } + } -// task(setupUnityProject, type:wooga.gradle.unity.tasks.Unity) { -// args "-executeMethod", "UnityEditor.SyncVS.SyncSolution" -// quit = true -// } -// tasks.withType(wooga.gradle.unity.tasks.Test) { -// it.dependsOn setupCodeCoverage -// it.args "-enableCodeCoverage" -// it.args "-debugCodeOptimization" -// it.args "-coverageResultsPath", "build/codeCoverage" -// it.args "-coverageOptions", "generateAdditionalMetrics" -// } -// task setupCodeCoverage { -// dependsOn setupUnityProject -// doLast { -// def manifest = new File(project.projectDir, 'Packages/manifest.json') -// def jsonSlurper = new JsonSlurper() -// def data = jsonSlurper.parse(manifest) -// data.dependencies["com.unity.testtools.codecoverage"] = "1.1.0" -// def json_str = JsonOutput.toJson(data) -// def json_beauty = JsonOutput.prettyPrint(json_str) -// manifest.write(json_beauty) -// } -// } - -// configuration inspired by https://github.com/MirageNet/Mirage/blob/master/.github/workflows/main.yml -// task(sonarBegin, type:Exec) { -// dependsOn test -// executable "packages/dotnet-framework-sonarscanner/tools/SonarScanner.MSBuild.exe" -// args "begin" -// args "/k:${rootProject.name}" -// args "/v:${version}" -// args "/d:sonar.login=${System.getenv('SONAR_TOKEN')}" -// args "/d:sonar.host.url=${System.getenv('SONAR_HOST')}" -// args "/d:sonar.exclusions=Assets/Paket.Unity3D/**" -// args "/d:sonar.cpd.exclusions=Assets/Tests/**" -// args "/d:sonar.coverage.exclusions=Assets/Tests/**" -// args "/d:sonar.cs.nunit.reportsPaths=build/reports/unity/*/*.xml" -// args "/d:sonar.cs.opencover.reportsPaths=build/codeCoverage/**/*.xml" -// } -// -// task(sonarMsbuild) { -// dependsOn sonarBegin -// doLast { -// def contentPath = "${unity.unityPath.getParent()}\\..\\Editor\\Data" -// exec { -// executable "${contentPath}\\NetCore\\Sdk-2.2.107\\dotnet" -// environment "FrameworkPathOverride", "${contentPath}\\MonoBleedingEdge" -// args "build" -// args "${project.name}.sln" -// } -// } -// } -// -// task(sonarEnd, type:Exec) { -// dependsOn sonarMsbuild -// executable "packages/dotnet-framework-sonarscanner/tools/SonarScanner.MSBuild.exe" -// args "end" -// args "/d:sonar.login=${System.getenv('SONAR_TOKEN')}" -// } -// -// task(sonarqube) { -// mustRunAfter test -// dependsOn sonarBegin, sonarEnd, sonarMsbuild -// } -// \ No newline at end of file + protected static Provider emptyProviderForException(Project project, + Provider provider, + Class exceptionClass) { + return project.provider { + try { + return provider.get() + }catch(Throwable e) { + if(exceptionClass.isInstance(e)) { + return null + } + throw e + } + } + } +} diff --git a/src/main/groovy/wooga/gradle/dotnetsonar/tasks/SonarScannerBegin.groovy b/src/main/groovy/wooga/gradle/dotnetsonar/tasks/SonarScannerBegin.groovy index 9dc721d..2778547 100644 --- a/src/main/groovy/wooga/gradle/dotnetsonar/tasks/SonarScannerBegin.groovy +++ b/src/main/groovy/wooga/gradle/dotnetsonar/tasks/SonarScannerBegin.groovy @@ -26,7 +26,7 @@ class SonarScannerBegin extends DefaultTask { assertKeyOnMap(properties, "sonar.version") sonarScanner.begin( properties["sonar.projectKey"].toString(), - properties["sonar.projectName"].toString(), + properties["sonar.projectName"]?.toString(), properties["sonar.version"]?.toString(), properties) }