From 76a07c6cb7aa41bcea2b79e44560816db955dde3 Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Wed, 18 Oct 2017 14:15:18 +0200 Subject: [PATCH] SONAR-9996 Add WS api/editions/uninstall --- .../server/edition/EditionsWsModule.java | 2 + .../server/edition/ws/PreviewAction.java | 2 +- .../sonar/server/edition/ws/StatusAction.java | 2 +- .../server/edition/ws/UninstallAction.java | 64 ++++++++ .../server/edition/EditionsWsModuleTest.java | 2 +- .../edition/ws/UninstallActionTest.java | 137 ++++++++++++++++++ sonar-ws/src/main/protobuf/ws-editions.proto | 1 + 7 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/edition/ws/UninstallAction.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/edition/ws/UninstallActionTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/EditionsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/edition/EditionsWsModule.java index 958250388d5..7470ffc2a37 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/edition/EditionsWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/edition/EditionsWsModule.java @@ -25,6 +25,7 @@ import org.sonar.server.edition.ws.EditionsWs; import org.sonar.server.edition.ws.FormDataAction; import org.sonar.server.edition.ws.PreviewAction; import org.sonar.server.edition.ws.StatusAction; +import org.sonar.server.edition.ws.UninstallAction; public class EditionsWsModule extends Module { @Override @@ -34,6 +35,7 @@ public class EditionsWsModule extends Module { ApplyLicenseAction.class, PreviewAction.class, FormDataAction.class, + UninstallAction.class, EditionsWs.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/ws/PreviewAction.java b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/PreviewAction.java index b159e48d9c3..e5de17fa81a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/edition/ws/PreviewAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/PreviewAction.java @@ -55,7 +55,7 @@ public class PreviewAction implements EditionsWsAction { WebService.NewAction action = controller.createAction("preview") .setSince("6.7") .setPost(true) - .setDescription("Preview the changes to SonarQube to match the specified license. Require 'Administer System' permission.") + .setDescription("Preview the changes to SonarQube to match the specified license. Requires 'Administer System' permission.") .setResponseExample(getClass().getResource("example-edition-preview.json")) .setHandler(this); diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/ws/StatusAction.java b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/StatusAction.java index 4c44999b959..4c93a2e2cac 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/edition/ws/StatusAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/StatusAction.java @@ -41,7 +41,7 @@ public class StatusAction implements EditionsWsAction { controller.createAction("status") .setSince("6.7") .setPost(false) - .setDescription("Provide status of SonarSource commercial edition of the current SonarQube. Require 'Administer System' permission.") + .setDescription("Provide status of SonarSource commercial edition of the current SonarQube. Requires 'Administer System' permission.") .setResponseExample(getClass().getResource("example-edition-status.json")) .setHandler(this); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/ws/UninstallAction.java b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/UninstallAction.java new file mode 100644 index 00000000000..67c1d269616 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/UninstallAction.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.edition.ws; + +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.edition.EditionManagementState; +import org.sonar.server.edition.MutableEditionManagementState; +import org.sonar.server.exceptions.BadRequestException; +import org.sonar.server.plugins.edition.EditionInstaller; +import org.sonar.server.user.UserSession; + +public class UninstallAction implements EditionsWsAction { + private final UserSession userSession; + private final MutableEditionManagementState mutableEditionManagementState; + private final EditionInstaller editionInstaller; + + public UninstallAction(UserSession userSession, MutableEditionManagementState mutableEditionManagementState, EditionInstaller editionInstaller) { + this.userSession = userSession; + this.mutableEditionManagementState = mutableEditionManagementState; + this.editionInstaller = editionInstaller; + } + + @Override + public void define(WebService.NewController controller) { + controller.createAction("uninstall") + .setSince("6.7") + .setPost(true) + .setDescription("Uninstall the currently installed edition. Requires 'Administer System' permission.") + .setHandler(this); + } + + @Override + public void handle(Request request, Response response) throws Exception { + userSession.checkLoggedIn().checkIsSystemAdministrator(); + + if (mutableEditionManagementState.getPendingInstallationStatus() != EditionManagementState.PendingStatus.NONE + && mutableEditionManagementState.getPendingInstallationStatus() != EditionManagementState.PendingStatus.UNINSTALL_IN_PROGRESS) { + throw BadRequestException.create("Uninstall of the current edition is not allowed when install of an edition is in progress"); + } + + editionInstaller.uninstall(); + mutableEditionManagementState.uninstall(); + response.noContent(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/edition/EditionsWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/edition/EditionsWsModuleTest.java index a499e10eeef..34ab5aa5fa3 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/edition/EditionsWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/edition/EditionsWsModuleTest.java @@ -34,6 +34,6 @@ public class EditionsWsModuleTest { underTest.configure(container); assertThat(container.getPicoContainer().getComponentAdapters()) - .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 5); + .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 6); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/edition/ws/UninstallActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/edition/ws/UninstallActionTest.java new file mode 100644 index 00000000000..aecb77356d4 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/edition/ws/UninstallActionTest.java @@ -0,0 +1,137 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.edition.ws; + +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.util.Arrays; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.sonar.api.server.ws.WebService; +import org.sonar.server.edition.EditionManagementState; +import org.sonar.server.edition.MutableEditionManagementState; +import org.sonar.server.exceptions.BadRequestException; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.plugins.edition.EditionInstaller; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.TestResponse; +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.verify; +import static org.mockito.Mockito.when; +import static org.sonar.server.edition.EditionManagementState.PendingStatus.NONE; +import static org.sonar.server.edition.EditionManagementState.PendingStatus.UNINSTALL_IN_PROGRESS; + +@RunWith(DataProviderRunner.class) +public class UninstallActionTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public UserSessionRule userSessionRule = UserSessionRule.standalone(); + + private EditionInstaller editionInstaller = mock(EditionInstaller.class); + private MutableEditionManagementState mutableEditionManagementState = mock(MutableEditionManagementState.class); + private UninstallAction action = new UninstallAction(userSessionRule, mutableEditionManagementState, editionInstaller); + private WsActionTester actionTester = new WsActionTester(action); + + @Test + public void check_definition() { + WebService.Action def = actionTester.getDef(); + + assertThat(def.key()).isEqualTo("uninstall"); + assertThat(def.since()).isEqualTo("6.7"); + assertThat(def.isPost()).isTrue(); + assertThat(def.isInternal()).isFalse(); + assertThat(def.responseExampleAsString()).isNull(); + assertThat(def.description()).isNotEmpty(); + assertThat(def.params()).isEmpty(); + } + + @Test + public void request_fails_if_user_not_logged_in() { + userSessionRule.anonymous(); + TestRequest request = actionTester.newRequest(); + + expectedException.expect(UnauthorizedException.class); + expectedException.expectMessage("Authentication is required"); + + request.execute(); + } + + @Test + public void request_fails_if_user_is_not_system_administer() { + userSessionRule.logIn(); + TestRequest request = actionTester.newRequest(); + + expectedException.expect(ForbiddenException.class); + expectedException.expectMessage("Insufficient privileges"); + + request.execute(); + } + + @Test + @UseDataProvider("notNoneOrUninstallStatuses") + public void request_fails_if_current_status_is_not_none(EditionManagementState.PendingStatus notNoneOrUninstall) { + userSessionRule.logIn().setSystemAdministrator(); + when(mutableEditionManagementState.getPendingInstallationStatus()).thenReturn(notNoneOrUninstall); + + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("Uninstall of the current edition is not allowed when install of an edition"); + actionTester.newRequest().execute(); + } + + @Test + public void successful_edition_uninstall() { + userSessionRule.logIn().setSystemAdministrator(); + when(mutableEditionManagementState.getPendingInstallationStatus()).thenReturn(NONE); + + TestResponse execute = actionTester.newRequest().execute(); + assertThat(execute.getStatus()).isEqualTo(204); + verify(editionInstaller).uninstall(); + verify(mutableEditionManagementState).uninstall(); + } + + @Test + public void successful_edition_uninstall_when_state_is_already_uninstall_in_progress() { + userSessionRule.logIn().setSystemAdministrator(); + when(mutableEditionManagementState.getPendingInstallationStatus()).thenReturn(UNINSTALL_IN_PROGRESS); + + TestResponse execute = actionTester.newRequest().execute(); + assertThat(execute.getStatus()).isEqualTo(204); + verify(editionInstaller).uninstall(); + verify(mutableEditionManagementState).uninstall(); + } + + @DataProvider + public static Object[][] notNoneOrUninstallStatuses() { + return Arrays.stream(EditionManagementState.PendingStatus.values()) + .filter(s -> s != NONE) + .filter(s -> s != UNINSTALL_IN_PROGRESS) + .map(s -> new Object[] {s}) + .toArray(Object[][]::new); + } +} diff --git a/sonar-ws/src/main/protobuf/ws-editions.proto b/sonar-ws/src/main/protobuf/ws-editions.proto index c8dc31ee29d..2c72180bacd 100644 --- a/sonar-ws/src/main/protobuf/ws-editions.proto +++ b/sonar-ws/src/main/protobuf/ws-editions.proto @@ -38,6 +38,7 @@ enum InstallationStatus { AUTOMATIC_READY = 2; AUTOMATIC_FAILED = 3; MANUAL_IN_PROGRESS = 4; + UNINSTALL_IN_PROGRESS = 5; } // POST api/editions/preview -- 2.39.5