From 01a14f1ac3e23faa8c213069a9bab099523ea8de Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Thu, 12 Oct 2017 13:30:14 +0200 Subject: [PATCH] SONAR-9943 add WS api/editions/status --- .../edition/EditionManagementState.java | 56 ++++++ .../server/edition/EditionsWsModule.java | 34 ++++ .../org/sonar/server/edition/License.java | 58 ++++++ .../MutableEditionManagementState.java | 74 +++++++ .../StandaloneEditionManagementStateImpl.java | 184 ++++++++++++++++++ .../sonar/server/edition/package-info.java | 23 +++ .../sonar/server/edition/ws/EditionsWs.java | 40 ++++ .../server/edition/ws/EditionsWsAction.java | 28 +++ .../sonar/server/edition/ws/StatusAction.java | 62 ++++++ .../sonar/server/edition/ws/package-info.java | 23 +++ .../platformlevel/PlatformLevel4.java | 4 + .../edition/ws/example-edition-status.json | 5 + .../server/edition/EditionsWsModuleTest.java | 39 ++++ .../org/sonar/server/edition/LicenseTest.java | 83 ++++++++ .../server/edition/ws/StatusActionTest.java | 109 +++++++++++ sonar-ws/src/main/protobuf/ws-editions.proto | 40 ++++ 16 files changed, 862 insertions(+) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/edition/EditionManagementState.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/edition/EditionsWsModule.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/edition/License.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/edition/MutableEditionManagementState.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/edition/StandaloneEditionManagementStateImpl.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/edition/package-info.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWs.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWsAction.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/edition/ws/StatusAction.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/edition/ws/package-info.java create mode 100644 server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-status.json create mode 100644 server/sonar-server/src/test/java/org/sonar/server/edition/EditionsWsModuleTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/edition/LicenseTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/edition/ws/StatusActionTest.java create mode 100644 sonar-ws/src/main/protobuf/ws-editions.proto diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/EditionManagementState.java b/server/sonar-server/src/main/java/org/sonar/server/edition/EditionManagementState.java new file mode 100644 index 00000000000..2a595a5b2a5 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/edition/EditionManagementState.java @@ -0,0 +1,56 @@ +/* + * 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; + +import java.util.Optional; + +public interface EditionManagementState { + /** + * @return {@link Optional#empty() empty} if there is no edition installed + */ + Optional getCurrentEditionKey(); + + /** + * @return the pending installation status. + */ + PendingStatus getPendingInstallationStatus(); + + /** + * @return {@link Optional#empty() empty} when {@link #getPendingInstallationStatus()} returns {@link PendingStatus#NONE}, + * otherwise a {@link String} + */ + Optional getPendingEditionKey(); + + /** + * The license string. + * + * @return {@link Optional#empty() empty} when {@link #getPendingInstallationStatus()} returns {@link PendingStatus#NONE}, + * otherwise a {@link String} + */ + Optional getPendingLicense(); + + enum PendingStatus { + NONE, + AUTOMATIC_IN_PROGRESS, + AUTOMATIC_READY, + AUTOMATIC_FAILED, + MANUAL_IN_PROGRESS + } +} 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 new file mode 100644 index 00000000000..fb05d1834c1 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/edition/EditionsWsModule.java @@ -0,0 +1,34 @@ +/* + * 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; + +import org.sonar.core.platform.Module; +import org.sonar.server.edition.ws.EditionsWs; +import org.sonar.server.edition.ws.StatusAction; + +public class EditionsWsModule extends Module { + @Override + protected void configureModule() { + add( + StandaloneEditionManagementStateImpl.class, + StatusAction.class, + EditionsWs.class); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/License.java b/server/sonar-server/src/main/java/org/sonar/server/edition/License.java new file mode 100644 index 00000000000..f430d26a165 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/edition/License.java @@ -0,0 +1,58 @@ +/* + * 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; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import javax.annotation.concurrent.Immutable; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +@Immutable +public class License { + private final String editionKey; + private final List pluginKeys; + private final String content; + + public License(String editionKey, List pluginKeys, String content) { + this.editionKey = enforceNotNullNorEmpty(editionKey, "editionKey"); + this.pluginKeys = ImmutableList.copyOf(pluginKeys); + this.content = enforceNotNullNorEmpty(content, "content"); + } + + private static String enforceNotNullNorEmpty(String str, String propertyName) { + checkNotNull(str, "%s can't be null", propertyName); + checkArgument(!str.isEmpty(), "%s can't be empty", propertyName); + return str; + } + + public String getEditionKey() { + return editionKey; + } + + public List getPluginKeys() { + return pluginKeys; + } + + public String getContent() { + return content; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/MutableEditionManagementState.java b/server/sonar-server/src/main/java/org/sonar/server/edition/MutableEditionManagementState.java new file mode 100644 index 00000000000..afa9b30bb7b --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/edition/MutableEditionManagementState.java @@ -0,0 +1,74 @@ +/* + * 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; + +/** + * Provides access to operations which will alter the Edition management state. + */ +public interface MutableEditionManagementState extends EditionManagementState { + /** + * Stage the specified license and records that an automatic install of the plugins will be performed. + * + * @return the new {@link PendingStatus}, always {@link PendingStatus#AUTOMATIC_IN_PROGRESS} + * an exception. + * + * @throws IllegalStateException if current status is not {@link PendingStatus#NONE} + */ + PendingStatus startAutomaticInstall(License license); + + /** + * Stage the specified license and records that a manual install of the plugins will be performed. + * + * @return the new {@link PendingStatus}, always {@link PendingStatus#MANUAL_IN_PROGRESS} + * + * @throws IllegalStateException if current status is not {@link PendingStatus#NONE} + */ + PendingStatus startManualInstall(License license); + + /** + * Records that the specified edition has been installed without plugin changes. + * + * @param newEditionKey can't be {@code null} nor empty + * + * @return the new {@link PendingStatus}, always {@link PendingStatus#NONE} + * + * @throws IllegalStateException if current status is not {@link PendingStatus#NONE} + */ + PendingStatus newEditionWithoutInstall(String newEditionKey); + + /** + * Records that automatic install is ready to be finalized. + * + * @return the new pending status, always {@link PendingStatus#AUTOMATIC_READY} + * + * @throws IllegalStateException if current status is not {@link PendingStatus#AUTOMATIC_IN_PROGRESS} + */ + PendingStatus automaticInstallReady(); + + /** + * Finalize an automatic or manual install + * + * @return the new pending status, always {@link PendingStatus#NONE} + * + * @throws IllegalStateException if current status is neither {@link PendingStatus#AUTOMATIC_READY} nor + * {@link PendingStatus#MANUAL_IN_PROGRESS} + */ + PendingStatus finalizeInstallation(); +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/StandaloneEditionManagementStateImpl.java b/server/sonar-server/src/main/java/org/sonar/server/edition/StandaloneEditionManagementStateImpl.java new file mode 100644 index 00000000000..1ca7eb3e62f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/edition/StandaloneEditionManagementStateImpl.java @@ -0,0 +1,184 @@ +/* + * 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; + +import com.google.common.collect.ImmutableSet; +import java.util.Map; +import java.util.Optional; +import org.picocontainer.Startable; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.property.InternalPropertiesDao; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static java.util.Objects.requireNonNull; +import static java.util.Optional.empty; +import static org.sonar.server.edition.EditionManagementState.PendingStatus.AUTOMATIC_IN_PROGRESS; +import static org.sonar.server.edition.EditionManagementState.PendingStatus.AUTOMATIC_READY; +import static org.sonar.server.edition.EditionManagementState.PendingStatus.MANUAL_IN_PROGRESS; +import static org.sonar.server.edition.EditionManagementState.PendingStatus.NONE; + +public class StandaloneEditionManagementStateImpl implements MutableEditionManagementState, Startable { + private static final String CURRENT_EDITION_KEY = "sonar.editionManagement.currentEditionKey"; + private static final String PENDING_INSTALLATION_STATUS = "sonar.editionManagement.pendingInstallationStatus"; + private static final String PENDING_EDITION_KEY = "sonar.editionManagement.pendingEditionKey"; + private static final String PENDING_LICENSE = "sonar.editionManagement.pendingLicense"; + + private final DbClient dbClient; + private String currentEditionKey; + private PendingStatus pendingInstallationStatus = NONE; + private String pendingEditionKey; + private String pendingLicense; + + public StandaloneEditionManagementStateImpl(DbClient dbClient) { + this.dbClient = dbClient; + } + + @Override + public void start() { + try (DbSession dbSession = dbClient.openSession(false)) { + // load current state value + Map> internalPropertyValues = dbClient.internalPropertiesDao().selectByKeys(dbSession, + ImmutableSet.of(CURRENT_EDITION_KEY, PENDING_INSTALLATION_STATUS, PENDING_EDITION_KEY, PENDING_LICENSE)); + this.currentEditionKey = internalPropertyValues.getOrDefault(CURRENT_EDITION_KEY, empty()) + .map(StandaloneEditionManagementStateImpl::emptyToNull) + .orElse(null); + this.pendingInstallationStatus = internalPropertyValues.getOrDefault(PENDING_INSTALLATION_STATUS, empty()) + .map(PendingStatus::valueOf) + .orElse(NONE); + this.pendingEditionKey = internalPropertyValues.getOrDefault(PENDING_EDITION_KEY, empty()) + .map(StandaloneEditionManagementStateImpl::emptyToNull) + .orElse(null); + this.pendingLicense = internalPropertyValues.getOrDefault(PENDING_LICENSE, empty()).orElse(null); + } + } + + @Override + public void stop() { + // nothing to do + } + + @Override + public Optional getCurrentEditionKey() { + return Optional.ofNullable(currentEditionKey); + } + + @Override + public PendingStatus getPendingInstallationStatus() { + return pendingInstallationStatus; + } + + @Override + public Optional getPendingEditionKey() { + return Optional.ofNullable(pendingEditionKey); + } + + @Override + public Optional getPendingLicense() { + return Optional.ofNullable(pendingLicense); + } + + @Override + public synchronized PendingStatus startAutomaticInstall(License license) { + checkLicense(license); + changeStatusFromTo(NONE, AUTOMATIC_IN_PROGRESS); + this.pendingLicense = license.getContent(); + this.pendingEditionKey = license.getEditionKey(); + persistProperties(); + return this.pendingInstallationStatus; + } + + @Override + public synchronized PendingStatus startManualInstall(License license) { + checkLicense(license); + changeStatusFromTo(NONE, MANUAL_IN_PROGRESS); + this.pendingLicense = license.getContent(); + this.pendingEditionKey = license.getEditionKey(); + this.pendingInstallationStatus = MANUAL_IN_PROGRESS; + persistProperties(); + return this.pendingInstallationStatus; + } + + @Override + public synchronized PendingStatus newEditionWithoutInstall(String newEditionKey) { + requireNonNull(newEditionKey, "newEditionKey can't be null"); + checkArgument(!newEditionKey.isEmpty(), "newEditionKey can't be empty"); + changeStatusFromTo(NONE, NONE); + this.currentEditionKey = newEditionKey; + persistProperties(); + return this.pendingInstallationStatus; + } + + @Override + public synchronized PendingStatus automaticInstallReady() { + changeStatusFromTo(AUTOMATIC_IN_PROGRESS, AUTOMATIC_READY); + persistProperties(); + return this.pendingInstallationStatus; + } + + @Override + public synchronized PendingStatus finalizeInstallation() { + checkState(this.pendingInstallationStatus == AUTOMATIC_READY || this.pendingInstallationStatus == MANUAL_IN_PROGRESS, + "Can't finalize installation when state is %s", this.pendingInstallationStatus); + + this.pendingInstallationStatus = NONE; + this.currentEditionKey = this.pendingEditionKey; + this.pendingEditionKey = null; + this.pendingLicense = null; + persistProperties(); + return this.pendingInstallationStatus; + } + + private void changeStatusFromTo(PendingStatus expectedStatus, PendingStatus newStatus) { + checkState(pendingInstallationStatus == expectedStatus, + "Can't move to {} when status is {} (should be {})", + newStatus, pendingInstallationStatus, expectedStatus); + this.pendingInstallationStatus = newStatus; + } + + private void persistProperties() { + try (DbSession dbSession = dbClient.openSession(false)) { + InternalPropertiesDao internalPropertiesDao = dbClient.internalPropertiesDao(); + if (pendingInstallationStatus == NONE) { + internalPropertiesDao.saveAsEmpty(dbSession, PENDING_EDITION_KEY); + internalPropertiesDao.saveAsEmpty(dbSession, PENDING_LICENSE); + } else { + internalPropertiesDao.save(dbSession, PENDING_EDITION_KEY, pendingEditionKey); + internalPropertiesDao.save(dbSession, PENDING_LICENSE, pendingLicense); + } + if (currentEditionKey == null) { + internalPropertiesDao.saveAsEmpty(dbSession, CURRENT_EDITION_KEY); + } else { + internalPropertiesDao.save(dbSession, CURRENT_EDITION_KEY, currentEditionKey); + } + internalPropertiesDao.save(dbSession, PENDING_INSTALLATION_STATUS, pendingInstallationStatus.name()); + dbSession.commit(); + } + } + + private static void checkLicense(License license) { + requireNonNull(license, "license can't be null"); + } + + private static String emptyToNull(String s) { + return s.isEmpty() ? null : s; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/edition/package-info.java new file mode 100644 index 00000000000..952be45c221 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/edition/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.edition; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWs.java b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWs.java new file mode 100644 index 00000000000..d93af9cf95f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWs.java @@ -0,0 +1,40 @@ +/* + * 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 java.util.Arrays; +import org.sonar.api.server.ws.WebService; + +public class EditionsWs implements WebService { + private final EditionsWsAction[] actions; + + public EditionsWs(EditionsWsAction[] actions) { + this.actions = actions; + } + + @Override + public void define(Context context) { + NewController controller = context.createController("api/editions") + .setSince("6.7") + .setDescription("Manage SonarSource commercial editions."); + Arrays.stream(actions).forEach(action -> action.define(controller)); + controller.done(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWsAction.java new file mode 100644 index 00000000000..a1de9b1ec3f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWsAction.java @@ -0,0 +1,28 @@ +/* + * 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.server.ws.WsAction; + +/** + * Marker interface for actions of {@link EditionsWs}. + */ +public interface EditionsWsAction extends WsAction { +} 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 new file mode 100644 index 00000000000..4c44999b959 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/StatusAction.java @@ -0,0 +1,62 @@ +/* + * 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.user.UserSession; +import org.sonar.server.ws.WsUtils; +import org.sonarqube.ws.WsEditions; + +public class StatusAction implements EditionsWsAction { + private final UserSession userSession; + private final EditionManagementState editionManagementState; + + public StatusAction(UserSession userSession, EditionManagementState editionManagementState) { + this.userSession = userSession; + this.editionManagementState = editionManagementState; + } + + @Override + public void define(WebService.NewController controller) { + controller.createAction("status") + .setSince("6.7") + .setPost(false) + .setDescription("Provide status of SonarSource commercial edition of the current SonarQube. Require 'Administer System' permission.") + .setResponseExample(getClass().getResource("example-edition-status.json")) + .setHandler(this); + } + + @Override + public void handle(Request request, Response response) throws Exception { + userSession + .checkLoggedIn() + .checkIsSystemAdministrator(); + + WsEditions.StatusResponse.Builder responseBuilder = WsEditions.StatusResponse.newBuilder() + .setCurrentEditionKey(editionManagementState.getCurrentEditionKey().orElse("")) + .setNextEditionKey(editionManagementState.getPendingEditionKey().orElse("")) + .setInstallationStatus(WsEditions.InstallationStatus.valueOf(editionManagementState.getPendingInstallationStatus().name())); + + WsUtils.writeProtobuf(responseBuilder.build(), request, response); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/ws/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/package-info.java new file mode 100644 index 00000000000..39e2c648310 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.edition.ws; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 56376b8e7a2..7038d965aef 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -53,6 +53,7 @@ import org.sonar.server.debt.DebtRulesXMLImporter; import org.sonar.server.duplication.ws.DuplicationsParser; import org.sonar.server.duplication.ws.DuplicationsWs; import org.sonar.server.duplication.ws.ShowResponseBuilder; +import org.sonar.server.edition.EditionsWsModule; import org.sonar.server.email.ws.EmailsWsModule; import org.sonar.server.es.IndexCreator; import org.sonar.server.es.IndexDefinitions; @@ -527,6 +528,9 @@ public class PlatformLevel4 extends PlatformLevel { CeModule.class, CeWsModule.class, + // SonarSource editions + EditionsWsModule.class, + InternalPropertiesImpl.class, ProjectConfigurationFactory.class, diff --git a/server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-status.json b/server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-status.json new file mode 100644 index 00000000000..aff72735324 --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-status.json @@ -0,0 +1,5 @@ +{ + "currentEditionKey": "", + "installationStatus": "AUTOMATIC_READY", + "nextEditionKey": "developer-edition" +} 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 new file mode 100644 index 00000000000..c4fae543a77 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/edition/EditionsWsModuleTest.java @@ -0,0 +1,39 @@ +/* + * 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; + +import org.junit.Test; +import org.sonar.core.platform.ComponentContainer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER; + +public class EditionsWsModuleTest { + private EditionsWsModule underTest = new EditionsWsModule(); + + @Test + public void verify_component_count() { + ComponentContainer container = new ComponentContainer(); + underTest.configure(container); + + assertThat(container.getPicoContainer().getComponentAdapters()) + .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 3); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/edition/LicenseTest.java b/server/sonar-server/src/test/java/org/sonar/server/edition/LicenseTest.java new file mode 100644 index 00000000000..51c0e2762a3 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/edition/LicenseTest.java @@ -0,0 +1,83 @@ +/* + * 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; + +import com.google.common.collect.ImmutableList; +import java.util.Collections; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LicenseTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void constructor_fails_if_editionKey_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("editionKey can't be null"); + + new License(null, Collections.emptyList(), "content"); + } + + @Test + public void constructor_fails_if_editionKey_is_empty() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("editionKey can't be empty"); + + new License("", Collections.emptyList(), "content"); + } + + @Test + public void constructor_fails_if_plugins_is_null() { + expectedException.expect(NullPointerException.class); + + new License("edition-key", null, "content"); + } + + @Test + public void constructor_fails_if_content_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("content can't be null"); + + new License("edition-key", Collections.emptyList(), null); + } + + @Test + public void constructor_fails_if_content_is_empty() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("content can't be empty"); + + new License("edition-key", Collections.emptyList(), ""); + } + + @Test + public void verify_getters() { + ImmutableList pluginKeys = ImmutableList.of("a", "b", "c"); + + License underTest = new License("edition-key", pluginKeys, "content"); + + assertThat(underTest.getEditionKey()).isEqualTo("edition-key"); + assertThat(underTest.getPluginKeys()).isEqualTo(pluginKeys); + assertThat(underTest.getContent()).isEqualTo("content"); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/edition/ws/StatusActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/edition/ws/StatusActionTest.java new file mode 100644 index 00000000000..e37a88ffa12 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/edition/ws/StatusActionTest.java @@ -0,0 +1,109 @@ +/* + * 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 java.util.Optional; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.server.ws.WebService; +import org.sonar.server.edition.EditionManagementState; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.WsActionTester; +import org.sonar.test.JsonAssert; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class StatusActionTest { + @Rule + public UserSessionRule userSessionRule = UserSessionRule.standalone(); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private EditionManagementState editionManagementState = mock(EditionManagementState.class); + private StatusAction underTest = new StatusAction(userSessionRule, editionManagementState); + private WsActionTester actionTester = new WsActionTester(underTest); + + @Test + public void verify_definition() { + WebService.Action def = actionTester.getDef(); + assertThat(def.key()).isEqualTo("status"); + assertThat(def.since()).isEqualTo("6.7"); + assertThat(def.isPost()).isFalse(); + 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 + public void verify_example() { + userSessionRule.logIn().setSystemAdministrator(); + when(editionManagementState.getCurrentEditionKey()).thenReturn(Optional.empty()); + when(editionManagementState.getPendingEditionKey()).thenReturn(Optional.of("developer-edition")); + when(editionManagementState.getPendingInstallationStatus()).thenReturn(EditionManagementState.PendingStatus.AUTOMATIC_READY); + + TestRequest request = actionTester.newRequest(); + + JsonAssert.assertJson(request.execute().getInput()).isSimilarTo(actionTester.getDef().responseExampleAsString()); + } + + @Test + public void response_contains_optional_fields_as_empty_string() { + userSessionRule.logIn().setSystemAdministrator(); + when(editionManagementState.getCurrentEditionKey()).thenReturn(Optional.empty()); + when(editionManagementState.getPendingEditionKey()).thenReturn(Optional.empty()); + when(editionManagementState.getPendingInstallationStatus()).thenReturn(EditionManagementState.PendingStatus.NONE); + + TestRequest request = actionTester.newRequest(); + + JsonAssert.assertJson(request.execute().getInput()) + .isSimilarTo("{" + + " \"currentEditionKey\": \"\"," + + " \"installationStatus\": \"NONE\"," + + " \"nextEditionKey\": \"\"" + + "}"); + } +} diff --git a/sonar-ws/src/main/protobuf/ws-editions.proto b/sonar-ws/src/main/protobuf/ws-editions.proto new file mode 100644 index 00000000000..9e486715aae --- /dev/null +++ b/sonar-ws/src/main/protobuf/ws-editions.proto @@ -0,0 +1,40 @@ +// SonarQube, open source software quality management tool. +// Copyright (C) 2008-2016 SonarSource +// mailto:contact AT sonarsource DOT com +// +// SonarQube 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. +// +// SonarQube 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. + +syntax = "proto2"; + +package sonarqube.ws.editions; + +option java_package = "org.sonarqube.ws"; +option java_outer_classname = "WsEditions"; +option optimize_for = SPEED; + +// GET api/editions/status +message StatusResponse { + optional string currentEditionKey = 1; + optional InstallationStatus installationStatus = 2; + optional string nextEditionKey = 3; +} + +enum InstallationStatus { + NONE = 0; + AUTOMATIC_IN_PROGRESS = 1; + AUTOMATIC_READY = 2; + AUTOMATIC_FAILED = 3; + MANUAL_IN_PROGRESS = 4; +} -- 2.39.5