diff --git a/src/main/java/jenkins/plugins/logstash/LogstashConfiguration.java b/src/main/java/jenkins/plugins/logstash/LogstashConfiguration.java index a90837e9..5762b84c 100644 --- a/src/main/java/jenkins/plugins/logstash/LogstashConfiguration.java +++ b/src/main/java/jenkins/plugins/logstash/LogstashConfiguration.java @@ -7,6 +7,7 @@ import java.util.logging.Level; import java.util.logging.Logger; +import javax.activation.MimeTypeParseException; import javax.annotation.CheckForNull; import org.kohsuke.stapler.StaplerRequest; @@ -171,15 +172,30 @@ public void migrateData() @Override public boolean configure(StaplerRequest staplerRequest, JSONObject json) throws FormException { + // when we bind the stapler request we get a new instance of logstashIndexer. // logstashIndexer is holder for the dao instance. // To avoid that we get a new dao instance in case there was no change in configuration // we compare it to the currently active configuration. staplerRequest.bindJSON(this, json); + + try { + // validate + logstashIndexer.validate(); + } catch (Exception ex) { + // You are here which means user is trying to save invalid indexer configuration. + // Exception will be thrown here so that it gets displayed on UI. + // But before that revert back to original configuration (in-memory) + // so that when user refreshes the configuration page, last saved settings will be displayed again. + logstashIndexer = activeIndexer; + throw new IllegalArgumentException(ex); + } + if (!Objects.equals(logstashIndexer, activeIndexer)) { activeIndexer = logstashIndexer; } + save(); return true; } diff --git a/src/main/java/jenkins/plugins/logstash/configuration/ElasticSearch.java b/src/main/java/jenkins/plugins/logstash/configuration/ElasticSearch.java index f5f1a7db..2d560c3b 100644 --- a/src/main/java/jenkins/plugins/logstash/configuration/ElasticSearch.java +++ b/src/main/java/jenkins/plugins/logstash/configuration/ElasticSearch.java @@ -5,6 +5,9 @@ import java.net.URISyntaxException; import java.net.URL; +import javax.activation.MimeType; +import javax.activation.MimeTypeParseException; + import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; @@ -21,6 +24,7 @@ public class ElasticSearch extends LogstashIndexer private String username; private Secret password; private URI uri; + private String mimeType; @DataBoundConstructor public ElasticSearch() @@ -32,6 +36,10 @@ public URI getUri() return uri; } + @Override + public void validate() throws MimeTypeParseException { + new MimeType(this.mimeType); + } /* * We use URL for the setter as stapler can autoconvert a string to a URL but not to a URI @@ -68,7 +76,16 @@ public void setPassword(String password) { this.password = Secret.fromString(password); } - + + @DataBoundSetter + public void setMimeType(String mimeType) { + this.mimeType = mimeType; + } + + public String getMimeType() { + return mimeType; + } + @Override public boolean equals(Object obj) { @@ -101,6 +118,10 @@ else if (!username.equals(other.username)) { return false; } + else if (!mimeType.equals(other.mimeType)) + { + return false; + } return true; } @@ -118,7 +139,9 @@ public int hashCode() @Override public ElasticSearchDao createIndexerInstance() { - return new ElasticSearchDao(getUri(), username, Secret.toString(password)); + ElasticSearchDao esDao = new ElasticSearchDao(getUri(), username, Secret.toString(password)); + esDao.setMimeType(getMimeType()); + return esDao; } @Extension @@ -163,5 +186,17 @@ public FormValidation doCheckUrl(@QueryParameter("value") String value) } return FormValidation.ok(); } + public FormValidation doCheckMimeType(@QueryParameter("value") String value) { + if (StringUtils.isBlank(value)) { + return FormValidation.error(Messages.ValueIsRequired()); + } + try { + //This is simply to check validity of the given mimeType + new MimeType(value); + } catch (MimeTypeParseException e) { + return FormValidation.error(Messages.ProvideValidMimeType()); + } + return FormValidation.ok(); + } } } diff --git a/src/main/java/jenkins/plugins/logstash/configuration/LogstashIndexer.java b/src/main/java/jenkins/plugins/logstash/configuration/LogstashIndexer.java index 6f3955b5..bc98aa00 100644 --- a/src/main/java/jenkins/plugins/logstash/configuration/LogstashIndexer.java +++ b/src/main/java/jenkins/plugins/logstash/configuration/LogstashIndexer.java @@ -48,6 +48,16 @@ public synchronized T getInstance() return instance; } + /** + * Purpose of this method is to validate the inputs (if required) and if found + * erroneous throw an exception so that it will be bubbled up to the UI. + * + * @throws Exception + */ + public void validate() throws Exception { + } + + /** * Creates a new {@link AbstractLogstashIndexerDao} instance corresponding to this configuration. diff --git a/src/main/java/jenkins/plugins/logstash/persistence/ElasticSearchDao.java b/src/main/java/jenkins/plugins/logstash/persistence/ElasticSearchDao.java index e5167f21..d96e077c 100644 --- a/src/main/java/jenkins/plugins/logstash/persistence/ElasticSearchDao.java +++ b/src/main/java/jenkins/plugins/logstash/persistence/ElasticSearchDao.java @@ -44,6 +44,7 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; + import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; @@ -51,6 +52,7 @@ import jenkins.plugins.logstash.configuration.ElasticSearch; + /** * Elastic Search Data Access Object. * @@ -58,6 +60,7 @@ * @since 1.0.4 */ public class ElasticSearchDao extends AbstractLogstashIndexerDao { + private final HttpClientBuilder clientBuilder; private final URI uri; private final String auth; @@ -65,6 +68,8 @@ public class ElasticSearchDao extends AbstractLogstashIndexerDao { private String username; private String password; + private String mimeType; + //primary constructor used by indexer factory public ElasticSearchDao(URI uri, String username, String password) { @@ -102,11 +107,11 @@ public ElasticSearchDao(URI uri, String username, String password) { clientBuilder = factory == null ? HttpClientBuilder.create() : factory; } + public URI getUri() { return uri; } - public String getHost() { return uri.getHost(); @@ -136,16 +141,27 @@ public String getKey() { return uri.getPath(); } - + + public String getMimeType() { + return this.mimeType; + } + + public void setMimeType(String mimeType) { + this.mimeType = mimeType; + } + String getAuth() { return auth; } - protected HttpPost getHttpPost(String data) { - HttpPost postRequest; - postRequest = new HttpPost(uri); - StringEntity input = new StringEntity(data, ContentType.APPLICATION_JSON); + HttpPost getHttpPost(String data) { + HttpPost postRequest = new HttpPost(uri); + String mimeType = this.getMimeType(); + // char encoding is set to UTF_8 since this request posts a JSON string + StringEntity input = new StringEntity(data, StandardCharsets.UTF_8); + mimeType = (mimeType != null) ? mimeType : ContentType.APPLICATION_JSON.toString(); + input.setContentType(mimeType); postRequest.setEntity(input); if (auth != null) { postRequest.addHeader("Authorization", "Basic " + auth); diff --git a/src/main/resources/jenkins/plugins/logstash/Messages.properties b/src/main/resources/jenkins/plugins/logstash/Messages.properties index b2844c22..14956598 100644 --- a/src/main/resources/jenkins/plugins/logstash/Messages.properties +++ b/src/main/resources/jenkins/plugins/logstash/Messages.properties @@ -23,4 +23,5 @@ DisplayName = Send console log to Logstash ValueIsInt = Value must be an integer ValueIsRequired = Value is required -PleaseProvideHost = Please set a valid host name \ No newline at end of file +PleaseProvideHost = Please set a valid host name +ProvideValidMimeType = Please provide a valid mime type \ No newline at end of file diff --git a/src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/config.jelly b/src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/config.jelly index a01fab63..fc1ec8f9 100644 --- a/src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/config.jelly +++ b/src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/config.jelly @@ -9,4 +9,7 @@ + + + \ No newline at end of file diff --git a/src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/help-mimeType.html b/src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/help-mimeType.html new file mode 100644 index 00000000..20dba0a5 --- /dev/null +++ b/src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/help-mimeType.html @@ -0,0 +1,6 @@ +
+

+ MIME type of the request body that is sent to ELASTICSEARCH indexer. It should be of the form type/subtype e.g. application/json
+ Since this is a field for MIME Type and not Content-Type we do not support additional content-type paramters like charset or boundry +

+
diff --git a/src/test/java/jenkins/plugins/logstash/configuration/ElasticSearchTest.java b/src/test/java/jenkins/plugins/logstash/configuration/ElasticSearchTest.java index 171ed06e..794e0d57 100644 --- a/src/test/java/jenkins/plugins/logstash/configuration/ElasticSearchTest.java +++ b/src/test/java/jenkins/plugins/logstash/configuration/ElasticSearchTest.java @@ -32,11 +32,13 @@ public void setup() throws MalformedURLException, URISyntaxException indexer.setUri(url); indexer.setPassword("password"); indexer.setUsername("user"); + indexer.setMimeType("application/json"); indexer2 = new ElasticSearch(); indexer2.setUri(url); indexer2.setPassword("password"); indexer2.setUsername("user"); + indexer2.setMimeType("application/json"); } @Test diff --git a/src/test/java/jenkins/plugins/logstash/persistence/ElasticSearchDaoTest.java b/src/test/java/jenkins/plugins/logstash/persistence/ElasticSearchDaoTest.java index e18e1e88..dc514341 100644 --- a/src/test/java/jenkins/plugins/logstash/persistence/ElasticSearchDaoTest.java +++ b/src/test/java/jenkins/plugins/logstash/persistence/ElasticSearchDaoTest.java @@ -49,7 +49,7 @@ ElasticSearchDao createDao(String url, String username, String password) throws public void before() throws Exception { int port = (int) (Math.random() * 1000); dao = createDao("http://localhost:8200/logstash", "username", "password"); - + when(mockClientBuilder.build()).thenReturn(mockHttpClient); when(mockHttpClient.execute(any(HttpPost.class))).thenReturn(mockResponse); when(mockResponse.getStatusLine()).thenReturn(mockStatusLine); @@ -196,6 +196,24 @@ public void pushFailStatusCode() throws Exception { e.getMessage().contains("Something bad happened.") && e.getMessage().contains("HTTP error code: 500")); throw e; } - + + } + @Test + public void getHttpPostSuccessWithUserInput() throws Exception { + String json = "{ 'foo': 'bar' }"; + String mimeType = "application/json"; + dao = createDao("http://localhost:8200/jenkins/logstash", "username", "password"); + dao.setMimeType(mimeType); + HttpPost post = dao.getHttpPost(json); + HttpEntity entity = post.getEntity(); + assertEquals("Content type do not match", mimeType, entity.getContentType().getValue()); + } + @Test + public void getHttpPostWithFallbackInput() throws Exception { + String json = "{ 'foo': 'bar' }"; + dao = createDao("http://localhost:8200/jenkins/logstash", "username", "password"); + HttpPost post = dao.getHttpPost(json); + HttpEntity entity = post.getEntity(); + assertEquals("Content type do not match", ContentType.APPLICATION_JSON.toString(), entity.getContentType().getValue()); } }