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.

LoadReportAnalysisMetadataHolderStep.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 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.ce.task.projectanalysis.step;
  21. import com.google.common.base.Joiner;
  22. import java.util.Date;
  23. import java.util.List;
  24. import java.util.Optional;
  25. import javax.annotation.CheckForNull;
  26. import javax.annotation.Nullable;
  27. import org.sonar.api.utils.MessageException;
  28. import org.sonar.ce.task.CeTask;
  29. import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolder;
  30. import org.sonar.ce.task.projectanalysis.analysis.Organization;
  31. import org.sonar.ce.task.projectanalysis.analysis.ScannerPlugin;
  32. import org.sonar.ce.task.projectanalysis.batch.BatchReportReader;
  33. import org.sonar.ce.task.projectanalysis.component.BranchLoader;
  34. import org.sonar.ce.task.step.ComputationStep;
  35. import org.sonar.core.platform.PluginRepository;
  36. import org.sonar.core.util.stream.MoreCollectors;
  37. import org.sonar.db.DbClient;
  38. import org.sonar.db.DbSession;
  39. import org.sonar.db.component.ComponentDto;
  40. import org.sonar.db.organization.OrganizationDto;
  41. import org.sonar.db.qualityprofile.QProfileDto;
  42. import org.sonar.scanner.protocol.output.ScannerReport;
  43. import org.sonar.scanner.protocol.output.ScannerReport.Metadata.Plugin;
  44. import org.sonar.scanner.protocol.output.ScannerReport.Metadata.QProfile;
  45. import org.sonar.server.organization.DefaultOrganizationProvider;
  46. import org.sonar.server.organization.OrganizationFlags;
  47. import org.sonar.server.project.Project;
  48. import org.sonar.server.qualityprofile.QualityProfile;
  49. import static com.google.common.base.Preconditions.checkState;
  50. import static java.lang.String.format;
  51. import static java.util.stream.Collectors.toMap;
  52. import static org.sonar.core.util.stream.MoreCollectors.toList;
  53. /**
  54. * Feed analysis metadata holder with metadata from the analysis report.
  55. */
  56. public class LoadReportAnalysisMetadataHolderStep implements ComputationStep {
  57. private final CeTask ceTask;
  58. private final BatchReportReader reportReader;
  59. private final MutableAnalysisMetadataHolder analysisMetadata;
  60. private final DefaultOrganizationProvider defaultOrganizationProvider;
  61. private final DbClient dbClient;
  62. private final BranchLoader branchLoader;
  63. private final PluginRepository pluginRepository;
  64. private final OrganizationFlags organizationFlags;
  65. public LoadReportAnalysisMetadataHolderStep(CeTask ceTask, BatchReportReader reportReader, MutableAnalysisMetadataHolder analysisMetadata,
  66. DefaultOrganizationProvider defaultOrganizationProvider, DbClient dbClient, BranchLoader branchLoader, PluginRepository pluginRepository,
  67. OrganizationFlags organizationFlags) {
  68. this.ceTask = ceTask;
  69. this.reportReader = reportReader;
  70. this.analysisMetadata = analysisMetadata;
  71. this.defaultOrganizationProvider = defaultOrganizationProvider;
  72. this.dbClient = dbClient;
  73. this.branchLoader = branchLoader;
  74. this.pluginRepository = pluginRepository;
  75. this.organizationFlags = organizationFlags;
  76. }
  77. @Override
  78. public void execute(ComputationStep.Context context) {
  79. ScannerReport.Metadata reportMetadata = reportReader.readMetadata();
  80. loadMetadata(reportMetadata);
  81. Organization organization = loadOrganization(reportMetadata);
  82. Runnable projectValidation = loadProject(reportMetadata, organization);
  83. loadQualityProfiles(reportMetadata, organization);
  84. branchLoader.load(reportMetadata);
  85. projectValidation.run();
  86. }
  87. private void loadMetadata(ScannerReport.Metadata reportMetadata) {
  88. analysisMetadata.setAnalysisDate(reportMetadata.getAnalysisDate());
  89. analysisMetadata.setRootComponentRef(reportMetadata.getRootComponentRef());
  90. analysisMetadata.setCrossProjectDuplicationEnabled(reportMetadata.getCrossProjectDuplicationActivated());
  91. analysisMetadata.setScmRevision(reportMetadata.getScmRevisionId());
  92. }
  93. /**
  94. * @return a {@link Runnable} to execute some checks on the project at the end of the step
  95. */
  96. private Runnable loadProject(ScannerReport.Metadata reportMetadata, Organization organization) {
  97. CeTask.Component mainComponent = mandatoryComponent(ceTask.getMainComponent());
  98. String mainComponentKey = mainComponent.getKey()
  99. .orElseThrow(() -> MessageException.of(format(
  100. "Compute Engine task main component key is null. Project with UUID %s must have been deleted since report was uploaded. Can not proceed.",
  101. mainComponent.getUuid())));
  102. CeTask.Component component = mandatoryComponent(ceTask.getComponent());
  103. String componentKey = component.getKey()
  104. .orElseThrow(() -> MessageException.of(format(
  105. "Compute Engine task component key is null. Project with UUID %s must have been deleted since report was uploaded. Can not proceed.",
  106. component.getUuid())));
  107. ComponentDto dto = toProject(reportMetadata.getProjectKey());
  108. analysisMetadata.setProject(Project.from(dto));
  109. return () -> {
  110. if (!mainComponentKey.equals(reportMetadata.getProjectKey())) {
  111. throw MessageException.of(format(
  112. "ProjectKey in report (%s) is not consistent with projectKey under which the report has been submitted (%s)",
  113. reportMetadata.getProjectKey(),
  114. mainComponentKey));
  115. }
  116. if (!dto.getOrganizationUuid().equals(organization.getUuid())) {
  117. throw MessageException.of(format("Project is not in the expected organization: %s", organization.getKey()));
  118. }
  119. if (componentKey.equals(mainComponentKey) && dto.getMainBranchProjectUuid() != null) {
  120. throw MessageException.of("Component should not reference a branch");
  121. }
  122. };
  123. }
  124. private static CeTask.Component mandatoryComponent(Optional<CeTask.Component> mainComponent) {
  125. return mainComponent
  126. .orElseThrow(() -> new IllegalStateException("component missing on ce task"));
  127. }
  128. private Organization loadOrganization(ScannerReport.Metadata reportMetadata) {
  129. try (DbSession dbSession = dbClient.openSession(false)) {
  130. Organization organization = toOrganization(dbSession, ceTask.getOrganizationUuid());
  131. checkOrganizationKeyConsistency(reportMetadata, organization);
  132. analysisMetadata.setOrganization(organization);
  133. analysisMetadata.setOrganizationsEnabled(organizationFlags.isEnabled(dbSession));
  134. return organization;
  135. }
  136. }
  137. private void loadQualityProfiles(ScannerReport.Metadata reportMetadata, Organization organization) {
  138. checkQualityProfilesConsistency(reportMetadata, organization);
  139. analysisMetadata.setQProfilesByLanguage(reportMetadata.getQprofilesPerLanguage().values().stream()
  140. .collect(toMap(
  141. QProfile::getLanguage,
  142. qp -> new QualityProfile(qp.getKey(), qp.getName(), qp.getLanguage(), new Date(qp.getRulesUpdatedAt())))));
  143. analysisMetadata.setScannerPluginsByKey(reportMetadata.getPluginsByKey().values().stream()
  144. .collect(toMap(
  145. Plugin::getKey,
  146. p -> new ScannerPlugin(p.getKey(), getBasePluginKey(p), p.getUpdatedAt()))));
  147. }
  148. @CheckForNull
  149. private String getBasePluginKey(Plugin p) {
  150. if (!pluginRepository.hasPlugin(p.getKey())) {
  151. // May happen if plugin was uninstalled between start of scanner analysis and now.
  152. // But it doesn't matter since all active rules are removed anyway, so no issues will be reported
  153. return null;
  154. }
  155. return pluginRepository.getPluginInfo(p.getKey()).getBasePlugin();
  156. }
  157. /**
  158. * Check that the Quality profiles sent by scanner correctly relate to the project organization.
  159. */
  160. private void checkQualityProfilesConsistency(ScannerReport.Metadata metadata, Organization organization) {
  161. List<String> profileKeys = metadata.getQprofilesPerLanguage().values().stream()
  162. .map(QProfile::getKey)
  163. .collect(toList(metadata.getQprofilesPerLanguage().size()));
  164. try (DbSession dbSession = dbClient.openSession(false)) {
  165. List<QProfileDto> profiles = dbClient.qualityProfileDao().selectByUuids(dbSession, profileKeys);
  166. String badKeys = profiles.stream()
  167. .filter(p -> !p.getOrganizationUuid().equals(organization.getUuid()))
  168. .map(QProfileDto::getKee)
  169. .collect(MoreCollectors.join(Joiner.on(", ")));
  170. if (!badKeys.isEmpty()) {
  171. throw MessageException.of(format("Quality profiles with following keys don't exist in organization [%s]: %s", organization.getKey(), badKeys));
  172. }
  173. }
  174. }
  175. private void checkOrganizationKeyConsistency(ScannerReport.Metadata reportMetadata, Organization organization) {
  176. String organizationKey = reportMetadata.getOrganizationKey();
  177. String resolveReportOrganizationKey = resolveReportOrganizationKey(organizationKey);
  178. if (!resolveReportOrganizationKey.equals(organization.getKey())) {
  179. if (reportBelongsToDefaultOrganization(organizationKey)) {
  180. throw MessageException.of(format(
  181. "Report does not specify an OrganizationKey but it has been submitted to another organization (%s) than the default one (%s)",
  182. organization.getKey(),
  183. defaultOrganizationProvider.get().getKey()));
  184. } else {
  185. throw MessageException.of(format(
  186. "OrganizationKey in report (%s) is not consistent with organizationKey under which the report as been submitted (%s)",
  187. resolveReportOrganizationKey,
  188. organization.getKey()));
  189. }
  190. }
  191. }
  192. private String resolveReportOrganizationKey(@Nullable String organizationKey) {
  193. if (reportBelongsToDefaultOrganization(organizationKey)) {
  194. return defaultOrganizationProvider.get().getKey();
  195. }
  196. return organizationKey;
  197. }
  198. private static boolean reportBelongsToDefaultOrganization(@Nullable String organizationKey) {
  199. return organizationKey == null || organizationKey.isEmpty();
  200. }
  201. private Organization toOrganization(DbSession dbSession, String organizationUuid) {
  202. Optional<OrganizationDto> organizationDto = dbClient.organizationDao().selectByUuid(dbSession, organizationUuid);
  203. checkState(organizationDto.isPresent(), "Organization with uuid '%s' can't be found", organizationUuid);
  204. return Organization.from(organizationDto.get());
  205. }
  206. private ComponentDto toProject(String projectKey) {
  207. try (DbSession dbSession = dbClient.openSession(false)) {
  208. Optional<ComponentDto> opt = dbClient.componentDao().selectByKey(dbSession, projectKey);
  209. checkState(opt.isPresent(), "Project with key '%s' can't be found", projectKey);
  210. return opt.get();
  211. }
  212. }
  213. @Override
  214. public String getDescription() {
  215. return "Load analysis metadata";
  216. }
  217. }