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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2020 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.organization.OrganizationDto;
  40. import org.sonar.db.project.ProjectDto;
  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. if (!component.getKey().isPresent()) {
  104. throw 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. }
  108. ProjectDto dto = toProject(reportMetadata.getProjectKey());
  109. analysisMetadata.setProject(Project.from(dto));
  110. return () -> {
  111. if (!mainComponentKey.equals(reportMetadata.getProjectKey())) {
  112. throw MessageException.of(format(
  113. "ProjectKey in report (%s) is not consistent with projectKey under which the report has been submitted (%s)",
  114. reportMetadata.getProjectKey(),
  115. mainComponentKey));
  116. }
  117. if (!dto.getOrganizationUuid().equals(organization.getUuid())) {
  118. throw MessageException.of(format("Project is not in the expected organization: %s", organization.getKey()));
  119. }
  120. };
  121. }
  122. private static CeTask.Component mandatoryComponent(Optional<CeTask.Component> mainComponent) {
  123. return mainComponent
  124. .orElseThrow(() -> new IllegalStateException("component missing on ce task"));
  125. }
  126. private Organization loadOrganization(ScannerReport.Metadata reportMetadata) {
  127. try (DbSession dbSession = dbClient.openSession(false)) {
  128. Organization organization = toOrganization(dbSession, ceTask.getOrganizationUuid());
  129. checkOrganizationKeyConsistency(reportMetadata, organization);
  130. analysisMetadata.setOrganization(organization);
  131. analysisMetadata.setOrganizationsEnabled(organizationFlags.isEnabled(dbSession));
  132. return organization;
  133. }
  134. }
  135. private void loadQualityProfiles(ScannerReport.Metadata reportMetadata, Organization organization) {
  136. checkQualityProfilesConsistency(reportMetadata, organization);
  137. analysisMetadata.setQProfilesByLanguage(reportMetadata.getQprofilesPerLanguage().values().stream()
  138. .collect(toMap(
  139. QProfile::getLanguage,
  140. qp -> new QualityProfile(qp.getKey(), qp.getName(), qp.getLanguage(), new Date(qp.getRulesUpdatedAt())))));
  141. analysisMetadata.setScannerPluginsByKey(reportMetadata.getPluginsByKey().values().stream()
  142. .collect(toMap(
  143. Plugin::getKey,
  144. p -> new ScannerPlugin(p.getKey(), getBasePluginKey(p), p.getUpdatedAt()))));
  145. }
  146. @CheckForNull
  147. private String getBasePluginKey(Plugin p) {
  148. if (!pluginRepository.hasPlugin(p.getKey())) {
  149. // May happen if plugin was uninstalled between start of scanner analysis and now.
  150. // But it doesn't matter since all active rules are removed anyway, so no issues will be reported
  151. return null;
  152. }
  153. return pluginRepository.getPluginInfo(p.getKey()).getBasePlugin();
  154. }
  155. /**
  156. * Check that the Quality profiles sent by scanner correctly relate to the project organization.
  157. */
  158. private void checkQualityProfilesConsistency(ScannerReport.Metadata metadata, Organization organization) {
  159. List<String> profileKeys = metadata.getQprofilesPerLanguage().values().stream()
  160. .map(QProfile::getKey)
  161. .collect(toList(metadata.getQprofilesPerLanguage().size()));
  162. try (DbSession dbSession = dbClient.openSession(false)) {
  163. List<QProfileDto> profiles = dbClient.qualityProfileDao().selectByUuids(dbSession, profileKeys);
  164. String badKeys = profiles.stream()
  165. .filter(p -> !p.getOrganizationUuid().equals(organization.getUuid()))
  166. .map(QProfileDto::getKee)
  167. .collect(MoreCollectors.join(Joiner.on(", ")));
  168. if (!badKeys.isEmpty()) {
  169. throw MessageException.of(format("Quality profiles with following keys don't exist in organization [%s]: %s", organization.getKey(), badKeys));
  170. }
  171. }
  172. }
  173. private void checkOrganizationKeyConsistency(ScannerReport.Metadata reportMetadata, Organization organization) {
  174. String organizationKey = reportMetadata.getOrganizationKey();
  175. String resolveReportOrganizationKey = resolveReportOrganizationKey(organizationKey);
  176. if (!resolveReportOrganizationKey.equals(organization.getKey())) {
  177. if (reportBelongsToDefaultOrganization(organizationKey)) {
  178. throw MessageException.of(format(
  179. "Report does not specify an OrganizationKey but it has been submitted to another organization (%s) than the default one (%s)",
  180. organization.getKey(),
  181. defaultOrganizationProvider.get().getKey()));
  182. } else {
  183. throw MessageException.of(format(
  184. "OrganizationKey in report (%s) is not consistent with organizationKey under which the report as been submitted (%s)",
  185. resolveReportOrganizationKey,
  186. organization.getKey()));
  187. }
  188. }
  189. }
  190. private String resolveReportOrganizationKey(@Nullable String organizationKey) {
  191. if (reportBelongsToDefaultOrganization(organizationKey)) {
  192. return defaultOrganizationProvider.get().getKey();
  193. }
  194. return organizationKey;
  195. }
  196. private static boolean reportBelongsToDefaultOrganization(@Nullable String organizationKey) {
  197. return organizationKey == null || organizationKey.isEmpty();
  198. }
  199. private Organization toOrganization(DbSession dbSession, String organizationUuid) {
  200. Optional<OrganizationDto> organizationDto = dbClient.organizationDao().selectByUuid(dbSession, organizationUuid);
  201. checkState(organizationDto.isPresent(), "Organization with uuid '%s' can't be found", organizationUuid);
  202. return Organization.from(organizationDto.get());
  203. }
  204. private ProjectDto toProject(String projectKey) {
  205. try (DbSession dbSession = dbClient.openSession(false)) {
  206. Optional<ProjectDto> opt = dbClient.projectDao().selectProjectByKey(dbSession, projectKey);
  207. checkState(opt.isPresent(), "Project with key '%s' can't be found", projectKey);
  208. return opt.get();
  209. }
  210. }
  211. @Override
  212. public String getDescription() {
  213. return "Load analysis metadata";
  214. }
  215. }