From 59924ebbe379f9f6f057c0bf6c462ad8fea01aeb Mon Sep 17 00:00:00 2001 From: Stefan Wolf Date: Mon, 10 Jun 2019 23:15:00 +0200 Subject: [PATCH 1/4] Detect build scans in pipeline logs by using a separate step. --- build.gradle | 8 ++- .../plugins/gradle/BuildScanAction.java | 4 +- .../plugins/gradle/BuildScanLogScanner.java | 33 +++++++++ .../plugins/gradle/BuildScanPublisher.java | 68 +++++++++++++++++++ .../gradle/GradleConsoleAnnotator.java | 27 ++------ .../gradle/BuildScanPublisher/help.html | 4 ++ 6 files changed, 117 insertions(+), 27 deletions(-) create mode 100644 src/main/java/hudson/plugins/gradle/BuildScanLogScanner.java create mode 100644 src/main/java/hudson/plugins/gradle/BuildScanPublisher.java create mode 100644 src/main/resources/hudson/plugins/gradle/BuildScanPublisher/help.html diff --git a/build.gradle b/build.gradle index 756e8559..60da8710 100644 --- a/build.gradle +++ b/build.gradle @@ -54,16 +54,18 @@ jenkinsPlugin { disabledTestInjection = false } -sourceCompatibility = '1.7' +sourceCompatibility = '1.8' dependencies { - jenkinsPlugins 'org.jenkins-ci.plugins:structs:1.3' + jenkinsPlugins 'org.jenkins-ci.plugins:structs:1.5' + jenkinsPlugins 'org.jenkins-ci.plugins.workflow:workflow-api:2.20' - signature 'org.codehaus.mojo.signature:java17:1.0@signature' + signature 'org.codehaus.mojo.signature:java18:1.0@signature' jenkinsTest 'org.jenkins-ci.main:jenkins-test-harness:2.44' jenkinsTest 'org.jenkins-ci.main:jenkins-test-harness-tools:2.2' jenkinsTest 'io.jenkins:configuration-as-code:1.4' + jenkinsPlugins 'org.jenkins-ci.plugins.workflow:workflow-aggregator:2.5' testImplementation 'org.spockframework:spock-core:1.2-groovy-2.4' } diff --git a/src/main/java/hudson/plugins/gradle/BuildScanAction.java b/src/main/java/hudson/plugins/gradle/BuildScanAction.java index 58cca9d3..d0b2930a 100644 --- a/src/main/java/hudson/plugins/gradle/BuildScanAction.java +++ b/src/main/java/hudson/plugins/gradle/BuildScanAction.java @@ -32,7 +32,9 @@ public String getUrlName() { } public void addScanUrl(String scanUrl) { - scanUrls.add(scanUrl); + if (!scanUrls.contains(scanUrl)) { + scanUrls.add(scanUrl); + } } @Exported diff --git a/src/main/java/hudson/plugins/gradle/BuildScanLogScanner.java b/src/main/java/hudson/plugins/gradle/BuildScanLogScanner.java new file mode 100644 index 00000000..cec0b64c --- /dev/null +++ b/src/main/java/hudson/plugins/gradle/BuildScanLogScanner.java @@ -0,0 +1,33 @@ +package hudson.plugins.gradle; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class BuildScanLogScanner { + private final BuildScanPublishedListener listener; + private int linesSinceBuildScanPublishingMessage = Integer.MAX_VALUE; + private static final Pattern BUILD_SCAN_PATTERN = Pattern.compile("Publishing (build scan|build information)\\.\\.\\."); + private static final Pattern URL_PATTERN = Pattern.compile("https?://\\S*"); + + public static final int MAX_PUBLISHED_MESSAGE_LENGTH = 70; + + public BuildScanLogScanner(BuildScanPublishedListener listener) { + this.listener = listener; + } + + void scanLine(String line) { + if (linesSinceBuildScanPublishingMessage < 10) { + linesSinceBuildScanPublishingMessage++; + Matcher matcher = URL_PATTERN.matcher(line); + if (matcher.find()) { + linesSinceBuildScanPublishingMessage = Integer.MAX_VALUE; + String buildScanUrl = matcher.group(); + listener.onBuildScanPublished(buildScanUrl); + } + } + if (line.length() < MAX_PUBLISHED_MESSAGE_LENGTH && BUILD_SCAN_PATTERN.matcher(line).find()) { + linesSinceBuildScanPublishingMessage = 0; + } + + } +} diff --git a/src/main/java/hudson/plugins/gradle/BuildScanPublisher.java b/src/main/java/hudson/plugins/gradle/BuildScanPublisher.java new file mode 100644 index 00000000..77631f80 --- /dev/null +++ b/src/main/java/hudson/plugins/gradle/BuildScanPublisher.java @@ -0,0 +1,68 @@ +package hudson.plugins.gradle; + +import com.google.common.collect.ImmutableSet; +import hudson.Extension; +import hudson.model.Run; +import org.jenkinsci.plugins.workflow.steps.Step; +import org.jenkinsci.plugins.workflow.steps.StepContext; +import org.jenkinsci.plugins.workflow.steps.StepDescriptor; +import org.jenkinsci.plugins.workflow.steps.StepExecution; +import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution; +import org.kohsuke.stapler.DataBoundConstructor; + +import javax.annotation.Nonnull; +import java.io.BufferedReader; +import java.util.Set; +import java.util.stream.Stream; + +public class BuildScanPublisher extends Step { + @DataBoundConstructor + public BuildScanPublisher() { + } + + @Override + public StepExecution start(StepContext context) { + return new Execution(context); + } + + static class Execution extends SynchronousNonBlockingStepExecution { + private static final long serialVersionUID = 1L; + + protected Execution(@Nonnull StepContext context) { + super(context); + } + + @Override + protected Void run() throws Exception { + Run run = getContext().get(Run.class); + BuildScanLogScanner scanner = new BuildScanLogScanner(new DefaultBuildScanPublishedListener(run)); + try ( + BufferedReader logReader = new BufferedReader(run.getLogReader()); + Stream lines = logReader.lines() + ) { + lines.forEach(scanner::scanLine); + } + return null; + } + } + + @Extension + public static final class DescriptorImpl extends StepDescriptor { + + @Override + public Set> getRequiredContext() { + return ImmutableSet.of(Run.class); + } + + @Nonnull + @Override + public String getDisplayName() { + return "Find published build scans"; + } + + @Override + public String getFunctionName() { + return "findBuildScans"; + } + } +} diff --git a/src/main/java/hudson/plugins/gradle/GradleConsoleAnnotator.java b/src/main/java/hudson/plugins/gradle/GradleConsoleAnnotator.java index 163a773e..af140efe 100644 --- a/src/main/java/hudson/plugins/gradle/GradleConsoleAnnotator.java +++ b/src/main/java/hudson/plugins/gradle/GradleConsoleAnnotator.java @@ -6,8 +6,6 @@ import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.charset.Charset; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * @author ikikko @@ -15,24 +13,18 @@ */ public class GradleConsoleAnnotator extends LineTransformationOutputStream { - private static final Pattern BUILD_SCAN_PATTERN = Pattern.compile("Publishing (build scan|build information)\\.\\.\\."); - private static final Pattern URL_PATTERN = Pattern.compile("https?://\\S*"); - private static final int MAX_PUBLISHED_MESSAGE_LENGTH = 70; - private final OutputStream out; private final Charset charset; private final boolean annotateGradleOutput; - private final BuildScanPublishedListener buildScanListener; private final int maxLineLength; - - private int linesSinceBuildScanPublishingMessage = Integer.MAX_VALUE; + private final BuildScanLogScanner buildScanLogScanner; public GradleConsoleAnnotator(OutputStream out, Charset charset, boolean annotateGradleOutput, BuildScanPublishedListener buildScanListener) { this.out = out; this.charset = charset; this.annotateGradleOutput = annotateGradleOutput; - this.buildScanListener = buildScanListener; - this.maxLineLength = annotateGradleOutput ? 500 : MAX_PUBLISHED_MESSAGE_LENGTH; + this.maxLineLength = annotateGradleOutput ? 500 : BuildScanLogScanner.MAX_PUBLISHED_MESSAGE_LENGTH; + this.buildScanLogScanner = new BuildScanLogScanner(buildScanListener); } @Override @@ -55,18 +47,7 @@ protected void eol(byte[] b, int len) throws IOException { } } - if (linesSinceBuildScanPublishingMessage < 10) { - linesSinceBuildScanPublishingMessage++; - Matcher matcher = URL_PATTERN.matcher(line); - if (matcher.find()) { - linesSinceBuildScanPublishingMessage = Integer.MAX_VALUE; - String buildScanUrl = matcher.group(); - buildScanListener.onBuildScanPublished(buildScanUrl); - } - } - if (len < MAX_PUBLISHED_MESSAGE_LENGTH && BUILD_SCAN_PATTERN.matcher(line).find()) { - linesSinceBuildScanPublishingMessage = 0; - } + buildScanLogScanner.scanLine(line); } out.write(b, 0, len); diff --git a/src/main/resources/hudson/plugins/gradle/BuildScanPublisher/help.html b/src/main/resources/hudson/plugins/gradle/BuildScanPublisher/help.html new file mode 100644 index 00000000..ddae0eff --- /dev/null +++ b/src/main/resources/hudson/plugins/gradle/BuildScanPublisher/help.html @@ -0,0 +1,4 @@ +
+ Inspect build log for published Gradle build scans. + The build scans will be shown on the pipeline build page. +
From 5000c8f01fe5a2f50986b3c68ada5c2119ccdc73 Mon Sep 17 00:00:00 2001 From: Stefan Wolf Date: Sat, 22 Jun 2019 17:37:46 +0200 Subject: [PATCH 2/4] Use workflow 2.6 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 60da8710..9d24a734 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,7 @@ dependencies { jenkinsTest 'org.jenkins-ci.main:jenkins-test-harness:2.44' jenkinsTest 'org.jenkins-ci.main:jenkins-test-harness-tools:2.2' jenkinsTest 'io.jenkins:configuration-as-code:1.4' - jenkinsPlugins 'org.jenkins-ci.plugins.workflow:workflow-aggregator:2.5' + jenkinsPlugins 'org.jenkins-ci.plugins.workflow:workflow-aggregator:2.6' testImplementation 'org.spockframework:spock-core:1.2-groovy-2.4' } From ee10b953044317b5ce5b38b12e58ed37192fdde8 Mon Sep 17 00:00:00 2001 From: Stefan Wolf Date: Sat, 22 Jun 2019 23:58:36 +0200 Subject: [PATCH 3/4] Add integration test for scanning Pipeline log --- build.gradle | 16 +++++++- .../gradle/BuildScanIntegrationTest.groovy | 38 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 9d24a734..df4164bb 100644 --- a/build.gradle +++ b/build.gradle @@ -59,13 +59,25 @@ sourceCompatibility = '1.8' dependencies { jenkinsPlugins 'org.jenkins-ci.plugins:structs:1.5' jenkinsPlugins 'org.jenkins-ci.plugins.workflow:workflow-api:2.20' + jenkinsPlugins 'org.jenkins-ci.plugins.workflow:workflow-cps:2.24' + jenkinsPlugins 'org.jenkins-ci.plugins.workflow:workflow-job:2.9' + jenkinsPlugins 'org.jenkins-ci.plugins.workflow:workflow-basic-steps:2.3' + jenkinsPlugins 'org.jenkins-ci.plugins.workflow:workflow-durable-task-step:2.8' + jenkinsPlugins 'org.jenkins-ci.plugins.workflow:workflow-step-api:2.10' signature 'org.codehaus.mojo.signature:java18:1.0@signature' - jenkinsTest 'org.jenkins-ci.main:jenkins-test-harness:2.44' + jenkinsTest 'org.jenkins-ci.main:jenkins-test-harness:2.49' jenkinsTest 'org.jenkins-ci.main:jenkins-test-harness-tools:2.2' jenkinsTest 'io.jenkins:configuration-as-code:1.4' - jenkinsPlugins 'org.jenkins-ci.plugins.workflow:workflow-aggregator:2.6' + + jenkinsServer 'org.jenkins-ci.plugins.workflow:workflow-aggregator:2.5' + jenkinsServer 'org.jenkins-ci.plugins.workflow:workflow-api:2.20' + jenkinsServer 'org.jenkins-ci.plugins.workflow:workflow-cps:2.24' + jenkinsServer 'org.jenkins-ci.plugins.workflow:workflow-job:2.9' + jenkinsServer 'org.jenkins-ci.plugins.workflow:workflow-basic-steps:2.3' + jenkinsServer 'org.jenkins-ci.plugins.workflow:workflow-durable-task-step:2.8' + jenkinsServer 'org.jenkins-ci.plugins.workflow:workflow-step-api:2.10' testImplementation 'org.spockframework:spock-core:1.2-groovy-2.4' } diff --git a/src/test/groovy/hudson/plugins/gradle/BuildScanIntegrationTest.groovy b/src/test/groovy/hudson/plugins/gradle/BuildScanIntegrationTest.groovy index c510de1a..a855e067 100644 --- a/src/test/groovy/hudson/plugins/gradle/BuildScanIntegrationTest.groovy +++ b/src/test/groovy/hudson/plugins/gradle/BuildScanIntegrationTest.groovy @@ -4,6 +4,8 @@ import hudson.model.FreeStyleProject import hudson.tasks.BatchFile import hudson.tasks.Maven import hudson.tasks.Shell +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition +import org.jenkinsci.plugins.workflow.job.WorkflowJob import org.jvnet.hudson.test.CreateFileBuilder import org.jvnet.hudson.test.ExtractResourceSCM import org.jvnet.hudson.test.JenkinsRule @@ -55,6 +57,42 @@ class BuildScanIntegrationTest extends AbstractIntegrationTest { new URL(action.scanUrls.get(0)) } + def 'detects build scan in pipeline log'() { + given: + gradleInstallationRule.gradleVersion = '5.1' + gradleInstallationRule.addInstallation() + def pipelineJob = j.createProject(WorkflowJob) + pipelineJob.setDefinition(new CpsFlowDefinition(""" +node { + def gradleHome + stage('Preparation') { + gradleHome = tool '${gradleInstallationRule.gradleVersion}' + } + stage('Build') { + // Run the maven build + if (isUnix()) { + sh 'touch settings.gradle' + sh "'\${gradleHome}/bin/gradle' help --scan" + } else { + bat(/"\${gradleHome}\\bin\\gradle.bat" help --scan/) + } + } + stage('Final') { + findBuildScans() + } +} +""", false)) + + when: + def build = pipelineJob.scheduleBuild2(0).get() + + then: + println JenkinsRule.getLog(build) + def action = build.getAction(BuildScanAction) + action.scanUrls.size() == 1 + new URL(action.scanUrls.get(0)) + } + def 'build scan is discovered from Maven build'() { given: def p = j.createFreeStyleProject() From aa09b3b9f378f22dad1576ef0dc7fbdf65e4225a Mon Sep 17 00:00:00 2001 From: Stefan Wolf Date: Thu, 4 Jul 2019 17:31:43 +0200 Subject: [PATCH 4/4] Fix tests (hopefully) --- .../plugins/gradle/BuildScanIntegrationTest.groovy | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/test/groovy/hudson/plugins/gradle/BuildScanIntegrationTest.groovy b/src/test/groovy/hudson/plugins/gradle/BuildScanIntegrationTest.groovy index a855e067..583bb217 100644 --- a/src/test/groovy/hudson/plugins/gradle/BuildScanIntegrationTest.groovy +++ b/src/test/groovy/hudson/plugins/gradle/BuildScanIntegrationTest.groovy @@ -59,19 +59,17 @@ class BuildScanIntegrationTest extends AbstractIntegrationTest { def 'detects build scan in pipeline log'() { given: - gradleInstallationRule.gradleVersion = '5.1' + gradleInstallationRule.gradleVersion = '5.5' gradleInstallationRule.addInstallation() def pipelineJob = j.createProject(WorkflowJob) pipelineJob.setDefinition(new CpsFlowDefinition(""" node { - def gradleHome - stage('Preparation') { - gradleHome = tool '${gradleInstallationRule.gradleVersion}' - } stage('Build') { // Run the maven build + def gradleHome = tool name: '${gradleInstallationRule.gradleVersion}', type: 'gradle' + writeFile file: 'settings.gradle', text: '' + writeFile file: 'build.gradle', text: "buildScan { termsOfServiceUrl = 'https://gradle.com/terms-of-service'; termsOfServiceAgree = 'yes' }" if (isUnix()) { - sh 'touch settings.gradle' sh "'\${gradleHome}/bin/gradle' help --scan" } else { bat(/"\${gradleHome}\\bin\\gradle.bat" help --scan/)