throw new UnsupportedOperationException();
}
+ @Override
+ public File getEditionDownloadedPluginsDir() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public File getUninstalledPluginsDir() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public File getEditionUninstalledPluginsDir() {
+ throw new UnsupportedOperationException();
+ }
+
}
}
/**
* Directory accessible by scanners through web server
- * @return a non-null directory that MAY exist
+ * @return a directory which may or not exist
*/
File getDeployDir();
/**
* Files of plugins published by web server for scanners
- * @return a non-null directory that MAY exist
+ * @return a directory which may or not exist
*/
File getDeployedPluginsDir();
/**
* Directory of plugins downloaded through update center. Files
* will be moved to {@link #getInstalledPluginsDir()} on startup.
- * @return an existing directory
+ * @return a directory which may or not exist
*/
File getDownloadedPluginsDir();
+ /**
+ * Directory of commercial plugins downloaded as part of the installation of an edition. Files
+ * will be moved to {@link #getInstalledPluginsDir()} on startup.
+ * @return a directory which may or not exist
+ */
+ File getEditionDownloadedPluginsDir();
+
/**
* Directory of currently installed plugins. Used at startup.
- * @return an existing directory
+ * @return a directory which may or not exist
*/
File getInstalledPluginsDir();
/**
* Directory of the plugins packaged by default with application. These
* plugins are installed during the first fresh startup.
- * @return an existing directory
+ * @return a directory which may or not exist
*/
File getBundledPluginsDir();
* @return an existing file
*/
File getPluginIndex();
+
+ /**
+ * Directory where plugins to be uninstalled are moved to.
+ * @return a directory which may or not exist
+ */
+ File getUninstalledPluginsDir();
+
+ /**
+ * Directory where plugins that are part of an edition and are to be uninstalled are moved to.
+ * @return a directory which may or not exist
+ */
+ File getEditionUninstalledPluginsDir();
}
private final File tempDir;
private final File dataDir;
private final File deployDir;
+ private final File uninstallDir;
+ private final File editionUninstallDir;
public ServerFileSystemImpl(Configuration config) {
this.homeDir = new File(config.get(ProcessProperties.PATH_HOME).get());
this.tempDir = new File(config.get(ProcessProperties.PATH_TEMP).get());
this.dataDir = new File(config.get(ProcessProperties.PATH_DATA).get());
this.deployDir = new File(this.dataDir, TomcatContexts.WEB_DEPLOY_PATH_RELATIVE_TO_DATA_DIR);
+ this.uninstallDir = new File(getTempDir(), "uninstalled-plugins");
+ this.editionUninstallDir = new File(getTempDir(), "uninstalled-edition-plugins");
}
@Override
return new File(getHomeDir(), "extensions/downloads");
}
+ @Override
+ public File getEditionDownloadedPluginsDir() {
+ return new File(getHomeDir(), "extensions/new-edition");
+ }
+
@Override
public File getInstalledPluginsDir() {
return new File(getHomeDir(), "extensions/plugins");
return new File(getDeployDir(), "plugins/index.txt");
}
+ @Override
+ public File getUninstalledPluginsDir() {
+ return uninstallDir;
+ }
+
+ @Override
+ public File getEditionUninstalledPluginsDir() {
+ return editionUninstallDir;
+ }
+
}
import org.sonar.server.platform.ws.SystemWs;
import org.sonar.server.platform.ws.UpgradesAction;
import org.sonar.server.plugins.PluginDownloader;
+import org.sonar.server.plugins.PluginUninstaller;
import org.sonar.server.plugins.ServerExtensionInstaller;
+import org.sonar.server.plugins.edition.EditionInstaller;
+import org.sonar.server.plugins.edition.EditionInstallerExecutor;
+import org.sonar.server.plugins.edition.EditionPluginDownloader;
+import org.sonar.server.plugins.edition.EditionPluginUninstaller;
import org.sonar.server.plugins.privileged.PrivilegedPluginsBootstraper;
import org.sonar.server.plugins.privileged.PrivilegedPluginsStopper;
import org.sonar.server.plugins.ws.AvailableAction;
LogServerId.class,
LogOAuthWarning.class,
PluginDownloader.class,
+ PluginUninstaller.class,
DeprecatedViews.class,
PageRepository.class,
ResourceTypes.class,
IndexDefinitions.class,
WebPagesFilter.class,
+ // edition
+ EditionInstaller.class,
+ EditionPluginDownloader.class,
+ EditionInstallerExecutor.class,
+ EditionPluginUninstaller.class,
+
// batch
BatchWsModule.class,
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.plugins;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.apache.commons.io.FileUtils;
+import org.picocontainer.Startable;
+import org.sonar.api.utils.HttpDownloader;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.updatecenter.common.Release;
+
+import static org.apache.commons.io.FileUtils.copyFile;
+import static org.apache.commons.io.FileUtils.copyFileToDirectory;
+import static org.apache.commons.io.FileUtils.forceMkdir;
+import static org.apache.commons.io.FileUtils.toFile;
+import static org.apache.commons.lang.StringUtils.substringAfterLast;
+import static org.sonar.core.util.FileUtils.deleteQuietly;
+
+public class AbstractPluginDownloader implements Startable {
+ private static final Logger LOG = Loggers.get(AbstractPluginDownloader.class);
+
+ private static final String TMP_SUFFIX = "tmp";
+ private static final String PLUGIN_EXTENSION = "jar";
+
+ private final HttpDownloader downloader;
+ private final File downloadDir;
+
+ protected AbstractPluginDownloader(File downloadDir, HttpDownloader downloader) {
+ this.downloadDir = downloadDir;
+ this.downloader = downloader;
+ }
+
+ /**
+ * Deletes the temporary files remaining from previous downloads
+ */
+ @Override
+ public void start() {
+ try {
+ forceMkdir(downloadDir);
+ cleanTempFiles();
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to create the directory: " + downloadDir, e);
+ }
+ }
+
+ @Override
+ public void stop() {
+ // Nothing to do
+ }
+
+ public void cancelDownloads() {
+ try {
+ if (downloadDir.exists()) {
+ org.sonar.core.util.FileUtils.cleanDirectory(downloadDir);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to clean the plugin downloads directory: " + downloadDir, e);
+ }
+ }
+
+ boolean hasDownloads() {
+ return !getDownloadedPluginFilenames().isEmpty();
+ }
+
+ List<String> getDownloadedPluginFilenames() {
+ List<String> names = new ArrayList<>();
+ for (File file : listPlugins(this.downloadDir)) {
+ names.add(file.getName());
+ }
+ return names;
+ }
+
+ /**
+ * @return the list of download plugins as {@link PluginInfo} instances
+ */
+ public Collection<PluginInfo> getDownloadedPlugins() {
+ return listPlugins(this.downloadDir)
+ .stream()
+ .map(PluginInfo::create)
+ .collect(MoreCollectors.toList());
+ }
+
+ protected void download(Release release) {
+ try {
+ downloadRelease(release);
+ } catch (Exception e) {
+ String message = String.format("Fail to download the plugin (%s, version %s) from %s (error is : %s)",
+ release.getArtifact().getKey(), release.getVersion().getName(), release.getDownloadUrl(), e.getMessage());
+ LOG.debug(message, e);
+ throw new IllegalStateException(message, e);
+ }
+ }
+
+ private void downloadRelease(Release release) throws URISyntaxException, IOException {
+ String url = release.getDownloadUrl();
+
+ URI uri = new URI(url);
+ if (url.startsWith("file:")) {
+ // used for tests
+ File file = toFile(uri.toURL());
+ copyFileToDirectory(file, downloadDir);
+ } else {
+ String filename = substringAfterLast(uri.getPath(), "/");
+ if (!filename.endsWith("." + PLUGIN_EXTENSION)) {
+ filename = release.getKey() + "-" + release.getVersion() + "." + PLUGIN_EXTENSION;
+ }
+ File targetFile = new File(downloadDir, filename);
+ File tempFile = new File(downloadDir, filename + "." + TMP_SUFFIX);
+ downloader.download(uri, tempFile);
+ copyFile(tempFile, targetFile);
+ deleteQuietly(tempFile);
+ }
+ }
+
+ protected void cleanTempFiles() {
+ Collection<File> tempFiles = FileUtils.listFiles(downloadDir, new String[] {TMP_SUFFIX}, false);
+ for (File tempFile : tempFiles) {
+ deleteQuietly(tempFile);
+ }
+ }
+
+ private static Collection<File> listPlugins(File dir) {
+ return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.plugins;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import org.apache.commons.io.FileUtils;
+import org.picocontainer.Startable;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.util.stream.MoreCollectors;
+
+import static java.lang.String.format;
+import static org.apache.commons.io.FileUtils.forceMkdir;
+
+public abstract class AbstractPluginUninstaller implements Startable {
+ private static final String PLUGIN_EXTENSION = "jar";
+
+ private final ServerPluginRepository serverPluginRepository;
+ private final File uninstallDir;
+
+ protected AbstractPluginUninstaller(ServerPluginRepository serverPluginRepository, File uninstallDir) {
+ this.serverPluginRepository = serverPluginRepository;
+ this.uninstallDir = uninstallDir;
+ }
+
+ @Override
+ public void start() {
+ try {
+ forceMkdir(uninstallDir);
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to create the directory: " + uninstallDir, e);
+ }
+ }
+
+ @Override
+ public void stop() {
+ // Nothing to do
+ }
+
+ public void uninstall(String pluginKey) {
+ ensurePluginIsInstalled(pluginKey);
+ serverPluginRepository.uninstall(pluginKey, uninstallDir);
+ }
+
+ public void cancelUninstalls() {
+ serverPluginRepository.cancelUninstalls(uninstallDir);
+ }
+
+ /**
+ * @return the list of plugins to be uninstalled as {@link PluginInfo} instances
+ */
+ public Collection<PluginInfo> getUninstalledPlugins() {
+ return listJarFiles(uninstallDir)
+ .stream()
+ .map(PluginInfo::create)
+ .collect(MoreCollectors.toList());
+ }
+
+ private static Collection<File> listJarFiles(File dir) {
+ if (dir.exists()) {
+ return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false);
+ }
+ return Collections.emptyList();
+ }
+
+ private void ensurePluginIsInstalled(String key) {
+ if (!serverPluginRepository.hasPlugin(key)) {
+ throw new IllegalArgumentException(format("Plugin [%s] is not installed", key));
+ }
+ }
+
+}
package org.sonar.server.plugins;
import com.google.common.base.Optional;
-import java.io.File;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
-import org.apache.commons.io.FileUtils;
-import org.picocontainer.Startable;
import org.sonar.api.utils.HttpDownloader;
-import org.sonar.api.utils.SonarException;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.server.platform.ServerFileSystem;
import org.sonar.updatecenter.common.Release;
import org.sonar.updatecenter.common.UpdateCenter;
import org.sonar.updatecenter.common.Version;
-import static org.apache.commons.io.FileUtils.copyFile;
-import static org.apache.commons.io.FileUtils.copyFileToDirectory;
-import static org.apache.commons.io.FileUtils.forceMkdir;
-import static org.apache.commons.io.FileUtils.toFile;
-import static org.apache.commons.lang.StringUtils.substringAfterLast;
-import static org.sonar.core.util.FileUtils.deleteQuietly;
import static org.sonar.server.ws.WsUtils.checkRequest;
/**
* Downloads plugins from update center. Files are copied in the directory extensions/downloads and then
* moved to extensions/plugins after server restart.
*/
-public class PluginDownloader implements Startable {
-
- private static final Logger LOG = Loggers.get(PluginDownloader.class);
- private static final String TMP_SUFFIX = "tmp";
- private static final String PLUGIN_EXTENSION = "jar";
-
+public class PluginDownloader extends AbstractPluginDownloader {
private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
- private final HttpDownloader downloader;
- private final File downloadDir;
- public PluginDownloader(UpdateCenterMatrixFactory updateCenterMatrixFactory, HttpDownloader downloader,
- ServerFileSystem fileSystem) {
+ public PluginDownloader(UpdateCenterMatrixFactory updateCenterMatrixFactory, HttpDownloader downloader, ServerFileSystem fileSystem) {
+ super(fileSystem.getDownloadedPluginsDir(), downloader);
this.updateCenterMatrixFactory = updateCenterMatrixFactory;
- this.downloader = downloader;
- this.downloadDir = fileSystem.getDownloadedPluginsDir();
- }
-
- /**
- * Deletes the temporary files remaining from previous downloads
- */
- @Override
- public void start() {
- try {
- forceMkdir(downloadDir);
- for (File tempFile : listTempFile(this.downloadDir)) {
- deleteQuietly(tempFile);
- }
- } catch (IOException e) {
- throw new IllegalStateException("Fail to create the directory: " + downloadDir, e);
- }
- }
-
- @Override
- public void stop() {
- // Nothing to do
- }
-
- public void cancelDownloads() {
- try {
- if (downloadDir.exists()) {
- org.sonar.core.util.FileUtils.cleanDirectory(downloadDir);
- }
- } catch (IOException e) {
- throw new IllegalStateException("Fail to clean the plugin downloads directory: " + downloadDir, e);
- }
- }
-
- public boolean hasDownloads() {
- return !getDownloadedPluginFilenames().isEmpty();
- }
-
- public List<String> getDownloadedPluginFilenames() {
- List<String> names = new ArrayList<>();
- for (File file : listPlugins(this.downloadDir)) {
- names.add(file.getName());
- }
- return names;
- }
-
- /**
- * @return the list of download plugins as {@link PluginInfo} instances
- */
- public Collection<PluginInfo> getDownloadedPlugins() {
- return listPlugins(this.downloadDir)
- .stream()
- .map(PluginInfo::create)
- .collect(MoreCollectors.toList());
}
public void download(String pluginKey, Version version) {
if (updateCenter.isPresent()) {
List<Release> installablePlugins = updateCenter.get().findInstallablePlugins(pluginKey, version);
checkRequest(!installablePlugins.isEmpty(), "Error while downloading plugin '%s' with version '%s'. No compatible plugin found.", pluginKey, version.getName());
- for (Release release : installablePlugins) {
- try {
- downloadRelease(release);
- } catch (Exception e) {
- String message = String.format("Fail to download the plugin (%s, version %s) from %s (error is : %s)",
- release.getArtifact().getKey(), release.getVersion().getName(), release.getDownloadUrl(), e.getMessage());
- LOG.debug(message);
- throw new SonarException(message, e);
- }
- }
- }
- }
-
- private void downloadRelease(Release release) throws URISyntaxException, IOException {
- String url = release.getDownloadUrl();
-
- URI uri = new URI(url);
- if (url.startsWith("file:")) {
- // used for tests
- File file = toFile(uri.toURL());
- copyFileToDirectory(file, downloadDir);
- } else {
- String filename = substringAfterLast(uri.getPath(), "/");
- if (!filename.endsWith("." + PLUGIN_EXTENSION)) {
- filename = release.getKey() + "-" + release.getVersion() + "." + PLUGIN_EXTENSION;
+ for (Release r : installablePlugins) {
+ super.download(r);
}
- File targetFile = new File(downloadDir, filename);
- File tempFile = new File(downloadDir, filename + "." + TMP_SUFFIX);
- downloader.download(uri, tempFile);
- copyFile(tempFile, targetFile);
- deleteQuietly(tempFile);
}
}
-
- private static Collection<File> listTempFile(File dir) {
- return FileUtils.listFiles(dir, new String[] {TMP_SUFFIX}, false);
- }
-
- private static Collection<File> listPlugins(File dir) {
- return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false);
- }
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.plugins;
+
+import org.sonar.server.platform.ServerFileSystem;
+
+public class PluginUninstaller extends AbstractPluginUninstaller {
+ public PluginUninstaller(ServerPluginRepository serverPluginRepository, ServerFileSystem fs) {
+ super(serverPluginRepository, fs.getUninstalledPluginsDir());
+ }
+}
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginLoader;
import org.sonar.core.platform.PluginRepository;
-import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.server.platform.ServerFileSystem;
import org.sonar.updatecenter.common.Version;
private final Map<String, Plugin> pluginInstancesByKeys = new HashMap<>();
private final Map<ClassLoader, String> keysByClassLoader = new HashMap<>();
- public ServerPluginRepository(SonarRuntime runtime, ServerUpgradeStatus upgradeStatus,
- ServerFileSystem fs, PluginLoader loader) {
+ public ServerPluginRepository(SonarRuntime runtime, ServerUpgradeStatus upgradeStatus, ServerFileSystem fs, PluginLoader loader) {
this.runtime = runtime;
this.upgradeStatus = upgradeStatus;
this.fs = fs;
loadPreInstalledPlugins();
copyBundledPlugins();
moveDownloadedPlugins();
+ moveDownloadedEditionPlugins();
unloadIncompatiblePlugins();
logInstalledPlugins();
loadInstances();
}
}
+ private void moveDownloadedEditionPlugins() {
+ if (fs.getEditionDownloadedPluginsDir().exists()) {
+ for (File sourceFile : listJarFiles(fs.getEditionDownloadedPluginsDir())) {
+ overrideAndRegisterPlugin(sourceFile, true);
+ }
+ }
+ }
+
/**
* Copies the plugins bundled with SonarQube distribution to directory extensions/plugins.
* Does nothing if not a fresh installation.
/**
* Uninstall a plugin and its dependents
*/
- public void uninstall(String pluginKey) {
- checkState(started.get(), NOT_STARTED_YET);
-
+ public void uninstall(String pluginKey, File uninstallDir) {
Set<String> uninstallKeys = new HashSet<>();
uninstallKeys.add(pluginKey);
appendDependentPluginKeys(pluginKey, uninstallKeys);
for (String uninstallKey : uninstallKeys) {
- PluginInfo info = pluginInfosByKeys.get(uninstallKey);
+ PluginInfo info = getPluginInfo(uninstallKey);
try {
LOG.info("Uninstalling plugin {} [{}]", info.getName(), info.getKey());
// we don't reuse info.getFile() just to be sure that file is located in from extensions/plugins
File masterFile = new File(fs.getInstalledPluginsDir(), info.getNonNullJarFile().getName());
- moveFileToDirectory(masterFile, uninstalledPluginsDir(), true);
+ moveFileToDirectory(masterFile, uninstallDir, true);
} catch (IOException e) {
throw new IllegalStateException(format("Fail to uninstall plugin %s [%s]", info.getName(), info.getKey()), e);
}
}
}
+ public void cancelUninstalls(File uninstallDir) {
+ for (File file : listJarFiles(uninstallDir)) {
+ try {
+ moveFileToDirectory(file, fs.getInstalledPluginsDir(), false);
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to cancel plugin uninstalls", e);
+ }
+ }
+ }
+
private void appendDependentPluginKeys(String pluginKey, Set<String> appendTo) {
- for (PluginInfo otherPlugin : pluginInfosByKeys.values()) {
+ for (PluginInfo otherPlugin : getPluginInfos()) {
if (!otherPlugin.getKey().equals(pluginKey)) {
for (PluginInfo.RequiredPlugin requirement : otherPlugin.getRequiredPlugins()) {
if (requirement.getKey().equals(pluginKey)) {
}
}
- public List<String> getUninstalledPluginFilenames() {
- return listJarFiles(uninstalledPluginsDir()).stream().map(File::getName).collect(MoreCollectors.toList());
- }
-
- /**
- * @return the list of plugins to be uninstalled as {@link PluginInfo} instances
- */
- public Collection<PluginInfo> getUninstalledPlugins() {
- return listJarFiles(uninstalledPluginsDir())
- .stream()
- .map(PluginInfo::create)
- .collect(MoreCollectors.toList());
- }
-
- public void cancelUninstalls() {
- for (File file : listJarFiles(uninstalledPluginsDir())) {
- try {
- moveFileToDirectory(file, fs.getInstalledPluginsDir(), false);
- } catch (IOException e) {
- throw new IllegalStateException("Fail to cancel plugin uninstalls", e);
- }
- }
- }
-
public Map<String, PluginInfo> getPluginInfosByKeys() {
return pluginInfosByKeys;
}
return pluginInfosByKeys.containsKey(key);
}
- /**
- * @return existing trash dir
- */
- private File uninstalledPluginsDir() {
- File dir = new File(fs.getTempDir(), "uninstalled-plugins");
- try {
- FileUtils.forceMkdir(dir);
- return dir;
- } catch (IOException e) {
- throw new IllegalStateException("Fail to create temp directory: " + dir.getAbsolutePath(), e);
- }
- }
-
private static Collection<File> listJarFiles(File dir) {
if (dir.exists()) {
return FileUtils.listFiles(dir, JAR_FILE_EXTENSIONS, false);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.plugins.edition;
+
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.server.plugins.ServerPluginRepository;
+
+public class EditionInstaller {
+ private final ReentrantLock lock = new ReentrantLock();
+ private final EditionInstallerExecutor executor;
+ private final EditionPluginDownloader editionPluginDownloader;
+ private final EditionPluginUninstaller editionPluginUninstaller;
+ private final ServerPluginRepository pluginRepository;
+
+ public EditionInstaller(EditionPluginDownloader editionDownloader, EditionPluginUninstaller editionPluginUninstaller,
+ ServerPluginRepository pluginRepository, EditionInstallerExecutor executor) {
+ this.editionPluginDownloader = editionDownloader;
+ this.editionPluginUninstaller = editionPluginUninstaller;
+ this.pluginRepository = pluginRepository;
+ this.executor = executor;
+ }
+
+ public void install(Set<String> editionPlugins) {
+ if (lock.tryLock()) {
+ try {
+ if (!requiresInstallationChange(editionPlugins)) {
+ return;
+ }
+
+ executor.execute(() -> asyncInstall(editionPlugins));
+ } catch (RuntimeException e) {
+ lock.unlock();
+ throw e;
+ }
+ } else {
+ throw new IllegalStateException("Another installation of an edition is already running");
+ }
+ }
+
+ public boolean requiresInstallationChange(Set<String> editionPluginKeys) {
+ return !pluginsToInstall(editionPluginKeys).isEmpty() || !pluginsToRemove(editionPluginKeys).isEmpty();
+ }
+
+ private void asyncInstall(Set<String> editionPluginKeys) {
+ try {
+ // TODO clean previously staged edition installations, or fail?
+ // editionPluginDownloader.cancelDownloads();
+ // editionPluginUninstaller.cancelUninstalls();
+ editionPluginDownloader.installEdition(pluginsToInstall(editionPluginKeys));
+ for (String pluginKey : pluginsToRemove(editionPluginKeys)) {
+ editionPluginUninstaller.uninstall(pluginKey);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private Set<String> pluginsToInstall(Set<String> editionPluginKeys) {
+ Set<String> installedKeys = pluginRepository.getPluginInfosByKeys().keySet();
+ return editionPluginKeys.stream()
+ .filter(p -> !installedKeys.contains(p))
+ .collect(Collectors.toSet());
+ }
+
+ private Set<String> pluginsToRemove(Set<String> editionPluginKeys) {
+ Set<String> installedCommercialPluginKeys = pluginRepository.getPluginInfos().stream()
+ .filter(EditionInstaller::isSonarSourceCommercialPlugin)
+ .map(PluginInfo::getKey)
+ .collect(Collectors.toSet());
+
+ return installedCommercialPluginKeys.stream()
+ .filter(p -> !editionPluginKeys.contains(p))
+ .collect(Collectors.toSet());
+ }
+
+ private static boolean isSonarSourceCommercialPlugin(PluginInfo pluginInfo) {
+ return "Commercial".equals(pluginInfo.getLicense()) && "SonarSource".equals(pluginInfo.getOrganizationName());
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.plugins.edition;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import org.sonar.server.util.AbstractStoppableExecutorService;
+
+public class EditionInstallerExecutor extends AbstractStoppableExecutorService<ExecutorService> {
+ public EditionInstallerExecutor() {
+ super(createExecutor());
+ }
+
+ @Override
+ public void execute(Runnable r) {
+ delegate.submit(r);
+ }
+
+ private static ExecutorService createExecutor() {
+ ThreadFactory threadFactory = new ThreadFactoryBuilder()
+ .setDaemon(true)
+ .setNameFormat("edition-installation-%d")
+ .build();
+ return Executors.newSingleThreadExecutor(threadFactory);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.plugins.edition;
+
+import com.google.common.base.Optional;
+import java.util.HashSet;
+import java.util.Set;
+import org.sonar.api.utils.HttpDownloader;
+import org.sonar.server.platform.ServerFileSystem;
+import org.sonar.server.plugins.AbstractPluginDownloader;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
+import org.sonar.updatecenter.common.Release;
+import org.sonar.updatecenter.common.UpdateCenter;
+import org.sonar.updatecenter.common.Version;
+
+public class EditionPluginDownloader extends AbstractPluginDownloader {
+ private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
+
+ public EditionPluginDownloader(UpdateCenterMatrixFactory updateCenterMatrixFactory, HttpDownloader downloader, ServerFileSystem fileSystem) {
+ super(fileSystem.getEditionDownloadedPluginsDir(), downloader);
+ this.updateCenterMatrixFactory = updateCenterMatrixFactory;
+ }
+
+ public void installEdition(Set<String> pluginKeys) {
+ try {
+ Optional<UpdateCenter> updateCenter = updateCenterMatrixFactory.getUpdateCenter(true);
+ if (updateCenter.isPresent()) {
+ Set<Release> pluginsToInstall = new HashSet<>();
+ for (String pluginKey : pluginKeys) {
+ pluginsToInstall.addAll(updateCenter.get().findInstallablePlugins(pluginKey, Version.create("")));
+ }
+
+ for (Release r : pluginsToInstall) {
+ super.download(r);
+ }
+ }
+ } catch (Exception e) {
+ cleanTempFiles();
+ throw e;
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.plugins.edition;
+
+import org.sonar.server.platform.ServerFileSystem;
+import org.sonar.server.plugins.AbstractPluginUninstaller;
+import org.sonar.server.plugins.ServerPluginRepository;
+
+public class EditionPluginUninstaller extends AbstractPluginUninstaller {
+
+ public EditionPluginUninstaller(ServerPluginRepository serverPluginRepository, ServerFileSystem fs) {
+ super(serverPluginRepository, fs.getEditionUninstalledPluginsDir());
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.plugins.edition;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.server.plugins.PluginDownloader;
-import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.PluginUninstaller;
import org.sonar.server.user.UserSession;
public class CancelAllAction implements PluginsWsAction {
private final PluginDownloader pluginDownloader;
- private final ServerPluginRepository pluginRepository;
+ private final PluginUninstaller pluginUninstaller;
private final UserSession userSession;
- public CancelAllAction(PluginDownloader pluginDownloader, ServerPluginRepository pluginRepository, UserSession userSession) {
+ public CancelAllAction(PluginDownloader pluginDownloader, PluginUninstaller pluginUninstaller, UserSession userSession) {
this.pluginDownloader = pluginDownloader;
- this.pluginRepository = pluginRepository;
+ this.pluginUninstaller = pluginUninstaller;
this.userSession = userSession;
}
userSession.checkIsSystemAdministrator();
pluginDownloader.cancelDownloads();
- pluginRepository.cancelUninstalls();
+ pluginUninstaller.cancelUninstalls();
response.noContent();
}
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.core.platform.PluginInfo;
import org.sonar.server.plugins.PluginDownloader;
+import org.sonar.server.plugins.PluginUninstaller;
import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.plugins.UpdateCenterMatrixFactory;
import org.sonar.server.user.UserSession;
private final ServerPluginRepository installer;
private final PluginWSCommons pluginWSCommons;
private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
+ private final PluginUninstaller pluginUninstaller;
public PendingAction(UserSession userSession, PluginDownloader pluginDownloader,
- ServerPluginRepository installer,
+ ServerPluginRepository installer, PluginUninstaller pluginUninstaller,
PluginWSCommons pluginWSCommons, UpdateCenterMatrixFactory updateCenterMatrixFactory) {
this.userSession = userSession;
this.pluginDownloader = pluginDownloader;
this.installer = installer;
+ this.pluginUninstaller = pluginUninstaller;
this.pluginWSCommons = pluginWSCommons;
this.updateCenterMatrixFactory = updateCenterMatrixFactory;
}
}
private void writePlugins(JsonWriter json, Map<String, Plugin> compatiblePluginsByKey) {
- Collection<PluginInfo> uninstalledPlugins = installer.getUninstalledPlugins();
+ Collection<PluginInfo> uninstalledPlugins = pluginUninstaller.getUninstalledPlugins();
Collection<PluginInfo> downloadedPlugins = pluginDownloader.getDownloadedPlugins();
Collection<PluginInfo> installedPlugins = installer.getPluginInfos();
MatchPluginKeys matchPluginKeys = new MatchPluginKeys(from(installedPlugins).transform(PluginInfoToKey.INSTANCE).toSet());
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
-import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.PluginUninstaller;
import org.sonar.server.user.UserSession;
-import static java.lang.String.format;
-
/**
* Implementation of the {@code uninstall} action for the Plugins WebService.
*/
public class UninstallAction implements PluginsWsAction {
private static final String PARAM_KEY = "key";
- private final ServerPluginRepository pluginRepository;
+ private final PluginUninstaller pluginUninstaller;
private final UserSession userSession;
- public UninstallAction(ServerPluginRepository pluginRepository, UserSession userSession) {
- this.pluginRepository = pluginRepository;
+ public UninstallAction(PluginUninstaller pluginUninstaller, UserSession userSession) {
+ this.pluginUninstaller = pluginUninstaller;
this.userSession = userSession;
}
userSession.checkIsSystemAdministrator();
String key = request.mandatoryParam(PARAM_KEY);
- ensurePluginIsInstalled(key);
- pluginRepository.uninstall(key);
+ pluginUninstaller.uninstall(key);
response.noContent();
}
- // FIXME should be moved to ServerPluginRepository#uninstall(String)
- private void ensurePluginIsInstalled(String key) {
- if (!pluginRepository.hasPlugin(key)) {
- throw new IllegalArgumentException(format("Plugin [%s] is not installed", key));
- }
- }
}
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.sonar.api.utils.HttpDownloader;
-import org.sonar.api.utils.SonarException;
import org.sonar.core.platform.PluginInfo;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.platform.ServerFileSystem;
when(updateCenterMatrixFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.of(updateCenter));
httpDownloader = mock(HttpDownloader.class);
- doAnswer(new Answer() {
+ doAnswer(new Answer<Void>() {
@Override
- public Object answer(InvocationOnMock inv) throws Throwable {
+ public Void answer(InvocationOnMock inv) throws Throwable {
File toFile = (File) inv.getArguments()[1];
touch(toFile);
return null;
try {
pluginDownloader.download("foo", create("1.0"));
fail();
- } catch (SonarException e) {
+ } catch (IllegalStateException e) {
// ok
}
}
try {
pluginDownloader.download("foo", create("1.0"));
fail();
- } catch (SonarException e) {
+ } catch (IllegalStateException e) {
// ok
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.plugins;
+
+import java.io.File;
+import java.io.IOException;
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.server.platform.ServerFileSystem;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class PluginUninstallerTest {
+ @Rule
+ public TemporaryFolder testFolder = new TemporaryFolder();
+
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+
+ private File uninstallDir;
+ private PluginUninstaller underTest;
+ private ServerPluginRepository serverPluginRepository;
+ private ServerFileSystem fs;
+
+ @Before
+ public void setUp() throws IOException {
+ serverPluginRepository = mock(ServerPluginRepository.class);
+ uninstallDir = testFolder.newFolder("uninstall");
+ fs = mock(ServerFileSystem.class);
+ when(fs.getUninstalledPluginsDir()).thenReturn(uninstallDir);
+ underTest = new PluginUninstaller(serverPluginRepository, fs);
+ }
+
+ @Test
+ public void uninstall() throws Exception {
+ when(serverPluginRepository.hasPlugin("plugin")).thenReturn(true);
+ underTest.uninstall("plugin");
+ verify(serverPluginRepository).uninstall("plugin", uninstallDir);
+ }
+
+ @Test
+ public void fail_uninstall_if_plugin_not_installed() {
+ when(serverPluginRepository.hasPlugin("plugin")).thenReturn(false);
+ exception.expect(IllegalArgumentException.class);
+ exception.expectMessage("Plugin [plugin] is not installed");
+ underTest.uninstall("plugin");
+ verifyZeroInteractions(serverPluginRepository);
+ }
+
+ @Test
+ public void create_uninstall_dir() {
+ File dir = new File(testFolder.getRoot(), "dir");
+ when(fs.getUninstalledPluginsDir()).thenReturn(dir);
+ underTest = new PluginUninstaller(serverPluginRepository, fs);
+ underTest.start();
+ assertThat(dir).isDirectory();
+ }
+
+ @Test
+ public void cancel() {
+ underTest.cancelUninstalls();
+ verify(serverPluginRepository).cancelUninstalls(uninstallDir);
+ verifyNoMoreInteractions(serverPluginRepository);
+ }
+
+ @Test
+ public void list_uninstalled_plugins() throws IOException {
+ new File(uninstallDir, "file1").createNewFile();
+ copyTestPluginTo("test-base-plugin", uninstallDir);
+ assertThat(underTest.getUninstalledPlugins()).extracting("key").containsOnly("testbase");
+ }
+
+ private File copyTestPluginTo(String testPluginName, File toDir) throws IOException {
+ File jar = TestProjectUtils.jarOf(testPluginName);
+ // file is copied because it's supposed to be moved by the test
+ FileUtils.copyFileToDirectory(jar, toDir);
+ return new File(toDir, jar.getName());
+ }
+}
assertThat(underTest.getPluginInfos()).isEmpty();
assertThat(underTest.getPluginInfosByKeys()).isEmpty();
- assertThat(underTest.getUninstalledPlugins()).isEmpty();
assertThat(underTest.hasPlugin("testbase")).isFalse();
}
@Test
public void uninstall() throws Exception {
File installedJar = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir());
-
+ File uninstallDir = temp.newFolder("uninstallDir");
+
underTest.start();
assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase");
- underTest.uninstall("testbase");
+ underTest.uninstall("testbase", uninstallDir);
assertThat(installedJar).doesNotExist();
// still up. Will be dropped after next startup
assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase");
- assertThat(underTest.getUninstalledPluginFilenames()).containsOnly(installedJar.getName());
- assertThat(underTest.getUninstalledPlugins()).extracting("key").containsOnly("testbase");
+ assertThat(uninstallDir.list()).containsOnly(installedJar.getName());
}
@Test
public void uninstall_dependents() throws Exception {
File base = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir());
File extension = copyTestPluginTo("test-require-plugin", fs.getInstalledPluginsDir());
+ File uninstallDir = temp.newFolder("uninstallDir");
underTest.start();
assertThat(underTest.getPluginInfos()).hasSize(2);
- underTest.uninstall("testbase");
-
+ underTest.uninstall("testbase", uninstallDir);
assertThat(base).doesNotExist();
assertThat(extension).doesNotExist();
- assertThat(underTest.getUninstalledPluginFilenames()).containsOnly(base.getName(), extension.getName());
- assertThat(underTest.getUninstalledPlugins()).extracting("key").containsOnly("testbase", "testrequire");
- }
-
- @Test
- public void cancel_uninstall() throws Exception {
- File base = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir());
- underTest.start();
-
- underTest.uninstall("testbase");
- assertThat(base).doesNotExist();
-
- underTest.cancelUninstalls();
- assertThat(base).exists();
- assertThat(underTest.getUninstalledPluginFilenames()).isEmpty();
- assertThat(underTest.getUninstalledPlugins()).isEmpty();
+ assertThat(uninstallDir.list()).containsOnly(base.getName(), extension.getName());
}
@Test
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.plugins.edition;
+
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+public class EditionInstallerExecutorTest {
+ @Test
+ public void execute() {
+ Runnable r = mock(Runnable.class);
+ new EditionInstallerExecutor().execute(r);
+ verify(r, timeout(5000)).run();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.plugins.edition;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.server.plugins.ServerPluginRepository;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+public class EditionInstallerTest {
+ private static final String pluginKey = "key";
+ @Mock
+ private EditionPluginDownloader downloader;
+ @Mock
+ private EditionPluginUninstaller uninstaller;
+ @Mock
+ private ServerPluginRepository pluginRepository;
+
+ private EditionInstallerExecutor executor = new EditionInstallerExecutor() {
+ public void execute(Runnable r) {
+ r.run();
+ }
+ };
+
+ private EditionInstaller installer;
+
+ @Before
+ public void before() {
+ MockitoAnnotations.initMocks(this);
+ installer = new EditionInstaller(downloader, uninstaller, pluginRepository, executor);
+ }
+
+ @Test
+ public void install() {
+ installer.install(Collections.singleton(pluginKey));
+ verify(downloader).installEdition(Collections.singleton(pluginKey));
+ }
+
+ @Test
+ public void check_plugins_to_install_and_remove() {
+ PluginInfo commercial1 = createPluginInfo("p1", true);
+ PluginInfo commercial2 = createPluginInfo("p2", true);
+ PluginInfo open1 = createPluginInfo("p3", false);
+ mockPluginRepository(commercial1, commercial2, open1);
+
+ Set<String> editionPlugins = new HashSet<>();
+ editionPlugins.add("p1");
+ editionPlugins.add("p4");
+ installer.install(editionPlugins);
+
+ verify(downloader).installEdition(Collections.singleton("p4"));
+ verify(uninstaller).uninstall("p2");
+ verifyNoMoreInteractions(uninstaller);
+ verifyNoMoreInteractions(downloader);
+
+ }
+
+ private void mockPluginRepository(PluginInfo... installedPlugins) {
+ Map<String, PluginInfo> pluginsByKey = Arrays.asList(installedPlugins).stream().collect(Collectors.toMap(p -> p.getKey(), p -> p));
+ when(pluginRepository.getPluginInfosByKeys()).thenReturn(pluginsByKey);
+ when(pluginRepository.getPluginInfos()).thenReturn(Arrays.asList(installedPlugins));
+ }
+
+ private static PluginInfo createPluginInfo(String pluginKey, boolean commercial) {
+ PluginInfo info = new PluginInfo(pluginKey);
+ if (commercial) {
+ info.setOrganizationName("SonarSource");
+ info.setLicense("Commercial");
+ }
+ return info;
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.plugins.edition;
+
+import com.google.common.base.Optional;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sonar.api.utils.HttpDownloader;
+import org.sonar.server.platform.ServerFileSystem;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
+import org.sonar.updatecenter.common.Release;
+import org.sonar.updatecenter.common.UpdateCenter;
+import org.sonar.updatecenter.common.Version;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class EditionPluginDownloaderTest {
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+ @Mock
+ private UpdateCenterMatrixFactory updateCenterMatrixFactory;
+ @Mock
+ private UpdateCenter updateCenter;
+ @Mock
+ private ServerFileSystem fs;
+ @Mock
+ private HttpDownloader httpDownloader;
+
+ private File downloadDir;
+ private EditionPluginDownloader downloader;
+
+ @Before
+ public void setUp() throws IOException {
+ MockitoAnnotations.initMocks(this);
+ downloadDir = temp.newFolder();
+ when(updateCenterMatrixFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.of(updateCenter));
+ when(fs.getEditionDownloadedPluginsDir()).thenReturn(downloadDir);
+ downloader = new EditionPluginDownloader(updateCenterMatrixFactory, httpDownloader, fs);
+ }
+
+ @Test
+ public void download_plugin_to_tmp_and_rename_it() throws IOException, URISyntaxException {
+ String url = "http://host/plugin.jar";
+ Release release = createRelease("pluginKey", "1.0", url);
+
+ // mock file downloaded
+ File tmp = new File(downloadDir, "plugin.jar.tmp");
+ tmp.createNewFile();
+
+ when(updateCenter.findInstallablePlugins("pluginKey", Version.create(""))).thenReturn(Collections.singletonList(release));
+ downloader.installEdition(Collections.singleton("pluginKey"));
+
+ verify(httpDownloader).download(new URI(url), tmp);
+ assertThat(tmp).doesNotExist();
+ assertThat(new File(downloadDir, "plugin.jar")).isFile();
+ }
+
+ private static Release createRelease(String key, String version, String url) {
+ Release release = mock(Release.class);
+ when(release.getKey()).thenReturn(key);
+ when(release.getVersion()).thenReturn(Version.create(version));
+ when(release.getDownloadUrl()).thenReturn(url);
+ return release;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.plugins.edition;
+
+import java.io.File;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.server.platform.ServerFileSystem;
+import org.sonar.server.plugins.ServerPluginRepository;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+public class EditionPluginUninstallerTest {
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Test
+ public void start_creates_uninstall_directory() {
+ File dir = new File(temp.getRoot(), "uninstall");
+ ServerPluginRepository repo = mock(ServerPluginRepository.class);
+ ServerFileSystem fs = mock(ServerFileSystem.class);
+ when(fs.getEditionUninstalledPluginsDir()).thenReturn(dir);
+ EditionPluginUninstaller uninstaller = new EditionPluginUninstaller(repo, fs);
+
+ uninstaller.start();
+ verify(fs).getEditionUninstalledPluginsDir();
+ verifyNoMoreInteractions(fs);
+ assertThat(dir).isDirectory();
+ }
+}
public abstract class AbstractUpdateCenterBasedPluginsWsActionTest {
protected static final String DUMMY_CONTROLLER_KEY = "dummy";
- protected static final String JSON_EMPTY_PLUGIN_LIST =
- "{" +
- " \"plugins\":" + "[]" +
- "}";
+ protected static final String JSON_EMPTY_PLUGIN_LIST = "{" +
+ " \"plugins\":" + "[]" +
+ "}";
protected static final Plugin PLUGIN_1 = Plugin.factory("pkey1").setName("p_name_1");
protected static final Plugin PLUGIN_2 = Plugin.factory("pkey2").setName("p_name_2").setDescription("p_desc_2");
protected static PluginUpdate pluginUpdate(String key, String name) {
return PluginUpdate.createWithStatus(
new Release(Plugin.factory(key).setName(name), Version.create("1.0")),
- COMPATIBLE
- );
+ COMPATIBLE);
}
@Before
import org.sonar.api.server.ws.WebService;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.plugins.PluginDownloader;
+import org.sonar.server.plugins.PluginUninstaller;
import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.WsTester;
public ExpectedException expectedException = ExpectedException.none();
private PluginDownloader pluginDownloader = mock(PluginDownloader.class);
- private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
- private CancelAllAction underTest = new CancelAllAction(pluginDownloader, pluginRepository, userSessionRule);
+ private PluginUninstaller pluginUninstaller = mock(PluginUninstaller.class);
+ private CancelAllAction underTest = new CancelAllAction(pluginDownloader, pluginUninstaller, userSessionRule);
private Request request = mock(Request.class);
private WsTester.TestResponse response = new WsTester.TestResponse();
underTest.handle(request, response);
verify(pluginDownloader, times(1)).cancelDownloads();
- verify(pluginRepository, times(1)).cancelUninstalls();
+ verify(pluginUninstaller, times(1)).cancelUninstalls();
assertThat(response.outputAsString()).isEmpty();
}
logInAsSystemAdministrator();
Version version = Version.create("1.0");
when(updateCenter.findAvailablePlugins()).thenReturn(ImmutableList.of(
- PluginUpdate.createWithStatus(new Release(Plugin.factory(PLUGIN_KEY), version), PluginUpdate.Status.COMPATIBLE)
- ));
+ PluginUpdate.createWithStatus(new Release(Plugin.factory(PLUGIN_KEY), version), PluginUpdate.Status.COMPATIBLE)));
WsTester.Result result = validRequest.execute();
import org.sonar.core.platform.PluginInfo;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.plugins.PluginDownloader;
+import org.sonar.server.plugins.PluginUninstaller;
import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.plugins.UpdateCenterMatrixFactory;
import org.sonar.server.tester.UserSessionRule;
public ExpectedException expectedException = ExpectedException.none();
private PluginDownloader pluginDownloader = mock(PluginDownloader.class);
+ private PluginUninstaller pluginUninstaller = mock(PluginUninstaller.class);
private ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class);
private UpdateCenterMatrixFactory updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class, RETURNS_DEEP_STUBS);
- private PendingAction underTest = new PendingAction(userSession, pluginDownloader, serverPluginRepository, new PluginWSCommons(), updateCenterMatrixFactory);
+ private PendingAction underTest = new PendingAction(userSession, pluginDownloader, serverPluginRepository,
+ pluginUninstaller, new PluginWSCommons(), updateCenterMatrixFactory);
private Request request = mock(Request.class);
private WsTester.TestResponse response = new WsTester.TestResponse();
@Test
public void verify_properties_displayed_in_json_per_removing_plugin() throws Exception {
logInAsSystemAdministrator();
- when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(newScmGitPluginInfo()));
+ when(pluginUninstaller.getUninstalledPlugins()).thenReturn(of(newScmGitPluginInfo()));
underTest.handle(request, response);
newUpdateCenter("scmgit");
when(serverPluginRepository.getPluginInfos()).thenReturn(of(installed));
- when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(removedPlugin));
+ when(pluginUninstaller.getUninstalledPlugins()).thenReturn(of(removedPlugin));
when(pluginDownloader.getDownloadedPlugins()).thenReturn(of(newPlugin, installed));
underTest.handle(request, response);
@Test
public void removing_plugins_are_sorted_and_unique() throws Exception {
logInAsSystemAdministrator();
- when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(
+ when(pluginUninstaller.getUninstalledPlugins()).thenReturn(of(
newPluginInfo(0).setName("Foo"),
newPluginInfo(3).setName("Bar"),
newPluginInfo(2).setName("Bar")));
createPluginUpdate("key1"),
createPluginUpdate("key0"),
createPluginUpdate("key2"),
- createPluginUpdate("key0")
- ));
+ createPluginUpdate("key0")));
assertThat(aggregates).hasSize(3);
assertThat(aggregates).extracting("plugin.key").containsOnlyOnce("key1", "key0", "key2");
Collection<PluginUpdateAggregator.PluginUpdateAggregate> aggregates = underTest.aggregate(ImmutableList.of(
pluginUpdate1,
pluginUpdate2,
- pluginUpdate3
- ));
+ pluginUpdate3));
assertThat(aggregates).hasSize(1);
Collection<PluginUpdate> releases = aggregates.iterator().next().getUpdates();
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.WebService;
import org.sonar.server.exceptions.ForbiddenException;
-import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.PluginUninstaller;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.WsTester;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
public class UninstallActionTest {
private static final String DUMMY_CONTROLLER_KEY = "dummy";
@Rule
public ExpectedException expectedException = ExpectedException.none();
- private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
- private UninstallAction underTest = new UninstallAction(pluginRepository, userSessionRule);
+ private PluginUninstaller pluginUninstaller = mock(PluginUninstaller.class);
+ private UninstallAction underTest = new UninstallAction(pluginUninstaller, userSessionRule);
private WsTester wsTester = new WsTester(new PluginsWs(underTest));
private Request invalidRequest = wsTester.newGetRequest(CONTROLLER_KEY, ACTION_KEY);
underTest.handle(invalidRequest, response);
}
- @Test
- public void IAE_is_raised_when_plugin_is_not_installed() throws Exception {
- logInAsSystemAdministrator();
-
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Plugin [findbugs] is not installed");
-
- underTest.handle(validRequest, response);
- }
-
@Test
public void if_plugin_is_installed_uninstallation_is_triggered() throws Exception {
logInAsSystemAdministrator();
- when(pluginRepository.hasPlugin(PLUGIN_KEY)).thenReturn(true);
underTest.handle(validRequest, response);
- verify(pluginRepository).uninstall(PLUGIN_KEY);
+ verify(pluginUninstaller).uninstall(PLUGIN_KEY);
assertThat(response.outputAsString()).isEmpty();
}
logInAsSystemAdministrator();
Version version = Version.create("1.0");
when(updateCenter.findPluginUpdates()).thenReturn(ImmutableList.of(
- PluginUpdate.createWithStatus(new Release(Plugin.factory(PLUGIN_KEY), version), Status.COMPATIBLE)
- ));
+ PluginUpdate.createWithStatus(new Release(Plugin.factory(PLUGIN_KEY), version), Status.COMPATIBLE)));
underTest.handle(validRequest, response);