diff --git a/README.md b/README.md index 520af243..7063d5a3 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,12 @@ timestamps { } ``` +You can also send extra parameters to the indexer by: +```Groovy + logstashSend failBuild: true, maxLines: 1000, additionalParams: [param1: "value1", param2: "value2"] +``` +Note: this feature won't work in freestyle builds + License ======= diff --git a/src/main/java/jenkins/plugins/logstash/LogstashWriter.java b/src/main/java/jenkins/plugins/logstash/LogstashWriter.java index aad77e3a..53d6a8fc 100644 --- a/src/main/java/jenkins/plugins/logstash/LogstashWriter.java +++ b/src/main/java/jenkins/plugins/logstash/LogstashWriter.java @@ -24,7 +24,6 @@ package jenkins.plugins.logstash; - import hudson.model.AbstractBuild; import hudson.model.TaskListener; import hudson.model.Run; @@ -44,6 +43,7 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Map; /** * A writer that wraps all Logstash DAOs. Handles error reporting and per build connection state. @@ -69,10 +69,17 @@ public class LogstashWriter implements Serializable { private final String agentName; public LogstashWriter(Run run, OutputStream error, TaskListener listener, Charset charset) { - this(run, error, listener, charset, null, null); + this(run, error, listener, charset, null, null, null); } + public LogstashWriter(Run run, OutputStream error, TaskListener listener, Charset charset, Map additionalParams) { + this(run, error, listener, charset, null, null, additionalParams); + } public LogstashWriter(Run run, OutputStream error, TaskListener listener, Charset charset, String stageName, String agentName) { + this(run, error, listener, charset, stageName, agentName, null); + } + + public LogstashWriter(Run run, OutputStream error, TaskListener listener, Charset charset, String stageName, String agentName, Map additionalParams) { this.errorStream = error != null ? error : System.err; this.stageName = stageName; this.agentName = agentName; @@ -85,10 +92,11 @@ public LogstashWriter(Run run, OutputStream error, TaskListener listener, this.buildData = null; } else { this.jenkinsUrl = getJenkinsUrl(); - this.buildData = getBuildData(); + this.buildData = getBuildData(additionalParams); } } + /** * Gets the charset that Jenkins is using during this build. * @@ -162,11 +170,11 @@ LogstashIndexerDao getIndexerDao() { return LogstashConfiguration.getInstance().getIndexerInstance(); } - BuildData getBuildData() { + BuildData getBuildData(Map additionalParams) { if (build instanceof AbstractBuild) { - return new BuildData((AbstractBuild) build, new Date(), listener); + return new BuildData((AbstractBuild) build, new Date(), listener, additionalParams); } else { - return new BuildData(build, new Date(), listener, stageName, agentName); + return new BuildData(build, new Date(), listener, stageName, agentName, additionalParams); } } diff --git a/src/main/java/jenkins/plugins/logstash/persistence/BuildData.java b/src/main/java/jenkins/plugins/logstash/persistence/BuildData.java index d94b409a..9b23b36e 100644 --- a/src/main/java/jenkins/plugins/logstash/persistence/BuildData.java +++ b/src/main/java/jenkins/plugins/logstash/persistence/BuildData.java @@ -176,15 +176,20 @@ public List getFailedTests() private String rootProjectDisplayName; private int rootBuildNum; private Map buildVariables; + private Map additionalParams; private Set sensitiveBuildVariables; private TestData testResults = null; - - // Freestyle project build + public BuildData(AbstractBuild build, Date currentTime, TaskListener listener) { + this(build, currentTime, listener, Collections.emptyMap()); + } + // Freestyle project build + public BuildData(AbstractBuild build, Date currentTime, TaskListener listener, Map additionalParams) { initData(build, currentTime); // build.getDuration() is always 0 in Notifiers rootProjectName = build.getRootBuild().getProject().getName(); + this.additionalParams = additionalParams; rootFullProjectName = build.getRootBuild().getProject().getFullName(); rootProjectDisplayName = build.getRootBuild().getDisplayName(); rootBuildNum = build.getRootBuild().getNumber(); @@ -211,30 +216,37 @@ public BuildData(AbstractBuild build, Date currentTime, TaskListener liste buildVariables.putAll(build.getEnvironment(listener)); } catch (Exception e) { // no base build env vars to merge - LOGGER.log(WARNING,"Unable update logstash buildVariables with EnvVars from " + build.getDisplayName(),e); + LOGGER.log(WARNING, "Unable update logstash buildVariables with EnvVars from " + build.getDisplayName(), e); } + for (String key : sensitiveBuildVariables) { buildVariables.remove(key); } } + public BuildData(Run build, Date currentTime, TaskListener listener, String stageName, String agentName){ + this(build, currentTime, listener, stageName, agentName, Collections.emptyMap()); + } + // Pipeline project build - public BuildData(Run build, Date currentTime, TaskListener listener, String stageName, String agentName) { + public BuildData(Run build, Date currentTime, TaskListener listener, String stageName, String agentName, + Map additionalParams) { initData(build, currentTime); - + this.additionalParams = additionalParams; this.agentName = agentName; this.stageName = stageName; rootProjectName = projectName; rootFullProjectName = fullProjectName; rootProjectDisplayName = displayName; rootBuildNum = buildNum; + this.buildVariables = new HashMap(); try { - // TODO: sensitive variables are not filtered, c.f. https://stackoverflow.com/questions/30916085 - buildVariables = build.getEnvironment(listener); + // TODO: sensitive variables are not filtered, c.f. + // https://stackoverflow.com/questions/30916085 + buildVariables.putAll(build.getEnvironment(listener)); } catch (IOException | InterruptedException e) { - LOGGER.log(WARNING,"Unable to get environment for " + build.getDisplayName(),e); - buildVariables = new HashMap<>(); + LOGGER.log(WARNING, "Unable to get environment for " + build.getDisplayName(), e); } } @@ -438,6 +450,14 @@ public void setBuildVariables(Map buildVariables) { this.buildVariables = buildVariables; } + public Map getAdditionalParams() { + return additionalParams; + } + + public void setAdditionalParams(Map additionalParams) { + this.additionalParams = additionalParams; + } + public Set getSensitiveBuildVariables() { return sensitiveBuildVariables; } diff --git a/src/main/java/jenkins/plugins/logstash/pipeline/LogstashSendStep.java b/src/main/java/jenkins/plugins/logstash/pipeline/LogstashSendStep.java index df0b88b3..26b7c88d 100644 --- a/src/main/java/jenkins/plugins/logstash/pipeline/LogstashSendStep.java +++ b/src/main/java/jenkins/plugins/logstash/pipeline/LogstashSendStep.java @@ -1,7 +1,9 @@ package jenkins.plugins.logstash.pipeline; import java.io.PrintStream; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import org.jenkinsci.plugins.workflow.steps.Step; @@ -27,12 +29,14 @@ public class LogstashSendStep extends Step private int maxLines; private boolean failBuild; + private Map additionalParams; @DataBoundConstructor - public LogstashSendStep(int maxLines, boolean failBuild) + public LogstashSendStep(int maxLines, boolean failBuild, Map additionalParams) { this.maxLines = maxLines; this.failBuild = failBuild; + this.additionalParams = additionalParams; } public int getMaxLines() @@ -48,7 +52,7 @@ public boolean isFailBuild() @Override public StepExecution start(StepContext context) throws Exception { - return new Execution(context, maxLines, failBuild); + return new Execution(context, maxLines, failBuild, additionalParams); } @SuppressFBWarnings(value="SE_TRANSIENT_FIELD_NOT_RESTORED", justification="Only used when starting.") @@ -59,29 +63,36 @@ private static class Execution extends SynchronousNonBlockingStepExecution private transient final int maxLines; private transient final boolean failBuild; + private transient final Map additionalParams; Execution(StepContext context, int maxLines, boolean failBuild) { super(context); this.maxLines = maxLines; this.failBuild = failBuild; + this.additionalParams = new HashMap(); + } + + Execution(StepContext context, int maxLines, boolean failBuild, Map additionalParams) + { + super(context); + this.maxLines = maxLines; + this.failBuild = failBuild; + this.additionalParams = additionalParams; } @Override - protected Void run() throws Exception - { + protected Void run() throws Exception { Run run = getContext().get(Run.class); TaskListener listener = getContext().get(TaskListener.class); PrintStream errorStream = listener.getLogger(); - LogstashWriter logstash = new LogstashWriter(run, errorStream, listener, run.getCharset()); + LogstashWriter logstash = new LogstashWriter(run, errorStream, listener, run.getCharset(), this.additionalParams); logstash.writeBuildLog(maxLines); - if (failBuild && logstash.isConnectionBroken()) - { + if (failBuild && logstash.isConnectionBroken()) { throw new Exception("Failed to send data to Indexer"); } return null; } - } @Extension diff --git a/src/test/java/jenkins/plugins/logstash/LogstashWriterTest.java b/src/test/java/jenkins/plugins/logstash/LogstashWriterTest.java index e3b94ddd..d46fb003 100644 --- a/src/test/java/jenkins/plugins/logstash/LogstashWriterTest.java +++ b/src/test/java/jenkins/plugins/logstash/LogstashWriterTest.java @@ -21,7 +21,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.GregorianCalendar; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.junit.After; import org.junit.Before; @@ -58,19 +60,19 @@ static LogstashWriter createLogstashWriter(final AbstractBuild testBuild, final String url, final LogstashIndexerDao indexer, final BuildData data) { - return new LogstashWriter(testBuild, error, null, testBuild.getCharset()) { + return new LogstashWriter(testBuild, error, null, testBuild.getCharset(), Collections.emptyMap()) { @Override LogstashIndexerDao getIndexerDao() { return indexer; } @Override - BuildData getBuildData() { + BuildData getBuildData(Map additionalParams) { assertNotNull("BuildData should never be requested for missing dao.", this.getDao()); // For testing, providing null data means use the actual method if (data == null) { - return super.getBuildData(); + return super.getBuildData(additionalParams); } else { return data; } diff --git a/src/test/java/jenkins/plugins/logstash/PipelineTest.java b/src/test/java/jenkins/plugins/logstash/PipelineTest.java index 98820341..082d282d 100644 --- a/src/test/java/jenkins/plugins/logstash/PipelineTest.java +++ b/src/test/java/jenkins/plugins/logstash/PipelineTest.java @@ -4,7 +4,9 @@ import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.MatcherAssert.assertThat; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.job.WorkflowJob; @@ -128,4 +130,27 @@ public void logstashSend() throws Exception JSONObject data = firstLine.getJSONObject("data"); assertThat(data.getString("result"),equalTo("SUCCESS")); } -} + + @Test + public void logstashSendWithAdditionalParams() throws Exception + { + WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); + HashMap someMap = new HashMap(); + someMap.put("param1", "value1"); + someMap.put("param2", "value2"); + p.setDefinition(new CpsFlowDefinition( + "echo 'Message'\n" + + "currentBuild.result = 'SUCCESS'\n" + + "def someMap = [param1:'value1', param2:'value2']\n" + + "logstashSend failBuild: true, maxLines: 5, additionalParams: someMap" + , true)); + j.assertBuildStatusSuccess(p.scheduleBuild2(0).get()); + List dataLines = memoryDao.getOutput(); + assertThat(dataLines.size(), equalTo(1)); + JSONObject firstLine = dataLines.get(0); + JSONObject data = firstLine.getJSONObject("data"); + assertThat(data.getJSONObject("additionalParams").getString("param1"), equalTo("value1")); + assertThat(data.getJSONObject("additionalParams").getString("param2"), equalTo("value2")); + assertThat(data.getString("result"),equalTo("SUCCESS")); + } +} \ No newline at end of file diff --git a/src/test/java/jenkins/plugins/logstash/persistence/BuildDataTest.java b/src/test/java/jenkins/plugins/logstash/persistence/BuildDataTest.java index 8d55d074..d660d91c 100644 --- a/src/test/java/jenkins/plugins/logstash/persistence/BuildDataTest.java +++ b/src/test/java/jenkins/plugins/logstash/persistence/BuildDataTest.java @@ -2,6 +2,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -51,13 +52,12 @@ @PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.crypto.*", "javax.xml.*", "org.xml.*"}) @PrepareForTest(LogstashConfiguration.class) public class BuildDataTest { - static final String FULL_STRING = "{\"id\":\"TEST_JOB_123\",\"result\":\"SUCCESS\",\"fullProjectName\":\"parent/BuildDataTest\"," + "\"projectName\":\"BuildDataTest\",\"displayName\":\"BuildData Test\",\"fullDisplayName\":\"BuildData Test #123456\"," + "\"description\":\"Mock project for testing BuildData\",\"url\":\"http://localhost:8080/jenkins/jobs/PROJECT_NAME/123\"," + "\"buildHost\":\"master\",\"buildLabel\":\"master\",\"buildNum\":123456,\"buildDuration\":60," + "\"rootProjectName\":\"RootBuildDataTest\",\"rootFullProjectName\":\"parent/RootBuildDataTest\"," - + "\"rootProjectDisplayName\":\"Root BuildData Test\",\"rootBuildNum\":456,\"buildVariables\":{}," + + "\"rootProjectDisplayName\":\"Root BuildData Test\",\"rootBuildNum\":456,\"buildVariables\":{}, \"additionalParams\":{}," + "\"sensitiveBuildVariables\":[],\"testResults\":{\"totalCount\":0,\"skipCount\":0,\"failCount\":0, \"passCount\":0," + "\"failedTests\":[], \"failedTestsWithErrorDetail\":[]}}"; @@ -373,7 +373,7 @@ public void toJsonSuccess() throws Exception // Verify results JSONAssert.assertEquals("Results don't match", JSONObject.fromObject(FULL_STRING), result); - verifyMocks(); + verifyMocks(); verifyTestResultActions(); } @@ -404,4 +404,30 @@ public void rootProjectFullName() throws Exception verifyMocks(); verifyTestResultActions(); } + + @Test + public void additionalParamsAreAddedCorrectly() throws Exception + { + when(mockBuild.getEnvironments()).thenReturn(new EnvironmentList(Arrays.asList(mockEnvironment))); + when(mockBuild.getBuildVariables()).thenReturn(new HashMap()); + when(mockComputer.getNode()).thenReturn(mockNode); + final String buildVarKey = "BuildVarKey"; + final String buildVarVal = "BuildVarVal"; + when(mockBuild.getEnvironment(mockListener)).thenReturn(new EnvVars(buildVarKey, buildVarVal)); + when(mockBuild.getSensitiveBuildVariables()).thenReturn(new HashSet<>()); + doNothing().when(mockEnvironment).buildEnvVars(any()); + + HashMap additionalParams = new HashMap(); + additionalParams.put("param1", "value1"); + additionalParams.put("param2", "value2"); + + BuildData buildData = new BuildData(mockBuild, mockDate, mockListener, additionalParams); + + Assert.assertEquals("Missing additionalParam: param1", "value1", buildData.getAdditionalParams().get("param1")); + Assert.assertEquals("Missing additionalParam: param1", "value2", buildData.getAdditionalParams().get("param2")); + + verify(mockEnvironment).buildEnvVars(any()); + verifyMocks(); + verifyTestResultActions(); + } }