*/
package org.sonar.server.computation.queue;
-import static com.google.common.base.Preconditions.checkArgument;
-import static java.lang.String.format;
-import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
-import static org.sonar.server.component.NewComponent.newComponentBuilder;
-import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
-
+import com.google.common.base.Optional;
import java.io.InputStream;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
-
import javax.annotation.Nullable;
-
import org.apache.commons.lang.StringUtils;
import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
import org.sonar.api.server.ServerSide;
import org.sonar.ce.queue.CeQueue;
import org.sonar.ce.queue.CeTask;
import org.sonar.db.permission.OrganizationPermission;
import org.sonar.server.component.ComponentUpdater;
import org.sonar.server.component.NewComponent;
+import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.permission.PermissionTemplateService;
import org.sonar.server.user.UserSession;
-import com.google.common.base.Optional;
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.String.format;
+import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
+import static org.sonar.server.component.NewComponent.newComponentBuilder;
+import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
@ServerSide
public class ReportSubmitter {
try (DbSession dbSession = dbClient.openSession(false)) {
OrganizationDto organizationDto = getOrganizationDtoOrFail(dbSession, organizationKey);
String effectiveProjectKey = ComponentKeys.createKey(projectKey, projectBranch);
- Optional<ComponentDto> opt = dbClient.componentDao().selectByKey(dbSession, effectiveProjectKey);
- ensureOrganizationIsConsistent(opt, organizationDto);
- ComponentDto project = opt.or(() -> createProject(dbSession, organizationDto, projectKey, projectBranch, projectName));
+ Optional<ComponentDto> component = dbClient.componentDao().selectByKey(dbSession, effectiveProjectKey);
+ validateProject(dbSession, component, projectKey);
+ ensureOrganizationIsConsistent(component, organizationDto);
+ ComponentDto project = component.or(() -> createProject(dbSession, organizationDto, projectKey, projectBranch, projectName));
checkScanPermission(project);
return submitReport(dbSession, reportInput, project, characteristics);
}
.orElseThrow(() -> new NotFoundException(format("Organization with key '%s' does not exist", organizationKey)));
}
+ private void validateProject(DbSession dbSession, Optional<ComponentDto> project, String rawProjectKey) {
+ List<String> errors = new ArrayList<>();
+ if (!project.isPresent()) {
+ return;
+ }
+
+ ComponentDto component = project.get();
+ if (!Qualifiers.PROJECT.equals(component.qualifier()) || !Scopes.PROJECT.equals(component.scope())) {
+ errors.add(format("Component '%s' is not a project", rawProjectKey));
+ }
+ if (!project.get().projectUuid().equals(project.get().uuid())) {
+ // Project key is already used as a module of another project
+ ComponentDto anotherBaseProject = dbClient.componentDao().selectOrFailByUuid(dbSession, project.get().projectUuid());
+ errors.add(format("The project '%s' is already defined in SonarQube but as a module of project '%s'. "
+ + "If you really want to stop directly analysing project '%s', please first delete it from SonarQube and then relaunch the analysis of project '%s'.",
+ rawProjectKey, anotherBaseProject.getKey(), anotherBaseProject.getKey(), rawProjectKey));
+ }
+ if (!errors.isEmpty()) {
+ throw BadRequestException.create(errors);
+ }
+ }
+
private static void ensureOrganizationIsConsistent(Optional<ComponentDto> project, OrganizationDto organizationDto) {
if (project.isPresent()) {
checkArgument(project.get().getOrganizationUuid().equals(organizationDto.getUuid()),
import java.util.Date;
import java.util.List;
import java.util.Map;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.resources.Scopes;
import org.sonar.api.utils.MessageException;
import org.sonar.core.component.ComponentKeys;
import org.sonar.db.DbClient;
/**
* Validate project and modules. It will fail in the following cases :
* <ol>
- * <li>branch is not valid</li>
- * <li>project or module key is not valid</li>
+ * <li>module key is not valid</li>
* <li>module key already exists in another project (same module key cannot exists in different projects)</li>
* <li>module key is already used as a project key</li>
* <li>date of the analysis is before last analysis</li>
public void visitProject(Component rawProject) {
this.rawProject = rawProject;
String rawProjectKey = rawProject.getKey();
- validateBatchKey(rawProject);
Optional<ComponentDto> baseProject = loadBaseComponent(rawProjectKey);
- validateRootIsProject(baseProject);
- validateProjectKey(baseProject, rawProjectKey);
validateAnalysisDate(baseProject);
}
- private void validateRootIsProject(Optional<ComponentDto> baseProject) {
- if (baseProject.isPresent()) {
- ComponentDto componentDto = baseProject.get();
- // the scope field is verified for excluding the project copies generated by portfolios
- if (!Qualifiers.PROJECT.equals(componentDto.qualifier()) || !Scopes.PROJECT.equals(componentDto.scope())) {
- validationMessages.add(format("Component (uuid=%s, key=%s) is not a project", rawProject.getUuid(), rawProject.getKey()));
- }
- }
- }
-
- private void validateProjectKey(Optional<ComponentDto> baseProject, String rawProjectKey) {
- if (baseProject.isPresent() && !baseProject.get().projectUuid().equals(baseProject.get().uuid())) {
- // Project key is already used as a module of another project
- ComponentDto anotherBaseProject = componentDao.selectOrFailByUuid(session, baseProject.get().projectUuid());
- validationMessages.add(format("The project \"%s\" is already defined in SonarQube but as a module of project \"%s\". "
- + "If you really want to stop directly analysing project \"%s\", please first delete it from SonarQube and then relaunch the analysis of project \"%s\".",
- rawProjectKey, anotherBaseProject.getDbKey(), anotherBaseProject.getDbKey(), rawProjectKey));
- }
- }
-
private void validateAnalysisDate(Optional<ComponentDto> baseProject) {
if (baseProject.isPresent()) {
java.util.Optional<SnapshotDto> snapshotDto = dbClient.snapshotDao().selectLastAnalysisByRootComponentUuid(session, baseProject.get().uuid());
import org.sonar.db.permission.OrganizationPermission;
import org.sonar.server.component.ComponentUpdater;
import org.sonar.server.component.NewComponent;
+import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.favorite.FavoriteUpdater;
import org.sonar.server.permission.PermissionTemplateService;
import org.sonar.server.tester.UserSessionRule;
+import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
+import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
+import static org.sonar.db.component.ComponentTesting.newModuleDto;
import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS;
import static org.sonar.db.permission.OrganizationPermission.SCAN;
private static final String TASK_UUID = "TASK_1";
@Rule
- public ExpectedException thrown = ExpectedException.none();
+ public ExpectedException expectedException = ExpectedException.none();
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
@Rule
defaultOrganizationUuid = db.getDefaultOrganization().getUuid();
}
- @Test
- public void submit_fails_with_NotFoundException_if_organization_with_specified_key_does_not_exist() {
- thrown.expect(NotFoundException.class);
- thrown.expectMessage("Organization with key 'fop' does not exist");
-
- underTest.submit("fop", PROJECT_KEY, null, null, null /* method will fail before parameter is used */);
- }
-
- @Test
- public void submit_fails_with_organizationKey_does_not_match_organization_of_specified_component() {
- userSession.logIn().setRoot();
- OrganizationDto organization = db.organizations().insert();
- ComponentDto project = db.components().insertPrivateProject(organization);
- mockSuccessfulPrepareSubmitCall();
-
- underTest.submit(organization.getKey(), project.getDbKey(), null, project.name(), IOUtils.toInputStream("{binary}"));
- }
-
@Test
public void submit_inserts_characteristics() {
userSession
when(componentUpdater.create(any(DbSession.class), any(NewComponent.class), eq(null))).thenReturn(project);
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(defaultOrganizationUuid), anyInt(), eq(PROJECT_KEY),
eq(Qualifiers.PROJECT)))
- .thenReturn(true);
+ .thenReturn(true);
Map<String, String> taskCharacteristics = new HashMap<>();
taskCharacteristics.put("incremental", "true");
when(componentUpdater.create(any(DbSession.class), any(NewComponent.class), eq(null))).thenReturn(createdProject);
when(
permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(organization.getUuid()), anyInt(), eq(PROJECT_KEY), eq(Qualifiers.PROJECT)))
- .thenReturn(true);
+ .thenReturn(true);
when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), eq(organization.getUuid()), any(ComponentDto.class))).thenReturn(true);
underTest.submit(organization.getKey(), PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
when(componentUpdater.create(any(DbSession.class), any(NewComponent.class), eq(null))).thenReturn(createdProject);
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(defaultOrganizationUuid), anyInt(),
eq(PROJECT_KEY), eq(Qualifiers.PROJECT)))
- .thenReturn(true);
+ .thenReturn(true);
when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), eq(defaultOrganizationUuid), any(ComponentDto.class))).thenReturn(false);
underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
when(componentUpdater.create(any(DbSession.class), any(NewComponent.class), eq(null))).thenReturn(project);
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(defaultOrganizationUuid), anyInt(),
eq(PROJECT_KEY), eq(Qualifiers.PROJECT)))
- .thenReturn(true);
+ .thenReturn(true);
underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
verify(queue).submit(any(CeTaskSubmit.class));
}
+ /**
+ * SONAR-8757
+ */
@Test
- public void fail_with_forbidden_exception_when_no_scan_permission() {
- thrown.expect(ForbiddenException.class);
+ public void project_branch_must_not_benefit_from_the_scan_permission_on_main_project() {
+ ComponentDto mainProject = db.components().insertPrivateProject();
+ userSession.addProjectPermission(GlobalPermissions.SCAN_EXECUTION, mainProject);
- underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
+ // user does not have the "scan" permission on the branch, so it can't scan it
+ String branchName = "branchFoo";
+ ComponentDto branchProject = db.components().insertPrivateProject(p -> p.setDbKey(mainProject.getDbKey() + ":" + branchName));
+
+ expectedException.expect(ForbiddenException.class);
+ underTest.submit(defaultOrganizationKey, mainProject.getDbKey(), branchName, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
}
@Test
- public void fail_with_forbidden_exception_on_new_project_when_only_project_scan_permission() {
- userSession.addProjectPermission(SCAN_EXECUTION, ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID));
+ public void fail_with_NotFoundException_if_organization_with_specified_key_does_not_exist() {
+ expectedException.expect(NotFoundException.class);
+ expectedException.expectMessage("Organization with key 'fop' does not exist");
+
+ underTest.submit("fop", PROJECT_KEY, null, null, null /* method will fail before parameter is used */);
+ }
+ @Test
+ public void fail_with_organizationKey_does_not_match_organization_of_specified_component() {
+ userSession.logIn().setRoot();
+ OrganizationDto organization = db.organizations().insert();
+ ComponentDto project = db.components().insertPrivateProject(organization);
mockSuccessfulPrepareSubmitCall();
- when(componentUpdater.create(any(DbSession.class), any(NewComponent.class), eq(null))).thenReturn(new ComponentDto().setUuid(PROJECT_UUID).setDbKey(PROJECT_KEY));
- thrown.expect(ForbiddenException.class);
+ underTest.submit(organization.getKey(), project.getDbKey(), null, project.name(), IOUtils.toInputStream("{binary}"));
+ }
+
+ @Test
+ public void fail_if_component_is_not_a_project() {
+ ComponentDto component = db.components().insertPublicPortfolio(db.getDefaultOrganization());
+ userSession.logIn().addProjectPermission(SCAN_EXECUTION, component);
+ mockSuccessfulPrepareSubmitCall();
+
+ expectedException.expect(BadRequestException.class);
+ expectedException.expectMessage(format("Component '%s' is not a project", component.getKey()));
+
+ underTest.submit(defaultOrganizationKey, component.getDbKey(), null, component.name(), IOUtils.toInputStream("{binary}"));
+ }
+
+ @Test
+ public void fail_if_project_key_already_exists_as_module() {
+ ComponentDto project = db.components().insertPrivateProject(db.getDefaultOrganization());
+ ComponentDto module = db.components().insertComponent(newModuleDto(project));
+ userSession.logIn().addProjectPermission(SCAN_EXECUTION, project);
+ mockSuccessfulPrepareSubmitCall();
+
+ try {
+ underTest.submit(defaultOrganizationKey, module.getDbKey(), null, module.name(), IOUtils.toInputStream("{binary}"));
+ fail();
+ } catch (BadRequestException e) {
+ assertThat(e.errors()).contains(
+ format("The project '%s' is already defined in SonarQube but as a module of project '%s'. " +
+ "If you really want to stop directly analysing project '%s', please first delete it from SonarQube and then relaunch the analysis of project '%s'.",
+ module.getKey(), project.getKey(), project.getKey(), module.getKey()));
+ }
+ }
+
+ @Test
+ public void fail_with_forbidden_exception_when_no_scan_permission() {
+ expectedException.expect(ForbiddenException.class);
+
underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
}
- /**
- * SONAR-8757
- */
@Test
- public void project_branch_must_not_benefit_from_the_scan_permission_on_main_project() {
- ComponentDto mainProject = db.components().insertPrivateProject();
- userSession.addProjectPermission(GlobalPermissions.SCAN_EXECUTION, mainProject);
+ public void fail_with_forbidden_exception_on_new_project_when_only_project_scan_permission() {
+ userSession.addProjectPermission(SCAN_EXECUTION, ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID));
- // user does not have the "scan" permission on the branch, so it can't scan it
- String branchName = "branchFoo";
- ComponentDto branchProject = db.components().insertPrivateProject(p -> p.setDbKey(mainProject.getDbKey() + ":" + branchName));
+ mockSuccessfulPrepareSubmitCall();
+ when(componentUpdater.create(any(DbSession.class), any(NewComponent.class), eq(null))).thenReturn(new ComponentDto().setUuid(PROJECT_UUID).setDbKey(PROJECT_KEY));
- thrown.expect(ForbiddenException.class);
- underTest.submit(defaultOrganizationKey, mainProject.getDbKey(), branchName, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
+ expectedException.expect(ForbiddenException.class);
+ underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
}
private void verifyReportIsPersisted(String taskUuid) {
ValidateProjectStep underTest = new ValidateProjectStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder);
- @Test
- public void fail_if_root_component_is_not_a_project_in_db() {
- reportReader.putComponent(ScannerReport.Component.newBuilder()
- .setRef(1)
- .setType(ComponentType.PROJECT)
- .setKey(PROJECT_KEY)
- .build());
- treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(PROJECT_KEY).build());
-
- ComponentDto project = ComponentTesting.newView(dbTester.organizations().insert(), "ABCD").setDbKey(PROJECT_KEY);
- dbClient.componentDao().insert(dbTester.getSession(), project);
- dbTester.getSession().commit();
-
- thrown.expect(MessageException.class);
- thrown.expectMessage("Validation of project failed:\n" +
- " o Component (uuid=ABCD, key=PROJECT_KEY) is not a project");
-
- underTest.execute();
- }
-
- @Test
- public void fail_on_invalid_key() {
- String invalidProjectKey = "Project\\Key";
-
- reportReader.putComponent(ScannerReport.Component.newBuilder()
- .setRef(1)
- .setType(ComponentType.PROJECT)
- .setKey(invalidProjectKey)
- .addChildRef(2)
- .build());
- reportReader.putComponent(ScannerReport.Component.newBuilder()
- .setRef(2)
- .setType(ComponentType.MODULE)
- .setKey("Module$Key")
- .build());
- treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(invalidProjectKey).addChildren(
- ReportComponent.builder(Component.Type.MODULE, 2).setUuid("BCDE").setKey("Module$Key").build())
- .build());
-
- thrown.expect(MessageException.class);
- thrown.expectMessage("Validation of project failed:\n" +
- " o \"Project\\Key\" is not a valid project or module key. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.\n" +
- " o \"Module$Key\" is not a valid project or module key. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit");
-
- underTest.execute();
- }
-
@Test
public void fail_if_module_key_is_already_used_as_project_key() {
reportReader.putComponent(ScannerReport.Component.newBuilder()
underTest.execute();
}
- @Test
- public void fail_if_project_key_already_exists_as_module() {
- String anotherProjectKey = "ANOTHER_PROJECT_KEY";
-
- reportReader.putComponent(ScannerReport.Component.newBuilder()
- .setRef(1)
- .setType(ComponentType.PROJECT)
- .setKey(PROJECT_KEY)
- .addChildRef(2)
- .build());
- reportReader.putComponent(ScannerReport.Component.newBuilder()
- .setRef(2)
- .setType(ComponentType.MODULE)
- .setKey(MODULE_KEY)
- .build());
-
- ComponentDto anotherProject = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert()).setDbKey(anotherProjectKey);
- dbClient.componentDao().insert(dbTester.getSession(), anotherProject);
- ComponentDto module = ComponentTesting.newModuleDto("ABCD", anotherProject).setDbKey(PROJECT_KEY);
- dbClient.componentDao().insert(dbTester.getSession(), module);
- dbTester.getSession().commit();
-
- treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(PROJECT_KEY).addChildren(
- ReportComponent.builder(Component.Type.MODULE, 2).setUuid("BCDE").setKey(MODULE_KEY).build())
- .build());
-
- thrown.expect(MessageException.class);
- thrown.expectMessage("Validation of project failed:\n" +
- " o Component (uuid=ABCD, key=PROJECT_KEY) is not a project\n" +
- " o The project \"" + PROJECT_KEY + "\" is already defined in SonarQube but as a module of project \"" + anotherProjectKey + "\". " +
- "If you really want to stop directly analysing project \"" + anotherProjectKey + "\", please first delete it from SonarQube and then relaunch the analysis of project \""
- + PROJECT_KEY + "\".");
-
- underTest.execute();
- }
-
@Test
public void not_fail_if_analysis_date_is_after_last_analysis() {
reportReader.putComponent(ScannerReport.Component.newBuilder()
.contains("Allowed characters");
}
+ @Test
+ public void display_explicit_message_when_using_existing_module_key_as_project_key() {
+ String projectKey = "com.sonarsource.it.samples:multi-modules-sample";
+ String moduleKey = "com.sonarsource.it.samples:multi-modules-sample:module_a";
+ scan("shared/xoo-multi-modules-sample", "sonar.projectKey", projectKey);
+
+ BuildResult buildResult = scanQuietly("shared/xoo-sample", "sonar.projectKey", moduleKey);
+ assertThat(buildResult.getLastStatus()).isEqualTo(1);
+ assertThat(buildResult.getLogs())
+ .contains(String.format("Component '%s' is not a project", moduleKey))
+ .contains(String.format("The project '%s' is already defined in SonarQube but as a module of project '%s'. If you really want to stop directly analysing project '%s', " +
+ "please first delete it from SonarQube and then relaunch the analysis of project '%s'", moduleKey, projectKey, projectKey, moduleKey));
+ }
+
/**
* SONAR-4547
*/