You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ScannerPluginInstaller.java 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.scanner.bootstrap;
  21. import com.google.gson.Gson;
  22. import java.io.File;
  23. import java.io.Reader;
  24. import java.util.ArrayList;
  25. import java.util.Collections;
  26. import java.util.HashMap;
  27. import java.util.List;
  28. import java.util.Map;
  29. import java.util.Optional;
  30. import java.util.Set;
  31. import java.util.function.Predicate;
  32. import javax.annotation.Nullable;
  33. import org.sonar.api.utils.log.Logger;
  34. import org.sonar.api.utils.log.Loggers;
  35. import org.sonar.api.utils.log.Profiler;
  36. import org.sonar.core.platform.PluginInfo;
  37. import org.sonar.core.plugin.PluginType;
  38. import org.sonar.scanner.http.DefaultScannerWsClient;
  39. import org.sonar.scanner.mediumtest.LocalPlugin;
  40. import org.sonarqube.ws.client.GetRequest;
  41. import static java.lang.String.format;
  42. /**
  43. * Downloads the plugins installed on server and stores them in a local user cache
  44. */
  45. public class ScannerPluginInstaller implements PluginInstaller {
  46. private static final Logger LOG = Loggers.get(ScannerPluginInstaller.class);
  47. private static final String PLUGINS_WS_URL = "api/plugins/installed";
  48. private final PluginFiles pluginFiles;
  49. private final DefaultScannerWsClient wsClient;
  50. private List<InstalledPlugin> availablePlugins;
  51. public ScannerPluginInstaller(PluginFiles pluginFiles, DefaultScannerWsClient wsClient) {
  52. this.pluginFiles = pluginFiles;
  53. this.wsClient = wsClient;
  54. }
  55. @Override
  56. public Map<String, ScannerPlugin> installAllPlugins() {
  57. LOG.info("Loading all plugins");
  58. return installPlugins(p -> true).installedPluginsByKey;
  59. }
  60. @Override
  61. public Map<String, ScannerPlugin> installRequiredPlugins() {
  62. LOG.info("Loading required plugins");
  63. InstallResult result = installPlugins(p -> p.getRequiredForLanguages() == null || p.getRequiredForLanguages().isEmpty());
  64. LOG.debug("Plugins not loaded because they are optional: {}", result.skippedPlugins);
  65. return result.installedPluginsByKey;
  66. }
  67. @Override
  68. public Map<String, ScannerPlugin> installPluginsForLanguages(Set<String> languageKeys) {
  69. LOG.info("Loading plugins for detected languages");
  70. LOG.debug("Detected languages: {}", languageKeys);
  71. InstallResult result = installPlugins(
  72. p -> p.getRequiredForLanguages() != null && !Collections.disjoint(p.getRequiredForLanguages(), languageKeys)
  73. );
  74. List<InstalledPlugin> skippedLanguagePlugins = result.skippedPlugins.stream()
  75. .filter(p -> p.getRequiredForLanguages() != null && !p.getRequiredForLanguages().isEmpty()).toList();
  76. LOG.debug("Optional language-specific plugins not loaded: {}", skippedLanguagePlugins);
  77. return result.installedPluginsByKey;
  78. }
  79. private InstallResult installPlugins(Predicate<InstalledPlugin> pluginFilter) {
  80. if (this.availablePlugins == null) {
  81. this.availablePlugins = listInstalledPlugins();
  82. }
  83. Profiler profiler = Profiler.create(LOG).startInfo("Load/download plugins");
  84. try {
  85. InstallResult result = new InstallResult();
  86. Loaded loaded = loadPlugins(result, pluginFilter);
  87. if (!loaded.ok) {
  88. // retry once, a plugin may have been uninstalled during downloads
  89. this.availablePlugins = listInstalledPlugins();
  90. result.installedPluginsByKey.clear();
  91. loaded = loadPlugins(result, pluginFilter);
  92. if (!loaded.ok) {
  93. throw new IllegalStateException(format("Fail to download plugin [%s]. Not found.", loaded.notFoundPlugin));
  94. }
  95. }
  96. return result;
  97. } finally {
  98. profiler.stopInfo();
  99. }
  100. }
  101. private Loaded loadPlugins(InstallResult result, Predicate<InstalledPlugin> pluginFilter) {
  102. List<InstalledPlugin> pluginsToInstall = availablePlugins.stream()
  103. .filter(pluginFilter).toList();
  104. for (InstalledPlugin plugin : pluginsToInstall) {
  105. Optional<File> jarFile = pluginFiles.get(plugin);
  106. if (jarFile.isEmpty()) {
  107. return new Loaded(false, plugin.key);
  108. }
  109. PluginInfo info = PluginInfo.create(jarFile.get());
  110. result.installedPluginsByKey.put(info.getKey(), new ScannerPlugin(plugin.key, plugin.updatedAt, PluginType.valueOf(plugin.type), info));
  111. }
  112. result.skippedPlugins = availablePlugins.stream()
  113. .filter(Predicate.not(pluginFilter)).toList();
  114. return new Loaded(true, null);
  115. }
  116. /**
  117. * Returns empty on purpose. This method is used only by medium tests.
  118. */
  119. @Override
  120. public List<LocalPlugin> installLocals() {
  121. return Collections.emptyList();
  122. }
  123. /**
  124. * Returns empty on purpose. This method is used only by medium tests.
  125. */
  126. @Override
  127. public List<LocalPlugin> installOptionalLocals(Set<String> languageKeys) {
  128. return Collections.emptyList();
  129. }
  130. /**
  131. * Gets information about the plugins installed on server (filename, checksum)
  132. */
  133. private List<InstalledPlugin> listInstalledPlugins() {
  134. Profiler profiler = Profiler.create(LOG).startInfo("Load plugins index");
  135. GetRequest getRequest = new GetRequest(PLUGINS_WS_URL);
  136. InstalledPlugins installedPlugins;
  137. try (Reader reader = wsClient.call(getRequest).contentReader()) {
  138. installedPlugins = new Gson().fromJson(reader, InstalledPlugins.class);
  139. } catch (Exception e) {
  140. throw new IllegalStateException("Fail to parse response of " + PLUGINS_WS_URL, e);
  141. }
  142. profiler.stopInfo();
  143. return installedPlugins.plugins;
  144. }
  145. private static class InstallResult {
  146. Map<String, ScannerPlugin> installedPluginsByKey = new HashMap<>();
  147. List<InstalledPlugin> skippedPlugins = new ArrayList<>();
  148. }
  149. private static class InstalledPlugins {
  150. List<InstalledPlugin> plugins;
  151. public InstalledPlugins() {
  152. // http://stackoverflow.com/a/18645370/229031
  153. }
  154. }
  155. static class InstalledPlugin {
  156. String key;
  157. String hash;
  158. long updatedAt;
  159. String type;
  160. private Set<String> requiredForLanguages;
  161. public InstalledPlugin() {
  162. // http://stackoverflow.com/a/18645370/229031
  163. }
  164. public Set<String> getRequiredForLanguages() {
  165. return requiredForLanguages;
  166. }
  167. @Override
  168. public String toString() {
  169. return key;
  170. }
  171. }
  172. private static class Loaded {
  173. private final boolean ok;
  174. @Nullable
  175. private final String notFoundPlugin;
  176. private Loaded(boolean ok, @Nullable String notFoundPlugin) {
  177. this.ok = ok;
  178. this.notFoundPlugin = notFoundPlugin;
  179. }
  180. }
  181. }