diff --git a/src/main/java/io/jenkins/plugins/launchable/GraphListenerImpl.java b/src/main/java/io/jenkins/plugins/launchable/GraphListenerImpl.java deleted file mode 100644 index d85e00b..0000000 --- a/src/main/java/io/jenkins/plugins/launchable/GraphListenerImpl.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.jenkins.plugins.launchable; - -import hudson.Extension; -import hudson.model.Queue; -import org.jenkinsci.plugins.workflow.flow.GraphListener; -import org.jenkinsci.plugins.workflow.graph.FlowNode; -import org.jenkinsci.plugins.workflow.job.WorkflowRun; - -import javax.inject.Inject; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -@Extension -public class GraphListenerImpl implements GraphListener { - @Inject - Ingester ingester; - - @Override - public void onNewHead(FlowNode node) { - try { - if (node.getDisplayFunctionName().equals("junit")) {// TODO: probably not the most robust check - Queue.Executable owner = node.getExecution().getOwner().getExecutable(); - if (owner instanceof WorkflowRun) {// another defensive check just to be safe - WorkflowRun run = (WorkflowRun) owner; - ingester.slurp(run.getRootDir(), new PropsBuilder<>(run)); - } - } - } catch (IOException e) { - // Priority #1: Do no harm to people's build - LOGGER.log(Level.WARNING, "Failed to send JUnit result to Launchable", e); - } - } - - private static final Logger LOGGER = Logger.getLogger("io.jenkins.plugins.launchable.GraphListenerImpl"); -} diff --git a/src/main/java/io/jenkins/plugins/launchable/Ingester.java b/src/main/java/io/jenkins/plugins/launchable/Ingester.java index a48eaa3..15fd609 100644 --- a/src/main/java/io/jenkins/plugins/launchable/Ingester.java +++ b/src/main/java/io/jenkins/plugins/launchable/Ingester.java @@ -4,6 +4,7 @@ import hudson.tasks.junit.TestResult; import hudson.util.Secret; import jenkins.model.GlobalConfiguration; +import jenkins.util.Timer; import net.sf.json.JSONObject; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; @@ -17,6 +18,7 @@ import java.util.logging.Level; import java.util.logging.Logger; +import static java.util.logging.Level.*; import static org.apache.http.entity.ContentType.APPLICATION_JSON; import static org.apache.http.entity.ContentType.APPLICATION_OCTET_STREAM; @@ -47,44 +49,45 @@ public boolean configure(StaplerRequest req, JSONObject json) throws FormExcepti * @param properties Additional contextual data to submit along with the test results. */ /*package*/ void slurp(File dir, PropsBuilder properties) throws IOException { - try { - File report = new File(dir, "junitResult.xml"); - if (!report.exists()) return; // be defensive just in case + File report = new File(dir, "junitResult.xml"); + if (!report.exists()) return; // be defensive just in case - if (apiKey==null) return; // not yet configured + Timer.get().execute(() -> {// don't slow down people's builds and risk getting kicked out + try { + if (apiKey==null) return; // not yet configured + OrganizationWorkspace orgWs = OrganizationWorkspace.fromApiKey(apiKey.getPlainText()); - OrganizationWorkspace orgWs = OrganizationWorkspace.fromApiKey(apiKey.getPlainText()); + // attempted to use JDK HttpRequest, but gave up due to the lack of multipart support + // TODO: how do I obtain a properly configured HttpClient for the proxy setting in Jenkins? + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + String endpoint = System.getProperty("INSIGHT_UPLOAD_URL") ; - // attempted to use JDK HttpRequest, but gave up due to the lack of multipart support - // TODO: how do I obtain a properly configured HttpClient for the proxy setting in Jenkins? - try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - String endpoint = System.getProperty("INSIGHT_UPLOAD_URL") ; - - if (endpoint==null) { - endpoint = DEFAULT_UPLOAD_URL; - } - HttpPost hc = new HttpPost(String.format("%s/intake/organizations/%s/workspaces/%s/events/jenkins", endpoint, orgWs.getOrganization(), orgWs.getWorkspace())); + if (endpoint==null) { + endpoint = DEFAULT_UPLOAD_URL; + } + HttpPost hc = new HttpPost(String.format("%s/intake/organizations/%s/workspaces/%s/events/jenkins", endpoint, orgWs.getOrganization(), orgWs.getWorkspace())); - MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - builder.addTextBody("metadata", properties.build().toString(), APPLICATION_JSON); - builder.addPart("file", new GzipFileMimePart(report, APPLICATION_OCTET_STREAM, "junitResult.xml.gz")); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addTextBody("metadata", properties.build().toString(), APPLICATION_JSON); + builder.addPart("file", new GzipFileMimePart(report, APPLICATION_OCTET_STREAM, "junitResult.xml.gz")); - hc.setEntity(builder.build()); - hc.addHeader("Authorization", "Bearer " + apiKey.getPlainText()); + hc.setEntity(builder.build()); + hc.addHeader("Authorization", "Bearer " + apiKey.getPlainText()); - try (CloseableHttpResponse response = httpClient.execute(hc)) { - if (response.getStatusLine().getStatusCode() >= 300) { - // treat redirect as an error, for the time being. we submit a big payload, so we don't want - // to be forced to repeat the payload after we send the whole thing once. - LOGGER.log(Level.WARNING, "Failed to submit test results: {0}", response.getStatusLine()); + try (CloseableHttpResponse response = httpClient.execute(hc)) { + if (response.getStatusLine().getStatusCode() >= 300) { + // treat redirect as an error, for the time being. we submit a big payload, so we don't want + // to be forced to repeat the payload after we send the whole thing once. + LOGGER.log(WARNING, "Failed to submit test results: {0}", response.getStatusLine()); + } } } + } catch (Exception e) { + // don't let our bug get in the way of orderly execution of jobs, as that'd be the fasest way to + // get kicked out of installations. + LOGGER.log(WARNING, "Failed to submit test results", e); } - } catch (Exception e) { - // don't let our bug get in the way of orderly execution of jobs, as that'd be the fasest way to - // get kicked out of installations. - LOGGER.log(Level.WARNING, "Failed to submit test results", e); - } + }); } private static final Logger LOGGER = Logger.getLogger(Ingester.class.getName()); diff --git a/src/main/java/io/jenkins/plugins/launchable/RunListenerImpl.java b/src/main/java/io/jenkins/plugins/launchable/RunListenerImpl.java new file mode 100644 index 0000000..bcf8cd7 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/launchable/RunListenerImpl.java @@ -0,0 +1,40 @@ +package io.jenkins.plugins.launchable; + +import hudson.Extension; +import hudson.model.listeners.RunListener; +import org.jenkinsci.plugins.workflow.flow.GraphListener; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; + +import javax.inject.Inject; +import java.util.logging.Logger; + +import static java.util.logging.Level.*; + +/** + * Records test results reported into {@link WorkflowRun}. + * + *

+ * Our earlier attempt to implement {@link GraphListener} backfired, as when 'junitReport' step was called + * multiple times, we end up reporting all test results accumulated up to that point, resulting in duplicate + * data reporting. + * + *

+ * This approach defers the data ingestion all the way down to the end of a workflow run. + */ +@Extension +public class RunListenerImpl extends RunListener { + @Inject + Ingester ingester; + + @Override + public void onFinalized(WorkflowRun run) { + try { + ingester.slurp(run.getRootDir(), new PropsBuilder<>(run)); + } catch (Exception e) { + // Priority #1: Do no harm to people's build + LOGGER.log(WARNING, "Failed to send JUnit result to Launchable", e); + } + } + + private static final Logger LOGGER = Logger.getLogger("io.jenkins.plugins.launchable.RunListenerImpl"); +}