Initial commit

This commit is contained in:
Michael McCaskill 2016-07-15 11:38:52 -04:00
commit 6b4ba28078
17 changed files with 1043 additions and 0 deletions

19
.editorconfig Normal file
View File

@ -0,0 +1,19 @@
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
#
# Subline Text: https://github.com/editorconfig/editorconfig-sublime
# Emacs: https://github.com/editorconfig/editorconfig-emacs
# Jetbrains (IntelliJ etc): https://github.com/editorconfig/editorconfig-jetbrains
# Eclipse: ??
#
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.idea/
target/
*.iml

7
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,7 @@
#!groovy
node {
stage 'Build and Test'
checkout scm
sh 'mvn clean package'
}

21
LICENSE.txt Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 CloudBees Inc and a number of other of contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

3
README.asc Normal file
View File

@ -0,0 +1,3 @@
= Plugin Site API
Backend API for Jenkins Plugin Site

137
pom.xml Normal file
View File

@ -0,0 +1,137 @@
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.jenkins</groupId>
<artifactId>plugins</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<properties>
<jersey.version>2.23.1</jersey.version>
<slf4j.version>1.7.21</slf4j.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>${jersey.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20160212</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.3.10.v20160621</version>
</plugin>
</plugins>
</build>
<scm>
<developerConnection>scm:git:ssh://git@github.com/jenkins-infra/plugin-site.git</developerConnection>
<connection>scm:git:ssh://git@github.com/jenkins-infra/plugin-site.git</connection>
</scm>
</project>

View File

@ -0,0 +1,35 @@
package io.jenkins.plugins;
import io.jenkins.plugins.db.support.ElasticsearchClientFactory;
import io.jenkins.plugins.db.support.EmbeddedElasticsearchServer;
import io.jenkins.plugins.schedule.JobScheduler;
import org.elasticsearch.client.Client;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.utilities.ServiceLocatorUtilities;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.ApplicationPath;
@ApplicationPath("/")
public class RestApp extends ResourceConfig {
@Inject
public RestApp(ServiceLocator locator) {
ServiceLocatorUtilities.enableImmediateScope(locator);
register(new AbstractBinder() {
@Override
protected void configure() {
bind(EmbeddedElasticsearchServer.class).to(EmbeddedElasticsearchServer.class).in(Singleton.class);
bindFactory(ElasticsearchClientFactory.class).to(Client.class).in(Singleton.class);
bind(JobScheduler.class).to(JobScheduler.class).in(Singleton.class);
}
});
packages("io.jenkins.plugins");
}
}

View File

@ -0,0 +1,25 @@
package io.jenkins.plugins.db.support;
import org.elasticsearch.client.Client;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.Immediate;
import javax.inject.Inject;
import javax.inject.Singleton;
@Immediate
public class ElasticsearchClientFactory implements Factory<Client> {
@Inject
private EmbeddedElasticsearchServer es;
@Override
public Client provide() {
return es.getClient();
}
@Override
public void dispose(Client client) {
}
}

View File

@ -0,0 +1,78 @@
package io.jenkins.plugins.db.support;
import org.apache.commons.io.FileUtils;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.node.Node;
import org.elasticsearch.node.NodeBuilder;
import org.glassfish.hk2.api.Immediate;
import org.jvnet.hk2.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* Copied & modified from:
* https://orrsella.com/2014/10/28/embedded-elasticsearch-server-for-scala-integration-tests/
*/
@Service
@Immediate
public class EmbeddedElasticsearchServer {
private final Logger logger = LoggerFactory.getLogger(EmbeddedElasticsearchServer.class);
private File dataDir;
private Node node;
public Client getClient() {
return node.client();
}
@PostConstruct
public void postConstruct() {
logger.info("Initialize elasticsearch");
try {
this.dataDir = Files.createTempDirectory("elasticsearch_data").toFile();
} catch (IOException e) {
logger.error("Problem creating temp data directory", e);
throw new RuntimeException(e);
}
final Settings settings = ImmutableSettings.settingsBuilder()
.put("path.data", dataDir)
.put("http.enabled", "false")
.build();
node = NodeBuilder.nodeBuilder().local(true).settings(settings).build();
node.start();
createIndex();
logger.info("Initializing elasticsearch done");
}
@PreDestroy
public void preDestroy() {
node.close();
FileUtils.deleteQuietly(this.dataDir);
}
public void createIndex() {
try {
final ClassLoader cl = getClass().getClassLoader();
final File file = new File(cl.getResource("elasticsearch/plugins.json").getFile());
final String body = FileUtils.readFileToString(file, "utf-8");
final Client client = getClient();
client.admin().indices().prepareCreate("plugins")
.addMapping("plugins", body)
.get();
} catch (Exception e) {
logger.error("Problem creating index", e);
}
}
}

View File

@ -0,0 +1,28 @@
package io.jenkins.plugins.endpoints;
import org.apache.commons.io.FileUtils;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.File;
@Path("/categories")
@Produces(MediaType.APPLICATION_JSON)
public class Categories {
@GET
public String getCategories() {
try {
final ClassLoader cl = getClass().getClassLoader();
final File file = new File(cl.getResource("categories.json").getFile());
return FileUtils.readFileToString(file, "utf-8");
} catch (Exception e) {
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
}

View File

@ -0,0 +1,38 @@
package io.jenkins.plugins.endpoints;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.client.Client;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.concurrent.ExecutionException;
@Path("/plugin/{name}")
@Produces(MediaType.APPLICATION_JSON)
public class Plugin {
private final Logger logger = LoggerFactory.getLogger(Plugin.class);
@Inject
private Client esClient;
@GET
public String getPlugin(@PathParam("name") String name) {
try {
final GetResponse getResponse = esClient.prepareGet("plugins", "plugins", name).execute().get();
if (getResponse.isExists()) {
return getResponse.getSourceAsString();
} else {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
} catch (InterruptedException | ExecutionException e) {
logger.error("Problem getting plugin " + name, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
}

View File

@ -0,0 +1,68 @@
package io.jenkins.plugins.endpoints;
import io.jenkins.plugins.schedule.JobScheduler;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.glassfish.hk2.api.Immediate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Path("/plugins")
@Produces(MediaType.APPLICATION_JSON)
@Immediate
public class Plugins {
private final Logger logger = LoggerFactory.getLogger(Plugins.class);
@Inject
private Client esClient;
// Hackity hack hack. This is only here because I can't figure out how to successfully
// get the JobScheduler in RestApp from the ServiceLocator. Whenever I try I get NPE
// so this injection at least makes @PostConstruct fire
@Inject
private JobScheduler jobScheduler;
@GET
public String search(
@QueryParam("q") String query,
@DefaultValue("name") @QueryParam("sort") String sort,
@DefaultValue("") @QueryParam("labels") String labels,
@DefaultValue("") @QueryParam("authors")String authors,
@DefaultValue("") @QueryParam("core")String core,
@DefaultValue("50") @QueryParam("size") int size,
@DefaultValue("1") @QueryParam("page") int page) {
try {
final SearchRequestBuilder requestBuilder = esClient.prepareSearch("plugins")
.addFields("name", "url", "title", "wiki", "excerpt", "labels")
.addHighlightedField("excerpt")
.setHighlighterFragmentSize(500)
.setHighlighterNumOfFragments(1)
.setHighlighterPreTags("<mark>")
.setHighlighterPostTags("</mark>")
.setFrom((page -1) * size)
.setSize(size);
final BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
.should(QueryBuilders.matchQuery("title", query))
.should(QueryBuilders.matchQuery("name", query))
.must(QueryBuilders.matchQuery("excerpt", query));
if (!labels.isEmpty()) {
queryBuilder.should(QueryBuilders.termsQuery("labels", labels));
}
final SearchResponse response = requestBuilder.setQuery(queryBuilder).execute().get();
return response.toString();
} catch (Exception e) {
logger.error("Problem executing ES query", e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
}

View File

@ -0,0 +1,78 @@
package io.jenkins.plugins.schedule;
import org.elasticsearch.client.Client;
import org.glassfish.hk2.api.Immediate;
import org.jvnet.hk2.annotations.Service;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
@Service
@Immediate
public class JobScheduler {
private static final JobKey JOB_KEY = JobKey.jobKey("populateElasticsearchJob");
private final Logger logger = LoggerFactory.getLogger(JobScheduler.class);
private Scheduler scheduler;
@Inject
private Client esClient;
@PostConstruct
public void postConstruct() {
try {
this.scheduler = StdSchedulerFactory.getDefaultScheduler();
this.scheduler.start();
} catch (SchedulerException e) {
logger.error("Problem initializing Quartz", e);
}
schedule();
}
@PreDestroy
public void preDestroy() {
try {
if (!this.scheduler.isShutdown()) {
this.scheduler.shutdown();
}
} catch (SchedulerException e) {
logger.error("Problem shutting down Quartz", e);
}
}
private void schedule() {
final JobDataMap dataMap = new JobDataMap();
dataMap.put(PopulateElasticsearchJob.ES_CLIENT_KEY, esClient);
final JobDetail job = JobBuilder
.newJob(PopulateElasticsearchJob.class)
.withIdentity(JOB_KEY)
.usingJobData(dataMap)
.build();
final Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("populateElasticserchTrigger")
// Fire now and then every 12 hours
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInHours(12)
.repeatForever()
)
.build();
try {
if (!scheduler.checkExists(JOB_KEY)) {
scheduler.scheduleJob(job, trigger);
} else {
logger.info("Already scheduled");
}
} catch (SchedulerException e) {
logger.error("Problem scheduling " + JOB_KEY + " + job");
}
}
}

View File

@ -0,0 +1,160 @@
package io.jenkins.plugins.schedule;
import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.action.bulk.BulkProcessor;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.client.Client;
import org.json.JSONArray;
import org.json.JSONObject;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.stream.Collectors;
public class PopulateElasticsearchJob implements Job {
public static String ES_CLIENT_KEY = "esClient";
private final Logger logger = LoggerFactory.getLogger(PopulateElasticsearchJob.class);
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
final Client esClient = (Client)context.getJobDetail().getJobDataMap().get(ES_CLIENT_KEY);
if (Objects.isNull(esClient)) {
throw new JobExecutionException(ES_CLIENT_KEY + " is null");
}
logger.info("Starting PopulateElasticsearchJob");
final int processors = Runtime.getRuntime().availableProcessors();
final ExecutorService executorService = Executors.newFixedThreadPool(processors * 10);
final CloseableHttpClient httpClient = HttpClients.createDefault();
try {
final ResponseHandler<JSONObject> updateCenterHandler = (httpResponse) -> {
final StatusLine status = httpResponse.getStatusLine();
if (status.getStatusCode() == 200) {
final HttpEntity entity = httpResponse.getEntity();
final String content = EntityUtils.toString(entity);
try {
return new JSONObject(String.join("", content.split("\n")[1])).getJSONObject("plugins");
} catch (Exception e) {
throw new ClientProtocolException("Update center returned invalid JSON");
}
} else {
throw new ClientProtocolException("Unexpected response from update center - " + status.toString());
}
};
final JSONObject plugins = httpClient.execute(new HttpGet("https://updates.jenkins-ci.org/current/update-center.json"), updateCenterHandler);
final List<Callable<Void>> tasks = new ArrayList<>();
for (String key : plugins.keySet()) {
final JSONObject plugin = plugins.getJSONObject(key);
final Callable<Void> task = () -> {
final ResponseHandler<Void> statsHandler = (httpResponse) -> {
final StatusLine status = httpResponse.getStatusLine();
if (status.getStatusCode() == 200) {
final HttpEntity entity = httpResponse.getEntity();
final String content = EntityUtils.toString(entity);
try {
final JSONObject stats = new JSONObject(content);
final JSONObject installations = stats.getJSONObject("installations");
final JSONObject installationsPercentage = stats.getJSONObject("installationsPercentage");
final JSONObject installationsPerVersion = stats.getJSONObject("installationsPerVersion");
final JSONObject installationsPercentagePerVersion = stats.getJSONObject("installationsPercentagePerVersion");
plugin.put("installations", new JSONArray(installations.keySet().stream().map((timestamp) -> {
final JSONObject installation = new JSONObject();
installation.put("timestamp", Long.valueOf(timestamp));
installation.put("total", installations.getInt(timestamp));
return installation;
}).collect(Collectors.toSet())));
plugin.put("installationsPercentage", new JSONArray(installationsPercentage.keySet().stream().map((timestamp) -> {
final JSONObject installation = new JSONObject();
installation.put("timestamp", Long.valueOf(timestamp));
installation.put("percentage", installationsPercentage.getDouble(timestamp));
return installation;
}).collect(Collectors.toSet())));
plugin.put("installationsPerVersion", new JSONArray(installationsPerVersion.keySet().stream().map((version) -> {
final JSONObject installation = new JSONObject();
installation.put("version", version);
installation.put("total", installationsPerVersion.getInt(version));
return installation;
}).collect(Collectors.toSet())));
plugin.put("installationsPercentagePerVersion", new JSONArray(installationsPercentagePerVersion.keySet().stream().map((version) -> {
final JSONObject installation = new JSONObject();
installation.put("version", version);
installation.put("total", installationsPercentagePerVersion.getDouble(version));
return installation;
}).collect(Collectors.toSet())));
return null;
} catch (Exception e) {
throw new ClientProtocolException("Problem processing stats", e);
}
} else {
throw new ClientProtocolException("Unexpected response for stats " + status.toString());
}
};
final String url = "http://stats.jenkins-ci.org/plugin-installation-trend/" + plugin.get("name") + ".stats.json";
try {
httpClient.execute(new HttpGet(url), statsHandler);
} catch (Exception e) {
logger.error("Problem getting status for plugin " + key);
}
return null;
};
tasks.add(task);
}
executorService.invokeAll(tasks).stream().map(task -> {
try {
return task.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
final BulkProcessor bulk = BulkProcessor.builder(esClient, new BulkProcessor.Listener() {
@Override
public void beforeBulk(long executionId, BulkRequest request) {
}
@Override
public void afterBulk(long executionId, BulkRequest request, BulkResponse response) {
logger.info("Indexed " + response.getItems().length + " plugins in " + response.getTook().minutes() + " min " + response.getTook().seconds() + " sec");
}
@Override
public void afterBulk(long executionId, BulkRequest request, Throwable failure) {
}
}).build();
plugins.keySet().forEach((key) -> {
final JSONObject plugin = plugins.getJSONObject(key);
bulk.add(esClient.prepareIndex("plugins", "plugins").setId(plugin.getString("name")).setSource(plugin.toString()).request());
});
bulk.awaitClose(2, TimeUnit.MINUTES);
logger.info("Finished PopulateElasticsearchJob");
} catch (Exception e) {
logger.error("Problem getting plugin information", e);
} finally {
try {
httpClient.close();
} catch (IOException e) {
logger.error("Problem closing httpClient", e);
}
if (!executorService.isShutdown()) {
executorService.shutdown();
}
}
}
}

View File

@ -0,0 +1,186 @@
{
"categories":
[
{
"id": "languagesPlatforms",
"title": "Platforms",
"description": "Jenkins plugins that are designed to give added support for building, testing or deploying to specific languages or platforms.",
"labels":
[
{
"id": "ios",
"title": "iOS development"
},
{
"id": "dotnet",
"title": "Azure and .NET"
},
{
"id": "android",
"title": "Android development"
},
{
"id": "ruby",
"title": "Ruby development"
},
{
"id": "scala",
"title": "Scala plugins"
}
]
},
{
"id": "userInterface",
"title": "User interface",
"description": "Add-ons designed to make your iteraction with Jenkins better.",
"labels":
[
{
"id": "ui",
"title": "User Interface"
},
{
"id": "listview-column",
"title": "List view column plugins"
}
]
},
{
"id": "adminTools",
"title": "Administration",
"description": "Add functionality to help streamline your administration of Jenkins.",
"labels":
[
{
"id": "slaves",
"title": "Agent launchers and controllers"
},
{
"id": "page-decorator",
"title": "Page decorators"
},
{
"id": "user",
"title": "Authentication and user management"
},
{
"id": "cluster",
"title": "Cluster management and distributed build"
},
{
"id": "cli",
"title": "CLI extensions"
}
]
},
{
"id": "buildManagement",
"title": "Build management",
"description": "Add new means of trigger jobs and new ways to ensure environment consistency for you automated processes.",
"labels":
[
{
"id": "trigger",
"title": "Build triggers"
},
{
"id": "buildwrapper",
"title": "Build wrappers"
},
{
"id": "notifier",
"title": "Build notifiers"
},
{
"id": "deployment",
"title": "Deployment plugins"
},
{
"id": "parameter",
"title": "Build parameters"
},
{
"id": "post-build",
"title": "Clean-up actions"
},
{
"id": "builder",
"title": "Build tools"
},
{
"id": "report",
"title": "Build reports"
},
{
"id": "upload",
"title": "Artifact uploaders"
}
]
},
{
"id": "scm",
"title": "Source code management",
"description": "Connect to your Jenkins to your SCM.",
"labels":
[
{
"id": "scm",
"title": "Source Code Management"
},
{
"id": "scm-related",
"title": "Source Code Management related"
}
]
},
{
"id": "junk",
"hidden": true,
"labels":
[
{
"id": "library"
},
{
"id": "misc"
},
{
"id": "must-be-labeled"
},
{
"id": "external"
},
{
"id": "maven"
}
]
}
]
}

View File

@ -0,0 +1,141 @@
{
"plugins" : {
"properties" : {
"buildDate" : {
"type" : "date",
"format" : "MMM dd, YYYY"
},
"dependencies" : {
"type" : "nested",
"properties" : {
"name" : {
"type" : "string",
"index" : "not_analyzed"
},
"optional" : {
"type" : "boolean"
},
"version" : {
"type" : "string",
"index" : "not_analyzed"
}
}
},
"developers" : {
"type" : "nested",
"properties" : {
"developerId" : {
"type" : "string",
"index" : "not_analyzed"
},
"email" : {
"type" : "string",
"index" : "not_analyzed"
},
"name" : {
"type" : "string"
}
}
},
"exerpt" : {
"type" : "string"
},
"gav" : {
"type" : "string",
"index" : "not_analyzed"
},
"installations" : {
"type" : "nested",
"properties" : {
"timestamp" : {
"type" : "long"
},
"total" : {
"type" : "integer"
}
}
},
"installationsPercentage": {
"type" : "nested",
"properties" : {
"timestamp" : {
"type" : "long"
},
"percentage" : {
"type" : "double"
}
}
},
"installationsPerVersion" : {
"type" : "nested",
"properties" : {
"version" : {
"type" : "string",
"index" : "not_analyzed"
},
"total" : {
"type" : "integer"
}
}
},
"installationsPercentagePerVersion" : {
"type" : "nested",
"properties" : {
"version" : {
"type" : "string",
"index" : "not_analyzed"
},
"percentage" : {
"type" : "double"
}
}
},
"labels" : {
"type" : "string",
"index" : "not_analyzed"
},
"name" : {
"type" : "string"
},
"previousTimestamp" : {
"type" : "date",
"format" : "date_time"
},
"previousVersion" : {
"type" : "string",
"index" : "not_analyzed"
},
"releaseTimestamp" : {
"type" : "date",
"format" : "date_time"
},
"requiredCore" : {
"type" : "string",
"index" : "not_analyzed"
},
"scm" : {
"type" : "string",
"index" : "not_analyzed"
},
"sha1" : {
"type" : "string",
"index" : "not_analyzed"
},
"title" : {
"type" : "string"
},
"url" : {
"type" : "string",
"index" : "not_analyzed"
},
"version" : {
"type" : "string",
"index" : "not_analyzed"
},
"wiki" : {
"type" : "string",
"index" : "not_analyzed"
}
}
}
}

View File

@ -0,0 +1,16 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
<logger name="org.elasticsearch" level="error"/>
<logger name="org.quartz" level="error"/>
</configuration>