releases.moxie | ●●●●● patch | view | raw | blame | history | |
src/main/distrib/data/gitblit.properties | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/manager/GitblitManager.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/manager/IPluginManager.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/manager/PluginManager.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/models/PluginRegistry.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/transport/ssh/commands/PluginDispatcher.java | ●●●●● patch | view | raw | blame | history |
releases.moxie
@@ -48,6 +48,7 @@ - { name: 'git.sshBackend', defaultValue: 'NIO2' } - { name: 'git.sshCommandStartThreads', defaultValue: '2' } - { name: 'plugins.folder', defaultValue: '${baseFolder}/plugins' } - { name: 'plugins.registry', defaultValue: 'http://gitblit.github.io/gitblit-registry/plugins.json' } } # src/main/distrib/data/gitblit.properties
@@ -548,6 +548,18 @@ # SINCE 1.4.0 tickets.perPage = 25 # The folder where plugins are loaded from. # # SINCE 1.5.0 # RESTART REQUIRED # BASEFOLDER plugins.folder = ${baseFolder}/plugins # The registry of available plugins. # # SINCE 1.5.0 plugins.registry = http://gitblit.github.io/gitblit-registry/plugins.json # # Groovy Integration # @@ -1850,11 +1862,3 @@ # SINCE 0.5.0 # RESTART REQUIRED server.shutdownPort = 8081 # Base folder for plugins. # This folder may contain Gitblit plugins # # SINCE 1.6.0 # RESTART REQUIRED # BASEFOLDER plugins.folder = ${baseFolder}/plugins src/main/java/com/gitblit/manager/GitblitManager.java
@@ -61,6 +61,8 @@ import com.gitblit.models.GitClientApplication; import com.gitblit.models.Mailing; import com.gitblit.models.Metric; import com.gitblit.models.PluginRegistry.PluginRegistration; import com.gitblit.models.PluginRegistry.PluginRelease; import com.gitblit.models.ProjectModel; import com.gitblit.models.RegistrantAccessPermission; import com.gitblit.models.RepositoryModel; @@ -1180,6 +1182,10 @@ return repositoryManager.isIdle(repository); } /* * PLUGIN MANAGER */ @Override public <T> List<T> getExtensions(Class<T> clazz) { return pluginManager.getExtensions(clazz); @@ -1196,6 +1202,36 @@ } @Override public boolean refreshRegistry() { return pluginManager.refreshRegistry(); } @Override public boolean installPlugin(String url) { return pluginManager.installPlugin(url); } @Override public boolean installPlugin(PluginRelease pv) { return pluginManager.installPlugin(pv); } @Override public List<PluginRegistration> getRegisteredPlugins() { return pluginManager.getRegisteredPlugins(); } @Override public PluginRegistration lookupPlugin(String idOrName) { return pluginManager.lookupPlugin(idOrName); } @Override public PluginRelease lookupRelease(String idOrName, String version) { return pluginManager.lookupRelease(idOrName, version); } @Override public List<PluginWrapper> getPlugins() { return pluginManager.getPlugins(); } src/main/java/com/gitblit/manager/IPluginManager.java
@@ -15,8 +15,13 @@ */ package com.gitblit.manager; import java.util.List; import ro.fortsoft.pf4j.PluginManager; import ro.fortsoft.pf4j.PluginWrapper; import com.gitblit.models.PluginRegistry.PluginRegistration; import com.gitblit.models.PluginRegistry.PluginRelease; public interface IPluginManager extends IManager, PluginManager { @@ -35,4 +40,43 @@ * @return true if successful */ boolean deletePlugin(PluginWrapper wrapper); /** * Refresh the plugin registry. */ boolean refreshRegistry(); /** * Install the plugin from the specified url. */ boolean installPlugin(String url); /** * Install the plugin. */ boolean installPlugin(PluginRelease pr); /** * The list of all registered plugins. * * @return a list of registered plugins */ List<PluginRegistration> getRegisteredPlugins(); /** * Lookup a plugin registration from the plugin registries. * * @param idOrName * @return a plugin registration or null */ PluginRegistration lookupPlugin(String idOrName); /** * Lookup a plugin release. * * @param idOrName * @param version (use null for the current version) * @return the identified plugin version or null */ PluginRelease lookupRelease(String idOrName, String version); } src/main/java/com/gitblit/manager/PluginManager.java
@@ -15,16 +15,37 @@ */ package com.gitblit.manager; import java.io.BufferedInputStream; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ro.fortsoft.pf4j.DefaultPluginManager; import ro.fortsoft.pf4j.PluginVersion; import ro.fortsoft.pf4j.PluginWrapper; import com.gitblit.Keys; import com.gitblit.models.PluginRegistry; import com.gitblit.models.PluginRegistry.PluginRegistration; import com.gitblit.models.PluginRegistry.PluginRelease; import com.gitblit.utils.Base64; import com.gitblit.utils.FileUtils; import com.gitblit.utils.JsonUtils; import com.gitblit.utils.StringUtils; import com.google.common.io.Files; import com.google.common.io.InputSupplier; /** * The plugin manager maintains the lifecycle of plugins. It is exposed as @@ -39,6 +60,11 @@ private final Logger logger = LoggerFactory.getLogger(getClass()); private final IRuntimeManager runtimeManager; // timeout defaults of Maven 3.0.4 in seconds private int connectTimeout = 20; private int readTimeout = 12800; public PluginManager(IRuntimeManager runtimeManager) { super(runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins")); @@ -75,4 +101,218 @@ } return true; } @Override public boolean refreshRegistry() { String dr = "http://gitblit.github.io/gitblit-registry/plugins.json"; String url = runtimeManager.getSettings().getString(Keys.plugins.registry, dr); try { return download(url); } catch (Exception e) { logger.error(String.format("Failed to retrieve plugins.json from %s", url), e); } return false; } protected List<PluginRegistry> getRegistries() { List<PluginRegistry> list = new ArrayList<PluginRegistry>(); File folder = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins"); FileFilter jsonFilter = new FileFilter() { @Override public boolean accept(File file) { return !file.isDirectory() && file.getName().toLowerCase().endsWith(".json"); } }; File [] files = folder.listFiles(jsonFilter); if (files == null || files.length == 0) { // automatically retrieve the registry if we don't have a local copy refreshRegistry(); files = folder.listFiles(jsonFilter); } if (files == null || files.length == 0) { return list; } for (File file : files) { PluginRegistry registry = null; try { String json = FileUtils.readContent(file, "\n"); registry = JsonUtils.fromJsonString(json, PluginRegistry.class); } catch (Exception e) { logger.error("Failed to deserialize " + file, e); } if (registry != null) { list.add(registry); } } return list; } @Override public List<PluginRegistration> getRegisteredPlugins() { List<PluginRegistration> list = new ArrayList<PluginRegistration>(); Map<String, PluginRegistration> map = new TreeMap<String, PluginRegistration>(); for (PluginRegistry registry : getRegistries()) { List<PluginRegistration> registrations = registry.registrations; list.addAll(registrations); for (PluginRegistration reg : registrations) { reg.installedRelease = null; map.put(reg.id, reg); } } for (PluginWrapper pw : getPlugins()) { String id = pw.getDescriptor().getPluginId(); PluginVersion pv = pw.getDescriptor().getVersion(); PluginRegistration reg = map.get(id); if (reg != null) { reg.installedRelease = pv.toString(); } } return list; } @Override public PluginRegistration lookupPlugin(String idOrName) { for (PluginRegistry registry : getRegistries()) { PluginRegistration reg = registry.lookup(idOrName); if (reg != null) { return reg; } } return null; } @Override public PluginRelease lookupRelease(String idOrName, String version) { for (PluginRegistry registry : getRegistries()) { PluginRegistration reg = registry.lookup(idOrName); if (reg != null) { PluginRelease pv; if (StringUtils.isEmpty(version)) { pv = reg.getCurrentRelease(); } else { pv = reg.getRelease(version); } if (pv != null) { return pv; } } } return null; } /** * Installs the plugin from the plugin version. * * @param pv * @throws IOException * @return true if successful */ @Override public boolean installPlugin(PluginRelease pv) { return installPlugin(pv.url); } /** * Installs the plugin from the url. * * @param url * @return true if successful */ @Override public boolean installPlugin(String url) { try { if (!download(url)) { return false; } // TODO stop, unload, load } catch (IOException e) { logger.error("Failed to install plugin from " + url, e); } return true; } /** * Download a file to the plugins folder. * * @param url * @return * @throws IOException */ protected boolean download(String url) throws IOException { File pFolder = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins"); File tmpFile = new File(pFolder, StringUtils.getSHA1(url) + ".tmp"); if (tmpFile.exists()) { tmpFile.delete(); } URL u = new URL(url); final URLConnection conn = getConnection(u); // try to get the server-specified last-modified date of this artifact long lastModified = conn.getHeaderFieldDate("Last-Modified", System.currentTimeMillis()); Files.copy(new InputSupplier<InputStream>() { @Override public InputStream getInput() throws IOException { return new BufferedInputStream(conn.getInputStream()); } }, tmpFile); File destFile = new File(pFolder, StringUtils.getLastPathElement(u.getPath())); if (destFile.exists()) { destFile.delete(); } tmpFile.renameTo(destFile); destFile.setLastModified(lastModified); return true; } protected URLConnection getConnection(URL url) throws IOException { java.net.Proxy proxy = getProxy(url); HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy); if (java.net.Proxy.Type.DIRECT != proxy.type()) { String auth = getProxyAuthorization(url); conn.setRequestProperty("Proxy-Authorization", auth); } String username = null; String password = null; if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) { // set basic authentication header String auth = Base64.encodeBytes((username + ":" + password).getBytes()); conn.setRequestProperty("Authorization", "Basic " + auth); } // configure timeouts conn.setConnectTimeout(connectTimeout * 1000); conn.setReadTimeout(readTimeout * 1000); switch (conn.getResponseCode()) { case HttpURLConnection.HTTP_MOVED_TEMP: case HttpURLConnection.HTTP_MOVED_PERM: // handle redirects by closing this connection and opening a new // one to the new location of the requested resource String newLocation = conn.getHeaderField("Location"); if (!StringUtils.isEmpty(newLocation)) { logger.info("following redirect to {0}", newLocation); conn.disconnect(); return getConnection(new URL(newLocation)); } } return conn; } protected Proxy getProxy(URL url) { return java.net.Proxy.NO_PROXY; } protected String getProxyAuthorization(URL url) { return ""; } } src/main/java/com/gitblit/models/PluginRegistry.java
New file @@ -0,0 +1,143 @@ /* * Copyright 2014 gitblit.com. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.gitblit.models; import java.io.Serializable; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.parboiled.common.StringUtils; import ro.fortsoft.pf4j.PluginVersion; /** * Represents a list of plugin registrations. */ public class PluginRegistry implements Serializable { private static final long serialVersionUID = 1L; public final String name; public final List<PluginRegistration> registrations; public PluginRegistry(String name) { this.name = name; registrations = new ArrayList<PluginRegistration>(); } public PluginRegistration lookup(String idOrName) { for (PluginRegistration registration : registrations) { if (registration.id.equalsIgnoreCase(idOrName) || registration.name.equalsIgnoreCase(idOrName)) { return registration; } } return null; } @Override public String toString() { return getClass().getSimpleName(); } public static enum InstallState { NOT_INSTALLED, INSTALLED, CAN_UPDATE, UNKNOWN } /** * Represents a plugin registration. */ public static class PluginRegistration implements Serializable { private static final long serialVersionUID = 1L; public final String id; public String name; public String description; public String provider; public String projectUrl; public String currentRelease; public transient String installedRelease; public List<PluginRelease> releases; public PluginRegistration(String id) { this.id = id; this.releases = new ArrayList<PluginRelease>(); } public PluginRelease getCurrentRelease() { PluginRelease current = null; if (!StringUtils.isEmpty(currentRelease)) { current = getRelease(currentRelease); } if (current == null) { Date date = new Date(0); for (PluginRelease pv : releases) { if (pv.date.after(date)) { current = pv; } } } return current; } public PluginRelease getRelease(String version) { for (PluginRelease pv : releases) { if (pv.version.equalsIgnoreCase(version)) { return pv; } } return null; } public InstallState getInstallState() { if (StringUtils.isEmpty(installedRelease)) { return InstallState.NOT_INSTALLED; } PluginVersion ir = PluginVersion.createVersion(installedRelease); PluginVersion cr = PluginVersion.createVersion(currentRelease); switch (ir.compareTo(cr)) { case -1: return InstallState.UNKNOWN; case 1: return InstallState.CAN_UPDATE; default: return InstallState.INSTALLED; } } @Override public String toString() { return id; } } public static class PluginRelease { public String version; public Date date; public String url; } } src/main/java/com/gitblit/transport/ssh/commands/PluginDispatcher.java
@@ -19,6 +19,7 @@ import java.util.List; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; import ro.fortsoft.pf4j.PluginDependency; import ro.fortsoft.pf4j.PluginDescriptor; @@ -26,6 +27,8 @@ import ro.fortsoft.pf4j.PluginWrapper; import com.gitblit.manager.IGitblit; import com.gitblit.models.PluginRegistry.PluginRegistration; import com.gitblit.models.PluginRegistry.PluginRelease; import com.gitblit.models.UserModel; import com.gitblit.utils.FlipTable; import com.gitblit.utils.FlipTable.Borders; @@ -46,7 +49,8 @@ register(user, StopPlugin.class); register(user, ShowPlugin.class); register(user, RemovePlugin.class); register(user, UploadPlugin.class); register(user, InstallPlugin.class); register(user, AvailablePlugins.class); } @CommandMetaData(name = "list", aliases = { "ls" }, description = "List the loaded plugins") @@ -283,11 +287,97 @@ } } @CommandMetaData(name = "receive", aliases= { "upload" }, description = "Upload a plugin to the server", hidden = true) public static class UploadPlugin extends SshCommand { @CommandMetaData(name = "install", description = "Download and installs a plugin", hidden = true) public static class InstallPlugin extends SshCommand { @Argument(index = 0, required = true, metaVar = "<URL>|<ID>|<NAME>", usage = "the id, name, or the url of the plugin to download and install") protected String urlOrIdOrName; @Option(name = "--version", usage = "The specific version to install") private String version; @Override public void run() throws UnloggedFailure { IGitblit gitblit = getContext().getGitblit(); try { String ulc = urlOrIdOrName.toLowerCase(); if (ulc.startsWith("http://") || ulc.startsWith("https://")) { if (gitblit.installPlugin(urlOrIdOrName)) { stdout.println(String.format("Installed %s", urlOrIdOrName)); } else { new UnloggedFailure(1, String.format("Failed to install %s", urlOrIdOrName)); } } else { PluginRelease pv = gitblit.lookupRelease(urlOrIdOrName, version); if (pv == null) { throw new UnloggedFailure(1, String.format("Plugin \"%s\" is not in the registry!", urlOrIdOrName)); } if (gitblit.installPlugin(pv)) { stdout.println(String.format("Installed %s", urlOrIdOrName)); } else { throw new UnloggedFailure(1, String.format("Failed to install %s", urlOrIdOrName)); } } } catch (Exception e) { log.error("Failed to install " + urlOrIdOrName, e); throw new UnloggedFailure(1, String.format("Failed to install %s", urlOrIdOrName), e); } } } @CommandMetaData(name = "available", description = "List the available plugins") public static class AvailablePlugins extends ListFilterCommand<PluginRegistration> { @Option(name = "--refresh", aliases = { "-r" }, usage = "refresh the plugin registry") protected boolean refresh; @Override protected List<PluginRegistration> getItems() throws UnloggedFailure { IGitblit gitblit = getContext().getGitblit(); if (refresh) { gitblit.refreshRegistry(); } List<PluginRegistration> list = gitblit.getRegisteredPlugins(); return list; } @Override protected boolean matches(String filter, PluginRegistration t) { return t.id.matches(filter) || t.name.matches(filter); } @Override protected void asTable(List<PluginRegistration> list) { String[] headers; if (verbose) { String [] h = { "Name", "Description", "Installed", "Release", "State", "Id", "Provider" }; headers = h; } else { String [] h = { "Name", "Description", "Installed", "Release", "State" }; headers = h; } Object[][] data = new Object[list.size()][]; for (int i = 0; i < list.size(); i++) { PluginRegistration p = list.get(i); if (verbose) { data[i] = new Object[] {p.name, p.description, p.installedRelease, p.currentRelease, p.getInstallState(), p.id, p.provider}; } else { data[i] = new Object[] {p.name, p.description, p.installedRelease, p.currentRelease, p.getInstallState()}; } } stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS)); } @Override protected void asTabbed(List<PluginRegistration> list) { for (PluginRegistration p : list) { if (verbose) { outTabbed(p.name, p.description, p.currentRelease, p.getInstallState(), p.id, p.provider); } else { outTabbed(p.name, p.description, p.currentRelease, p.getInstallState()); } } } } }