Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect build scans in pipeline logs #71

Merged
merged 4 commits into from
Jul 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,31 @@ 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'
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: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:2.49'
jenkinsTest 'org.jenkins-ci.main:jenkins-test-harness-tools:2.2'
jenkinsTest 'io.jenkins:configuration-as-code:1.4'

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'
}

Expand Down
4 changes: 3 additions & 1 deletion src/main/java/hudson/plugins/gradle/BuildScanAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ public String getUrlName() {
}

public void addScanUrl(String scanUrl) {
scanUrls.add(scanUrl);
if (!scanUrls.contains(scanUrl)) {
scanUrls.add(scanUrl);
}
}

@Exported
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/hudson/plugins/gradle/BuildScanLogScanner.java
Original file line number Diff line number Diff line change
@@ -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;
}

}
}
68 changes: 68 additions & 0 deletions src/main/java/hudson/plugins/gradle/BuildScanPublisher.java
Original file line number Diff line number Diff line change
@@ -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<Void> {
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<String> lines = logReader.lines()
) {
lines.forEach(scanner::scanLine);
}
return null;
}
}

@Extension
public static final class DescriptorImpl extends StepDescriptor {

@Override
public Set<? extends Class<?>> getRequiredContext() {
return ImmutableSet.of(Run.class);
}

@Nonnull
@Override
public String getDisplayName() {
return "Find published build scans";
}

@Override
public String getFunctionName() {
return "findBuildScans";
}
}
}
27 changes: 4 additions & 23 deletions src/main/java/hudson/plugins/gradle/GradleConsoleAnnotator.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,25 @@
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
* @see <a href="https://github.com/jenkinsci/ant-plugin/blob/master/src/main/java/hudson/tasks/_ant/AntConsoleAnnotator.java">AntConsoleAnnotator</a>
*/
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
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div>
Inspect build log for published Gradle build scans.
The build scans will be shown on the pipeline build page.
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -55,6 +57,40 @@ class BuildScanIntegrationTest extends AbstractIntegrationTest {
new URL(action.scanUrls.get(0))
}

def 'detects build scan in pipeline log'() {
given:
gradleInstallationRule.gradleVersion = '5.5'
gradleInstallationRule.addInstallation()
def pipelineJob = j.createProject(WorkflowJob)
pipelineJob.setDefinition(new CpsFlowDefinition("""
node {
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 "'\${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()
Expand Down