diff --git a/pom.xml b/pom.xml index 28b06c86..beac531d 100644 --- a/pom.xml +++ b/pom.xml @@ -121,6 +121,19 @@ workflow-step-api true + + org.jenkins-ci.plugins + pipeline-stage-step + 2.3 + + + org.jenkins-ci.plugins.workflow + workflow-durable-task-step + + + org.jenkins-ci.plugins.workflow + workflow-job + io.jenkins configuration-as-code @@ -140,19 +153,19 @@ org.mockito mockito-core - 2.13.0 + 2.25.0 test org.powermock powermock-api-mockito2 - 2.0.0-beta.5 + 2.0.2 test org.powermock powermock-module-junit4 - 2.0.0-beta.5 + 2.0.2 test @@ -188,11 +201,6 @@ workflow-cps test - - org.jenkins-ci.plugins.workflow - workflow-job - test - org.jenkins-ci.plugins.workflow workflow-basic-steps @@ -203,11 +211,6 @@ workflow-scm-step test - - org.jenkins-ci.plugins.workflow - workflow-durable-task-step - test - diff --git a/src/main/java/jenkins/plugins/logstash/LogstashConsoleLogFilter.java b/src/main/java/jenkins/plugins/logstash/LogstashConsoleLogFilter.java index d5995163..9eb7ec0a 100644 --- a/src/main/java/jenkins/plugins/logstash/LogstashConsoleLogFilter.java +++ b/src/main/java/jenkins/plugins/logstash/LogstashConsoleLogFilter.java @@ -18,13 +18,8 @@ public class LogstashConsoleLogFilter extends ConsoleLogFilter implements Serial private static final Logger LOGGER = Logger.getLogger(LogstashConsoleLogFilter.class.getName()); - private transient Run run; public LogstashConsoleLogFilter() {} - public LogstashConsoleLogFilter(Run run) - { - this.run = run; - } private static final long serialVersionUID = 1L; @Override @@ -49,15 +44,7 @@ public OutputStream decorateLogger(Run build, OutputStream logger) throws IOExce return logger; } } - if (run != null) - { - LogstashWriter logstash = getLogStashWriter(run, logger); - return new LogstashOutputStream(logger, logstash); - } - else - { - return logger; - } + return logger; } LogstashWriter getLogStashWriter(Run build, OutputStream errorStream) diff --git a/src/main/java/jenkins/plugins/logstash/LogstashWriter.java b/src/main/java/jenkins/plugins/logstash/LogstashWriter.java index 2523814a..aad77e3a 100644 --- a/src/main/java/jenkins/plugins/logstash/LogstashWriter.java +++ b/src/main/java/jenkins/plugins/logstash/LogstashWriter.java @@ -35,8 +35,11 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import java.io.IOException; import java.io.OutputStream; +import java.io.Serializable; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Date; @@ -51,22 +54,31 @@ * @author Liam Newman * @since 1.0.5 */ -public class LogstashWriter { +@SuppressFBWarnings(value="SE_NO_SERIALVERSIONID") +public class LogstashWriter implements Serializable { private final OutputStream errorStream; - private final Run build; + private final transient Run build; private final TaskListener listener; private final BuildData buildData; private final String jenkinsUrl; private final LogstashIndexerDao dao; private boolean connectionBroken; - private final Charset charset; + private final String charset; + private final String stageName; + private final String agentName; public LogstashWriter(Run run, OutputStream error, TaskListener listener, Charset charset) { + this(run, error, listener, charset, null, null); + } + + public LogstashWriter(Run run, OutputStream error, TaskListener listener, Charset charset, String stageName, String agentName) { this.errorStream = error != null ? error : System.err; + this.stageName = stageName; + this.agentName = agentName; this.build = run; this.listener = listener; - this.charset = charset; + this.charset = charset.toString(); this.dao = this.getDaoOrNull(); if (this.dao == null) { this.jenkinsUrl = ""; @@ -82,7 +94,7 @@ public LogstashWriter(Run run, OutputStream error, TaskListener listener, * * @return the charset */ - public Charset getCharset() + public String getCharset() { return charset; } @@ -154,12 +166,12 @@ BuildData getBuildData() { if (build instanceof AbstractBuild) { return new BuildData((AbstractBuild) build, new Date(), listener); } else { - return new BuildData(build, new Date(), listener); + return new BuildData(build, new Date(), listener, stageName, agentName); } } String getJenkinsUrl() { - return Jenkins.getInstance().getRootUrl(); + return Jenkins.get().getRootUrl(); } /** @@ -207,8 +219,10 @@ private LogstashIndexerDao getDaoOrNull() { private void logErrorMessage(String msg) { try { connectionBroken = true; - errorStream.write(msg.getBytes(charset)); - errorStream.flush(); + if (errorStream != null) { + errorStream.write(msg.getBytes(charset)); + errorStream.flush(); + } } catch (IOException ex) { // This should never happen, but if it does we just have to let it go. ex.printStackTrace(); diff --git a/src/main/java/jenkins/plugins/logstash/configuration/ElasticSearch.java b/src/main/java/jenkins/plugins/logstash/configuration/ElasticSearch.java index efac5c11..ba366344 100644 --- a/src/main/java/jenkins/plugins/logstash/configuration/ElasticSearch.java +++ b/src/main/java/jenkins/plugins/logstash/configuration/ElasticSearch.java @@ -8,6 +8,8 @@ import javax.activation.MimeType; import javax.activation.MimeTypeParseException; +import javax.annotation.Nonnull; + import java.security.cert.CertificateException; import org.apache.commons.lang.StringUtils; @@ -132,7 +134,7 @@ public boolean equals(Object obj) if (getClass() != obj.getClass()) return false; ElasticSearch other = (ElasticSearch) obj; - if (!Secret.toString(password).equals(other.getPassword().getPlainText())) + if (!Secret.toString(password).equals(Secret.toString(other.getPassword()))) { return false; } @@ -188,35 +190,26 @@ public ElasticSearchDao createIndexerInstance() ElasticSearchDao esDao = new ElasticSearchDao(getUri(), username, Secret.toString(password)); esDao.setMimeType(getMimeType()); - try { - esDao.setCustomKeyStore(getCustomKeyStore()); - } catch (KeyStoreException | CertificateException | - NoSuchAlgorithmException | KeyManagementException | IOException e) { - LOGGER.log(Level.WARNING, e.getMessage(), e); - } - return esDao; - } - - private KeyStore getCustomKeyStore() { - KeyStore customKeyStore = null; - - // Fetch custom alias+certificate as a keystore (if present) if (!StringUtils.isBlank(customServerCertificateId)) { - StandardCertificateCredentials certificateCredentials = getCredentials(customServerCertificateId); - if (certificateCredentials != null) { - // Fetch keystore containing custom certificate - customKeyStore = certificateCredentials.getKeyStore(); + try { + StandardCertificateCredentials certificateCredentials = getCredentials(customServerCertificateId); + if (certificateCredentials != null) { + esDao.setCustomKeyStore(certificateCredentials.getKeyStore(), + Secret.toString(certificateCredentials.getPassword())); + } + } catch (KeyStoreException | CertificateException | + NoSuchAlgorithmException | IOException e) { + LOGGER.log(Level.WARNING, e.getMessage(), e); } } - - return customKeyStore; + return esDao; } private StandardCertificateCredentials getCredentials(String credentials) { return (StandardCertificateCredentials) CredentialsMatchers.firstOrNull( - CredentialsProvider.lookupCredentials(StandardCredentials.class, - Jenkins.getInstance(), ACL.SYSTEM, Collections.emptyList()), + CredentialsProvider.lookupCredentials(StandardCertificateCredentials.class, + Jenkins.get(), ACL.SYSTEM, Collections.emptyList()), CredentialsMatchers.withId(credentials) ); } @@ -247,7 +240,7 @@ public ListBoxModel doFillCustomServerCertificateIdItems( CredentialsMatchers.instanceOf(StandardCertificateCredentials.class) ), CredentialsProvider.lookupCredentials(StandardCredentials.class, - Jenkins.getInstance(), + Jenkins.get(), ACL.SYSTEM, Collections.emptyList() ) diff --git a/src/main/java/jenkins/plugins/logstash/configuration/RabbitMq.java b/src/main/java/jenkins/plugins/logstash/configuration/RabbitMq.java index 7ff98667..1047547e 100644 --- a/src/main/java/jenkins/plugins/logstash/configuration/RabbitMq.java +++ b/src/main/java/jenkins/plugins/logstash/configuration/RabbitMq.java @@ -125,7 +125,7 @@ public boolean equals(Object obj) if (getClass() != obj.getClass()) return false; RabbitMq other = (RabbitMq) obj; - if (!Secret.toString(password).equals(other.getPassword().getPlainText())) + if (!Secret.toString(password).equals(Secret.toString(other.getPassword()))) { return false; } diff --git a/src/main/java/jenkins/plugins/logstash/configuration/Redis.java b/src/main/java/jenkins/plugins/logstash/configuration/Redis.java index 4242ca42..373743be 100644 --- a/src/main/java/jenkins/plugins/logstash/configuration/Redis.java +++ b/src/main/java/jenkins/plugins/logstash/configuration/Redis.java @@ -55,7 +55,7 @@ public boolean equals(Object obj) if (getClass() != obj.getClass()) return false; Redis other = (Redis) obj; - if (!Secret.toString(password).equals(other.getPassword().getPlainText())) + if (!Secret.toString(password).equals(Secret.toString(other.getPassword()))) { return false; } diff --git a/src/main/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDao.java b/src/main/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDao.java index 0fea9d4d..47a16241 100644 --- a/src/main/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDao.java +++ b/src/main/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDao.java @@ -24,6 +24,7 @@ package jenkins.plugins.logstash.persistence; +import java.io.Serializable; import java.util.Calendar; import java.util.List; @@ -36,7 +37,7 @@ * @author Rusty Gerard * @since 1.0.0 */ -public abstract class AbstractLogstashIndexerDao implements LogstashIndexerDao { +public abstract class AbstractLogstashIndexerDao implements LogstashIndexerDao, Serializable { @Override public JSONObject buildPayload(BuildData buildData, String jenkinsUrl, List logLines) { diff --git a/src/main/java/jenkins/plugins/logstash/persistence/BuildData.java b/src/main/java/jenkins/plugins/logstash/persistence/BuildData.java index 4379a4de..d94b409a 100644 --- a/src/main/java/jenkins/plugins/logstash/persistence/BuildData.java +++ b/src/main/java/jenkins/plugins/logstash/persistence/BuildData.java @@ -48,6 +48,7 @@ import static java.util.logging.Level.WARNING; import java.io.IOException; +import java.io.Serializable; import java.lang.invoke.MethodHandles; import net.sf.json.JSONObject; @@ -56,21 +57,25 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /** * POJO for mapping build info to JSON. * * @author Rusty Gerard * @since 1.0.0 */ -public class BuildData { +@SuppressFBWarnings(value="SE_NO_SERIALVERSIONID") +public class BuildData implements Serializable { + // ISO 8601 date format private final static Logger LOGGER = Logger.getLogger(MethodHandles.lookup().lookupClass().getCanonicalName()); - public static class TestData { + public static class TestData implements Serializable { private final int totalCount, skipCount, failCount, passCount; private final List failedTestsWithErrorDetail; private final List failedTests; - public static class FailedTest { + public static class FailedTest implements Serializable { private final String fullName, errorDetails; public FailedTest(String fullName, String errorDetails) { super(); @@ -160,6 +165,8 @@ public List getFailedTests() private String url; private String buildHost; private String buildLabel; + private String stageName; + private String agentName; private int buildNum; private long buildDuration; private transient String timestamp; // This belongs in the root object @@ -212,9 +219,11 @@ public BuildData(AbstractBuild build, Date currentTime, TaskListener liste } // Pipeline project build - public BuildData(Run build, Date currentTime, TaskListener listener) { + public BuildData(Run build, Date currentTime, TaskListener listener, String stageName, String agentName) { initData(build, currentTime); + this.agentName = agentName; + this.stageName = stageName; rootProjectName = projectName; rootFullProjectName = fullProjectName; rootProjectDisplayName = displayName; @@ -262,14 +271,15 @@ private void initData(Run build, Date currentTime) { public void updateResult() { - if (result == null && build.getResult() != null) - { - Result result = build.getResult(); - this.result = result == null ? null : result.toString(); - } - Action testResultAction = build.getAction(AbstractTestResultAction.class); - if (testResults == null && testResultAction != null) { - testResults = new TestData(testResultAction); + if (build != null) { + if (result == null && build.getResult() != null) { + Result result = build.getResult(); + this.result = result == null ? null : result.toString(); + } + Action testResultAction = build.getAction(AbstractTestResultAction.class); + if (testResults == null && testResultAction != null) { + testResults = new TestData(testResultAction); + } } } @@ -443,4 +453,20 @@ public TestData getTestResults() { public void setTestResults(TestData testResults) { this.testResults = testResults; } + + public String getStageName() { + return stageName; + } + + public void setStageName(String stageName) { + this.stageName = stageName; + } + + public String getAgentName() { + return agentName; + } + + public void setAgentName(String agentName) { + this.agentName = agentName; + } } diff --git a/src/main/java/jenkins/plugins/logstash/persistence/ElasticSearchDao.java b/src/main/java/jenkins/plugins/logstash/persistence/ElasticSearchDao.java index 96feb347..4ceafb10 100644 --- a/src/main/java/jenkins/plugins/logstash/persistence/ElasticSearchDao.java +++ b/src/main/java/jenkins/plugins/logstash/persistence/ElasticSearchDao.java @@ -36,6 +36,7 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.CloseableHttpClient; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; @@ -52,6 +53,7 @@ import com.google.common.collect.Range; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import jenkins.plugins.logstash.utils.SSLHelper; @@ -61,9 +63,10 @@ * @author Liam Newman * @since 1.0.4 */ +@SuppressFBWarnings(value="SE_NO_SERIALVERSIONID") public class ElasticSearchDao extends AbstractLogstashIndexerDao { - private final HttpClientBuilder clientBuilder; + private transient HttpClientBuilder clientBuilder; private final URI uri; private final String auth; private final Range successCodes = closedOpen(200,300); @@ -71,7 +74,8 @@ public class ElasticSearchDao extends AbstractLogstashIndexerDao { private String username; private String password; private String mimeType; - private KeyStore customKeyStore; + private byte[] keystoreBytes; + private String keyStorePassword; //primary constructor used by indexer factory public ElasticSearchDao(URI uri, String username, String password) { @@ -106,7 +110,36 @@ public ElasticSearchDao(URI uri, String username, String password) { auth = null; } - clientBuilder = factory == null ? HttpClientBuilder.create() : factory; + clientBuilder = factory; + } + + private byte[] getKeystoreBytes() { + return keystoreBytes; + } + + private String getKeyStorePassword() { + return keyStorePassword; + } + + private synchronized HttpClientBuilder getClientBuilder() throws IOException { + if (clientBuilder == null) { + clientBuilder = HttpClientBuilder.create(); + if (getKeystoreBytes() != null) { + KeyStore trustStore; + try { + trustStore = KeyStore.getInstance("PKCS12"); + String pwd = getKeyStorePassword(); + if (pwd == null) { + pwd = ""; + } + trustStore.load(new ByteArrayInputStream(getKeystoreBytes()), pwd.toCharArray()); + SSLHelper.setClientBuilderSSLContext(clientBuilder, trustStore); + } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | KeyManagementException e) { + throw new IOException(e); + } + } + } + return clientBuilder; } @@ -152,19 +185,18 @@ public void setMimeType(String mimeType) { this.mimeType = mimeType; } - public KeyStore getCustomKeyStore() { - return this.customKeyStore; - } - String getAuth() { return auth; } - public void setCustomKeyStore(KeyStore customKeyStore) throws - CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException { - SSLHelper.setClientBuilderSSLContext(this.clientBuilder, customKeyStore); - this.customKeyStore = customKeyStore; + public void setCustomKeyStore(KeyStore customKeyStore, String keyStorePassword) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { + if (customKeyStore != null) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + customKeyStore.store(bos, keyStorePassword.toCharArray()); + keystoreBytes = bos.toByteArray(); + this.keyStorePassword = keyStorePassword; + } } HttpPost getHttpPost(String data) { @@ -185,7 +217,7 @@ HttpPost getHttpPost(String data) { public void push(String data) throws IOException { HttpPost post = getHttpPost(data); - try (CloseableHttpClient httpClient = clientBuilder.build(); CloseableHttpResponse response = httpClient.execute(post)) { + try (CloseableHttpClient httpClient = getClientBuilder().build(); CloseableHttpResponse response = httpClient.execute(post)) { if (!successCodes.contains(response.getStatusLine().getStatusCode())) { throw new IOException(this.getErrorMessage(response)); } diff --git a/src/main/java/jenkins/plugins/logstash/persistence/HostBasedLogstashIndexerDao.java b/src/main/java/jenkins/plugins/logstash/persistence/HostBasedLogstashIndexerDao.java index 0ede70f6..41cc0c3d 100644 --- a/src/main/java/jenkins/plugins/logstash/persistence/HostBasedLogstashIndexerDao.java +++ b/src/main/java/jenkins/plugins/logstash/persistence/HostBasedLogstashIndexerDao.java @@ -31,6 +31,7 @@ * @since 2.0.0 */ public abstract class HostBasedLogstashIndexerDao extends AbstractLogstashIndexerDao { + private final String host; private final int port; diff --git a/src/main/java/jenkins/plugins/logstash/persistence/RabbitMqDao.java b/src/main/java/jenkins/plugins/logstash/persistence/RabbitMqDao.java index 552d3668..fbbda53c 100644 --- a/src/main/java/jenkins/plugins/logstash/persistence/RabbitMqDao.java +++ b/src/main/java/jenkins/plugins/logstash/persistence/RabbitMqDao.java @@ -33,6 +33,8 @@ import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /** * RabbitMQ Data Access Object. * @@ -42,13 +44,15 @@ * @author Rusty Gerard * @since 1.0.0 */ +@SuppressFBWarnings(value="SE_NO_SERIALVERSIONID") public class RabbitMqDao extends HostBasedLogstashIndexerDao { - private final ConnectionFactory pool; + + private transient ConnectionFactory pool; private final String queue; private final String username; private final String password; - private final Charset charset; + private final String charset; private final String virtualHost; @@ -68,7 +72,7 @@ public RabbitMqDao(String host, int port, String key, String username, String pa this.queue = queue; this.username = username; this.password = password; - this.charset = charset; + this.charset = charset.toString(); this.virtualHost = vhost; if (StringUtils.isBlank(queue)) { @@ -78,17 +82,31 @@ public RabbitMqDao(String host, int port, String key, String username, String pa // The ConnectionFactory must be a singleton // We assume this is used as a singleton as well // Calling this method means the configuration has changed and the pool must be re-initialized - pool = factory == null ? new ConnectionFactory() : factory; - pool.setHost(host); - pool.setPort(port); - if (virtualHost != null) - { - pool.setVirtualHost(virtualHost); + pool = factory; + initPool(); + } + + private synchronized ConnectionFactory getPool() { + if (pool == null) { + pool = new ConnectionFactory(); + initPool(); } + return pool; + } - if (!StringUtils.isBlank(username) && !StringUtils.isBlank(password)) { - pool.setPassword(password); - pool.setUsername(username); + private void initPool() { + if (pool != null) + { + pool.setHost(getHost()); + pool.setPort(getPort()); + if (virtualHost != null) { + pool.setVirtualHost(virtualHost); + } + + if (!StringUtils.isBlank(username) && !StringUtils.isBlank(password)) { + pool.setPassword(password); + pool.setUsername(username); + } } } @@ -126,7 +144,7 @@ public void push(String data) throws IOException { Connection connection = null; Channel channel = null; try { - connection = pool.newConnection(); + connection = getPool().newConnection(); channel = connection.createChannel(); // Ensure the queue exists diff --git a/src/main/java/jenkins/plugins/logstash/persistence/RedisDao.java b/src/main/java/jenkins/plugins/logstash/persistence/RedisDao.java index 65f964f9..63848b12 100644 --- a/src/main/java/jenkins/plugins/logstash/persistence/RedisDao.java +++ b/src/main/java/jenkins/plugins/logstash/persistence/RedisDao.java @@ -28,6 +28,7 @@ import org.apache.commons.lang.StringUtils; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; @@ -40,8 +41,10 @@ * @author Rusty Gerard * @since 1.0.0 */ +@SuppressFBWarnings(value="SE_NO_SERIALVERSIONID") public class RedisDao extends HostBasedLogstashIndexerDao { - private final JedisPool pool; + + private transient JedisPool pool; private final String password; private final String key; @@ -68,7 +71,13 @@ public RedisDao(String host, int port, String key, String password) { // The JedisPool must be a singleton // We assume this is used as a singleton as well - pool = factory == null ? new JedisPool(new JedisPoolConfig(), host, port) : factory; + pool = factory; + } + + private synchronized void getJedisPool() { + if (pool == null) { + pool = new JedisPool(new JedisPoolConfig(), getHost(), getPort()); + } } public String getPassword() @@ -86,6 +95,7 @@ public void push(String data) throws IOException { Jedis jedis = null; boolean connectionBroken = false; try { + getJedisPool(); jedis = pool.getResource(); if (!StringUtils.isBlank(password)) { jedis.auth(password); diff --git a/src/main/java/jenkins/plugins/logstash/persistence/SyslogDao.java b/src/main/java/jenkins/plugins/logstash/persistence/SyslogDao.java index 86f96d41..755ace6a 100644 --- a/src/main/java/jenkins/plugins/logstash/persistence/SyslogDao.java +++ b/src/main/java/jenkins/plugins/logstash/persistence/SyslogDao.java @@ -7,13 +7,16 @@ import com.cloudbees.syslog.Severity; import com.cloudbees.syslog.sender.UdpSyslogMessageSender; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /* * TODO: add support for TcpSyslogMessageSender */ +@SuppressFBWarnings(value="SE_NO_SERIALVERSIONID") public class SyslogDao extends HostBasedLogstashIndexerDao { private MessageFormat messageFormat = MessageFormat.RFC_3164; - private final UdpSyslogMessageSender messageSender; + private transient UdpSyslogMessageSender messageSender; public SyslogDao(String host, int port) { this(null, host, port); @@ -27,7 +30,7 @@ public SyslogDao(String host, int port) { public SyslogDao(UdpSyslogMessageSender udpSyslogMessageSender, String host, int port) { super(host, port); - messageSender = udpSyslogMessageSender == null ? new UdpSyslogMessageSender() : udpSyslogMessageSender; + messageSender = udpSyslogMessageSender; } public void setMessageFormat(MessageFormat format) { @@ -38,12 +41,19 @@ public MessageFormat getMessageFormat() { return messageFormat; } + private synchronized void getMessageSender() { + if (messageSender == null) { + messageSender = new UdpSyslogMessageSender(); + } + } + @Override public void push(String data) throws IOException { // Making the JSON document compliant to Common Event Expression (CEE) // Ref: http://www.rsyslog.com/json-elasticsearch/ data = " @cee: " + data; // SYSLOG Configuration + getMessageSender(); messageSender.setDefaultMessageHostname(getHost()); messageSender.setDefaultAppName("jenkins:"); messageSender.setDefaultFacility(Facility.USER); diff --git a/src/main/java/jenkins/plugins/logstash/pipeline/GlobalDecorator.java b/src/main/java/jenkins/plugins/logstash/pipeline/GlobalDecorator.java new file mode 100644 index 00000000..a8509f1c --- /dev/null +++ b/src/main/java/jenkins/plugins/logstash/pipeline/GlobalDecorator.java @@ -0,0 +1,65 @@ +package jenkins.plugins.logstash.pipeline; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.jenkinsci.plugins.workflow.log.TaskListenerDecorator; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import hudson.Extension; +import hudson.model.Queue; +import hudson.model.Run; +import jenkins.plugins.logstash.LogstashConfiguration; +import jenkins.plugins.logstash.LogstashOutputStream; +import jenkins.plugins.logstash.LogstashWriter; + +@SuppressFBWarnings(value="SE_NO_SERIALVERSIONID") +public class GlobalDecorator extends TaskListenerDecorator { + private static final Logger LOGGER = Logger.getLogger(GlobalDecorator.class.getName()); + + private transient Run run; + private String stageName; + private String agentName; + + public GlobalDecorator(WorkflowRun run) { + this(run, null, null); + } + public GlobalDecorator(WorkflowRun run, String stageName, String agentName) { + LOGGER.log(Level.INFO, "Creating decorator for {0}", run.toString()); + this.run = run; + this.stageName = stageName; + this.agentName = agentName; + } + + @Override + public OutputStream decorate(OutputStream logger) throws IOException, InterruptedException { + LogstashWriter writer = new LogstashWriter(run, logger, null, StandardCharsets.UTF_8, stageName, agentName); + LogstashOutputStream out = new LogstashOutputStream(logger, writer); + return out; + } + + @Extension + public static final class Factory implements TaskListenerDecorator.Factory { + + @Override + public TaskListenerDecorator of(FlowExecutionOwner owner) { + if (!LogstashConfiguration.getInstance().isEnableGlobally()) { + return null; + } + try { + Queue.Executable executable = owner.getExecutable(); + if (executable instanceof WorkflowRun) { + return new GlobalDecorator((WorkflowRun) executable); + } + } catch (IOException x) { + LOGGER.log(Level.WARNING, null, x); + } + return null; + } + } +} diff --git a/src/main/java/jenkins/plugins/logstash/pipeline/LogstashStep.java b/src/main/java/jenkins/plugins/logstash/pipeline/LogstashStep.java index 142f97c1..17a660fd 100644 --- a/src/main/java/jenkins/plugins/logstash/pipeline/LogstashStep.java +++ b/src/main/java/jenkins/plugins/logstash/pipeline/LogstashStep.java @@ -3,9 +3,18 @@ import java.io.IOException; import java.util.HashSet; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.Nonnull; +import org.jenkinsci.plugins.workflow.actions.LabelAction; +import org.jenkinsci.plugins.workflow.actions.WorkspaceAction; +import org.jenkinsci.plugins.workflow.graph.BlockStartNode; +import org.jenkinsci.plugins.workflow.graph.FlowNode; +import org.jenkinsci.plugins.workflow.graph.StepNode; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.jenkinsci.plugins.workflow.log.TaskListenerDecorator; import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl; import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback; import org.jenkinsci.plugins.workflow.steps.BodyInvoker; @@ -13,13 +22,17 @@ 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.support.steps.ExecutorStep; +import org.jenkinsci.plugins.workflow.support.steps.StageStep; import org.kohsuke.stapler.DataBoundConstructor; +import com.google.common.collect.ImmutableSet; + import hudson.Extension; -import hudson.console.ConsoleLogFilter; import hudson.model.Run; +import hudson.model.TaskListener; import jenkins.YesNoMaybe; -import jenkins.plugins.logstash.LogstashConsoleLogFilter; +import jenkins.plugins.logstash.LogstashConfiguration; /** * This is the pipeline counterpart of the LogstashJobProperty. @@ -27,6 +40,7 @@ */ public class LogstashStep extends Step { + private static final Logger LOGGER = Logger.getLogger(LogstashStep.class.getName()); /** Constructor. */ @DataBoundConstructor public LogstashStep() {} @@ -56,20 +70,69 @@ public void onResume() @Override public boolean start() throws Exception { StepContext context = getContext(); - context - .newBodyInvoker() - .withContext(createConsoleLogFilter(context)) - .withCallback(BodyExecutionCallback.wrap(context)) - .start(); + BodyInvoker invoker = context.newBodyInvoker().withCallback(BodyExecutionCallback.wrap(context)); + if (LogstashConfiguration.getInstance().isEnableGlobally()) { + context.get(TaskListener.class).getLogger().println("The logstash step is unnecessary when logstash is enabled for all builds."); + } else { + invoker.withContext(getMergedDecorator(context)); + } + invoker.start(); return false; } - private ConsoleLogFilter createConsoleLogFilter(StepContext context) + private TaskListenerDecorator getMergedDecorator(StepContext context) throws IOException, InterruptedException { - ConsoleLogFilter original = context.get(ConsoleLogFilter.class); - Run build = context.get(Run.class); - ConsoleLogFilter subsequent = new LogstashConsoleLogFilter(build); - return BodyInvoker.mergeConsoleLogFilters(original, subsequent); + Run run = context.get(Run.class); + FlowNode node = context.get(FlowNode.class); + FlowNode stageNode = getStageNode(node); + String stageName = null; + if (stageNode != null) { + LabelAction labelAction = stageNode.getAction(LabelAction.class); + if (labelAction != null) { + stageName = labelAction.getDisplayName(); + } + } + String agentName = getAgentName(node); + return TaskListenerDecorator.merge(context.get(TaskListenerDecorator.class), new GlobalDecorator((WorkflowRun) run, stageName, agentName)); + } + + private String getAgentName(FlowNode node) { + for (BlockStartNode bsn : node.iterateEnclosingBlocks()) { + if (bsn instanceof StepNode) { + StepDescriptor descriptor = ((StepNode) bsn).getDescriptor(); + if (descriptor instanceof ExecutorStep.DescriptorImpl) { + WorkspaceAction workspaceAction = bsn.getAction(WorkspaceAction.class); + if (workspaceAction != null) { + return workspaceAction.getNode(); + } + } + } + } + + return null; + } + + private FlowNode getStageNode(FlowNode node) { + for (BlockStartNode bsn : node.iterateEnclosingBlocks()) { + if (isStageNode(bsn)) { + return bsn; + } + } + return null; + } + + private boolean isStageNode(FlowNode node) { + if (node instanceof StepNode) { + StepDescriptor descriptor = ((StepNode) node).getDescriptor(); + if (descriptor instanceof StageStep.DescriptorImpl) { + LabelAction labelAction = node.getAction(LabelAction.class); + if (labelAction != null) { + return true; + } + } + } + + return false; } /** {@inheritDoc} */ @@ -102,11 +165,8 @@ public boolean takesImplicitBlockArgument() { } @Override - public Set> getRequiredContext() - { - Set> contexts = new HashSet<>(); - contexts.add(Run.class); - return contexts; + public Set> getRequiredContext() { + return ImmutableSet.of(Run.class, FlowNode.class); } } diff --git a/src/test/java/jenkins/plugins/logstash/LogstashOutputStreamTest.java b/src/test/java/jenkins/plugins/logstash/LogstashOutputStreamTest.java index 7752bf38..163626cf 100644 --- a/src/test/java/jenkins/plugins/logstash/LogstashOutputStreamTest.java +++ b/src/test/java/jenkins/plugins/logstash/LogstashOutputStreamTest.java @@ -1,8 +1,8 @@ package jenkins.plugins.logstash; import static org.hamcrest.core.StringContains.containsString; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; import static org.mockito.Mockito.*; import java.io.ByteArrayOutputStream; @@ -33,7 +33,7 @@ public void before() throws Exception { buffer = new ByteArrayOutputStream(); Mockito.doNothing().when(mockWriter).write(anyString()); when(mockWriter.isConnectionBroken()).thenReturn(false); - when(mockWriter.getCharset()).thenReturn(Charset.defaultCharset()); + when(mockWriter.getCharset()).thenReturn(Charset.defaultCharset().toString()); } @After diff --git a/src/test/java/jenkins/plugins/logstash/PipelineTest.java b/src/test/java/jenkins/plugins/logstash/PipelineTest.java index ec67b616..98820341 100644 --- a/src/test/java/jenkins/plugins/logstash/PipelineTest.java +++ b/src/test/java/jenkins/plugins/logstash/PipelineTest.java @@ -1,7 +1,8 @@ package jenkins.plugins.logstash; import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.MatcherAssert.assertThat; import java.util.List; @@ -15,6 +16,7 @@ import org.jvnet.hudson.test.BuildWatcher; import org.jvnet.hudson.test.JenkinsRule; +import hudson.model.Slave; import jenkins.plugins.logstash.configuration.MemoryIndexer; import jenkins.plugins.logstash.persistence.MemoryDao; import net.sf.json.JSONObject; @@ -54,6 +56,45 @@ public void logstash() throws Exception assertThat(data.getString("result"),equalTo("SUCCESS")); } + @Test + public void logstashStageAndAgent() throws Exception + { + Slave slave = j.createOnlineSlave(); + String agentName = slave.getNodeName(); + WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition("stage('stage1') { " + + "node('" + agentName + "') {\n" + + "logstash {\n" + + "currentBuild.result = 'SUCCESS'\n" + + "echo 'Message'\n" + + "}}}", 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.getString("result"),equalTo("SUCCESS")); + assertThat(data.getString("stageName"),equalTo("stage1")); + assertThat(data.getString("agentName"),equalTo(agentName)); + } + + @Test + public void globalLogstash() throws Exception + { + LogstashConfiguration config = LogstashConfiguration.getInstance(); + config.setEnableGlobally(true); + WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition( + "currentBuild.result = 'SUCCESS'\n" + + "echo 'Message'\n", true)); + j.assertBuildStatusSuccess(p.scheduleBuild2(0).get()); + List dataLines = memoryDao.getOutput(); + assertThat(dataLines.size(), greaterThan(0)); + JSONObject lastLine = dataLines.get(dataLines.size()-1); + JSONObject data = lastLine.getJSONObject("data"); + assertThat(data.getString("result"),equalTo("SUCCESS")); + } + @Test public void logstashSendNotifier() throws Exception { diff --git a/src/test/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDaoTest.java b/src/test/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDaoTest.java index f88ced1f..57e0f7f2 100644 --- a/src/test/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDaoTest.java +++ b/src/test/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDaoTest.java @@ -79,6 +79,7 @@ public void buildPayloadSuccessTwoLines() throws Exception { private AbstractLogstashIndexerDao getInstance() { return new AbstractLogstashIndexerDao() { + @Override public void push(String data) throws IOException {} diff --git a/src/test/java/jenkins/plugins/logstash/persistence/ElasticSearchSSLCertsTest.java b/src/test/java/jenkins/plugins/logstash/persistence/ElasticSearchSSLCertsTest.java index 333f65a6..1ceed6d9 100644 --- a/src/test/java/jenkins/plugins/logstash/persistence/ElasticSearchSSLCertsTest.java +++ b/src/test/java/jenkins/plugins/logstash/persistence/ElasticSearchSSLCertsTest.java @@ -40,14 +40,11 @@ public class ElasticSearchSSLCertsTest { @Rule public ExpectedException thrown = ExpectedException.none(); - private static final KeyStore NO_CLIENT_KEYSTORE = null; private static final SSLContext NO_SSL_CONTEXT = null; - private static final TrustManager[] NO_SERVER_TRUST_MANAGER = null; private static final char[] KEYPASS_AND_STOREPASS_VALUE = "aaaaaa".toCharArray(); private static final String JAVA_KEYSTORE = "jks"; - private static final String CLIENT_KEYSTORE = "elasticsearch-sslcerts/keystore.ks"; - private static final String CLIENT_TRUSTSTORE = "elasticsearch-sslcerts/truststore.ks"; + private static final String CLIENT_KEYSTORE = "elasticsearch-sslcerts/cert.pkcs12"; @Test public void NoSSLPost_NoSSLServer_Returns200OK() throws Exception { @@ -98,7 +95,7 @@ public void SSLPost_SSLServer_UpdatedTrustStore_Returns200OK() throws Exception ElasticSearchDao dao = new ElasticSearchDao(new URI("https://" + baseUrl), "", ""); KeyStore keyStore = getStore(CLIENT_KEYSTORE, KEYPASS_AND_STOREPASS_VALUE); - dao.setCustomKeyStore(keyStore); + dao.setCustomKeyStore(keyStore, "aaaaaa"); try { dao.push(""); @@ -116,7 +113,7 @@ public void SSLPost_NoSSLServer_ThrowsSSLException() throws Exception { ElasticSearchDao dao = new ElasticSearchDao(new URI("https://" + baseUrl), "", ""); KeyStore keyStore = getStore(CLIENT_KEYSTORE, KEYPASS_AND_STOREPASS_VALUE); - dao.setCustomKeyStore(keyStore); + dao.setCustomKeyStore(keyStore, "aaaaaa"); try { thrown.expect(IsInstanceOf.instanceOf(SSLException.class));