From 54a47fb03ec189410fa6d53a5355b2116624fe33 Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Thu, 10 May 2018 21:46:42 +0200 Subject: [PATCH] SONAR-10591 add WS api/plugins/download --- .../platformlevel/PlatformLevel4.java | 2 + .../server/plugins/ws/DownloadAction.java | 89 ++++++++++ .../server/plugins/ws/DownloadActionTest.java | 158 ++++++++++++++++++ 3 files changed, 249 insertions(+) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/plugins/ws/DownloadAction.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/plugins/ws/DownloadActionTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 21d6a286cb1..2ca98540648 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -143,6 +143,7 @@ import org.sonar.server.plugins.privileged.PrivilegedPluginsBootstraper; import org.sonar.server.plugins.privileged.PrivilegedPluginsStopper; import org.sonar.server.plugins.ws.AvailableAction; import org.sonar.server.plugins.ws.CancelAllAction; +import org.sonar.server.plugins.ws.DownloadAction; import org.sonar.server.plugins.ws.InstallAction; import org.sonar.server.plugins.ws.InstalledAction; import org.sonar.server.plugins.ws.PendingAction; @@ -529,6 +530,7 @@ public class PlatformLevel4 extends PlatformLevel { PluginUpdateAggregator.class, InstalledAction.class, AvailableAction.class, + DownloadAction.class, UpdatesAction.class, PendingAction.class, InstallAction.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/DownloadAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/DownloadAction.java new file mode 100644 index 00000000000..b4342ea4de3 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/DownloadAction.java @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.ws; + +import java.io.InputStream; +import java.util.Optional; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +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.exceptions.NotFoundException; +import org.sonar.server.plugins.InstalledPlugin; +import org.sonar.server.plugins.InstalledPlugin.FileAndMd5; +import org.sonar.server.plugins.PluginFileSystem; + +public class DownloadAction implements PluginsWsAction { + + private static final String PACK200 = "pack200"; + private static final String ACCEPT_COMPRESSIONS_PARAM = "acceptCompressions"; + private static final String PLUGIN_PARAM = "plugin"; + + private final PluginFileSystem pluginFileSystem; + + public DownloadAction(PluginFileSystem pluginFileSystem) { + this.pluginFileSystem = pluginFileSystem; + } + + @Override + public void define(WebService.NewController controller) { + WebService.NewAction action = controller.createAction("download") + .setSince("7.2") + .setDescription("Download plugin JAR, for usage by scanner engine") + .setInternal(true) + .setHandler(this); + + action.createParam(PLUGIN_PARAM) + .setRequired(true) + .setDescription("The key identifying the plugin to download") + .setExampleValue("cobol"); + + action.createParam(ACCEPT_COMPRESSIONS_PARAM) + .setExampleValue(PACK200); + } + + @Override + public void handle(Request request, Response response) throws Exception { + String pluginKey = request.mandatoryParam(PLUGIN_PARAM); + + Optional file = pluginFileSystem.getInstalledPlugin(pluginKey); + if (!file.isPresent()) { + throw new NotFoundException("Plugin " + pluginKey + " not found"); + } + + FileAndMd5 downloadedFile; + FileAndMd5 compressedJar = file.get().getCompressedJar(); + if (compressedJar != null && PACK200.equals(request.param(ACCEPT_COMPRESSIONS_PARAM))) { + response.stream().setMediaType("application/octet-stream"); + + response.setHeader("Sonar-Compression", PACK200); + response.setHeader("Sonar-UncompressedMD5", file.get().getLoadedJar().getMd5()); + downloadedFile = compressedJar; + } else { + response.stream().setMediaType("application/java-archive"); + downloadedFile = file.get().getLoadedJar(); + } + response.setHeader("Sonar-MD5", downloadedFile.getMd5()); + try (InputStream input = FileUtils.openInputStream(downloadedFile.getFile())) { + IOUtils.copyLarge(input, response.stream().output()); + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/DownloadActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/DownloadActionTest.java new file mode 100644 index 00000000000..feb159307ca --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/DownloadActionTest.java @@ -0,0 +1,158 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.ws; + +import java.io.File; +import java.io.IOException; +import java.util.Optional; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.server.ws.WebService; +import org.sonar.core.platform.PluginInfo; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.plugins.InstalledPlugin; +import org.sonar.server.plugins.InstalledPlugin.FileAndMd5; +import org.sonar.server.plugins.PluginFileSystem; +import org.sonar.server.ws.TestResponse; +import org.sonar.server.ws.WsAction; +import org.sonar.server.ws.WsActionTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DownloadActionTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private PluginFileSystem pluginFileSystem = mock(PluginFileSystem.class); + private WsAction underTest = new DownloadAction(pluginFileSystem); + private WsActionTester tester = new WsActionTester(underTest); + + @Test + public void test_definition() { + WebService.Action def = tester.getDef(); + + assertThat(def.isInternal()).isTrue(); + assertThat(def.since()).isEqualTo("7.2"); + assertThat(def.params()) + .extracting(WebService.Param::key) + .containsExactlyInAnyOrder("plugin", "acceptCompressions"); + } + + @Test + public void return_404_if_plugin_not_found() { + when(pluginFileSystem.getInstalledPlugin("foo")).thenReturn(Optional.empty()); + + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("Plugin foo not found"); + + tester.newRequest() + .setParam("plugin", "foo") + .execute(); + } + + @Test + public void return_jar_if_plugin_exists() throws Exception { + InstalledPlugin plugin = newPlugin(); + when(pluginFileSystem.getInstalledPlugin(plugin.getPluginInfo().getKey())).thenReturn(Optional.of(plugin)); + + TestResponse response = tester.newRequest() + .setParam("plugin", plugin.getPluginInfo().getKey()) + .execute(); + + assertThat(response.getHeader("Sonar-MD5")).isEqualTo(plugin.getLoadedJar().getMd5()); + assertThat(response.getHeader("Sonar-Compression")).isNull(); + assertThat(response.getMediaType()).isEqualTo("application/java-archive"); + verifySameContent(response, plugin.getLoadedJar().getFile()); + } + + @Test + public void return_uncompressed_jar_if_client_does_not_accept_compression() throws Exception { + InstalledPlugin plugin = newCompressedPlugin(); + when(pluginFileSystem.getInstalledPlugin(plugin.getPluginInfo().getKey())).thenReturn(Optional.of(plugin)); + + TestResponse response = tester.newRequest() + .setParam("plugin", plugin.getPluginInfo().getKey()) + .execute(); + + assertThat(response.getHeader("Sonar-MD5")).isEqualTo(plugin.getLoadedJar().getMd5()); + assertThat(response.getHeader("Sonar-Compression")).isNull(); + assertThat(response.getHeader("Sonar-UncompressedMD5")).isNull(); + assertThat(response.getMediaType()).isEqualTo("application/java-archive"); + verifySameContent(response, plugin.getLoadedJar().getFile()); + } + + @Test + public void return_uncompressed_jar_if_client_requests_unsupported_compression() throws Exception { + InstalledPlugin plugin = newCompressedPlugin(); + when(pluginFileSystem.getInstalledPlugin(plugin.getPluginInfo().getKey())).thenReturn(Optional.of(plugin)); + + TestResponse response = tester.newRequest() + .setParam("plugin", plugin.getPluginInfo().getKey()) + .setParam("acceptCompressions", "zip") + .execute(); + + assertThat(response.getHeader("Sonar-MD5")).isEqualTo(plugin.getLoadedJar().getMd5()); + assertThat(response.getHeader("Sonar-Compression")).isNull(); + assertThat(response.getHeader("Sonar-UncompressedMD5")).isNull(); + assertThat(response.getMediaType()).isEqualTo("application/java-archive"); + verifySameContent(response, plugin.getLoadedJar().getFile()); + } + + @Test + public void return_compressed_jar_if_client_accepts_pack200() throws Exception { + InstalledPlugin plugin = newCompressedPlugin(); + when(pluginFileSystem.getInstalledPlugin(plugin.getPluginInfo().getKey())).thenReturn(Optional.of(plugin)); + + TestResponse response = tester.newRequest() + .setParam("plugin", plugin.getPluginInfo().getKey()) + .setParam("acceptCompressions", "pack200") + .execute(); + + assertThat(response.getHeader("Sonar-MD5")).isEqualTo(plugin.getCompressedJar().getMd5()); + assertThat(response.getHeader("Sonar-UncompressedMD5")).isEqualTo(plugin.getLoadedJar().getMd5()); + assertThat(response.getHeader("Sonar-Compression")).isEqualTo("pack200"); + assertThat(response.getMediaType()).isEqualTo("application/octet-stream"); + verifySameContent(response, plugin.getCompressedJar().getFile()); + } + + private InstalledPlugin newPlugin() throws IOException { + FileAndMd5 jar = new FileAndMd5(temp.newFile()); + return new InstalledPlugin(new PluginInfo("foo"), jar, null); + } + + private InstalledPlugin newCompressedPlugin() throws IOException { + FileAndMd5 jar = new FileAndMd5(temp.newFile()); + FileAndMd5 compressedJar = new FileAndMd5(temp.newFile()); + return new InstalledPlugin(new PluginInfo("foo"), jar, compressedJar); + } + + private static void verifySameContent(TestResponse response, File file) throws IOException { + assertThat(IOUtils.toByteArray(response.getInputStream())).isEqualTo(FileUtils.readFileToByteArray(file)); + } +} -- 2.39.5