From eb34454afc19f4994ae587b43ff2d0c79978de23 Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Mon, 14 Mar 2022 11:29:02 -0500 Subject: [PATCH] SONAR-16096 Create webservices to get and clear scanner plugin cache --- .../server/component/ComponentFinder.java | 5 - .../server/scannercache/ScannerCache.java | 52 ++++++ .../server/scannercache/package-info.java | 23 +++ .../server/scannercache/ws/ClearAction.java | 62 +++++++ .../server/scannercache/ws/GetAction.java | 129 ++++++++++++++ .../scannercache/ws/ScannerCacheWs.java | 45 +++++ .../scannercache/ws/ScannerCacheWsAction.java | 30 ++++ .../scannercache/ws/ScannerCacheWsModule.java | 33 ++++ .../server/scannercache/ws/package-info.java | 23 +++ .../server/ce/queue/CeQueueCleanerTest.java | 3 +- .../scannercache/ws/ClearActionTest.java | 93 +++++++++++ .../server/scannercache/ws/GetActionTest.java | 158 ++++++++++++++++++ .../ws/ScannerCacheWsModuleTest.java | 35 ++++ .../scannercache/ws/ScannerCacheWsTest.java | 56 +++++++ .../platformlevel/PlatformLevel4.java | 6 + 15 files changed, 747 insertions(+), 6 deletions(-) create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ScannerCache.java create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/package-info.java create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/ClearAction.java create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/GetAction.java create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/ScannerCacheWs.java create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/ScannerCacheWsAction.java create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/ScannerCacheWsModule.java create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/package-info.java create mode 100644 server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/ClearActionTest.java create mode 100644 server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/GetActionTest.java create mode 100644 server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/ScannerCacheWsModuleTest.java create mode 100644 server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/ScannerCacheWsTest.java diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentFinder.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentFinder.java index 8c4a77febfa..5a26580fdbb 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentFinder.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentFinder.java @@ -126,11 +126,6 @@ public class ComponentFinder { .orElseThrow(() -> new IllegalStateException(String.format("Can't find main branch for project '%s'", projectDto.getKey()))); } - public BranchDto getMainBranch(DbSession dbSession, ComponentDto projectDto) { - return dbClient.branchDao().selectByUuid(dbSession, projectDto.uuid()) - .orElseThrow(() -> new IllegalStateException(String.format("Can't find main branch for project '%s'", projectDto.getKey()))); - } - private static void checkByUuidOrKey(@Nullable String componentUuid, @Nullable String componentKey, ParamNames parameterNames) { checkArgument(componentUuid != null ^ componentKey != null, MSG_COMPONENT_ID_OR_KEY_TEMPLATE, parameterNames.getUuidParam(), parameterNames.getKeyParam()); } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ScannerCache.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ScannerCache.java new file mode 100644 index 00000000000..dc59028dffa --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ScannerCache.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.scannercache; + +import javax.annotation.CheckForNull; +import org.sonar.api.server.ServerSide; +import org.sonar.db.DbClient; +import org.sonar.db.DbInputStream; +import org.sonar.db.DbSession; +import org.sonar.db.scannercache.ScannerCacheDao; + +@ServerSide +public class ScannerCache { + private final DbClient dbClient; + private final ScannerCacheDao dao; + + public ScannerCache(DbClient dbClient, ScannerCacheDao dao) { + this.dbClient = dbClient; + this.dao = dao; + } + + @CheckForNull + public DbInputStream get(String branchUuid) { + try (DbSession session = dbClient.openSession(false)) { + return dao.selectData(session, branchUuid); + } + } + + public void clear() { + try (DbSession session = dbClient.openSession(false)) { + dao.removeAll(session); + session.commit(); + } + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/package-info.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/package-info.java new file mode 100644 index 00000000000..fdd26060e29 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.scannercache; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/ClearAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/ClearAction.java new file mode 100644 index 00000000000..44794a1cf7c --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/ClearAction.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.scannercache.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.db.permission.GlobalPermission; +import org.sonar.server.scannercache.ScannerCache; +import org.sonar.server.user.UserSession; + +import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; + +public class ClearAction implements ScannerCacheWsAction { + private final UserSession userSession; + private final ScannerCache cache; + + public ClearAction(UserSession userSession, ScannerCache cache) { + this.userSession = userSession; + this.cache = cache; + } + + @Override + public void define(WebService.NewController context) { + context.createAction("clear") + .setInternal(true) + .setPost(true) + .setDescription("Clear the scanner's cached data for all projects and branches. Requires global administration permission. ") + .setSince("9.4") + .setHandler(this); + } + + @Override + public void handle(Request request, Response response) throws Exception { + checkPermission(); + cache.clear(); + response.noContent(); + } + + private void checkPermission() { + if (!userSession.hasPermission(GlobalPermission.ADMINISTER)) { + throw insufficientPrivilegesException(); + } + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/GetAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/GetAction.java new file mode 100644 index 00000000000..5db206245a8 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/GetAction.java @@ -0,0 +1,129 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.scannercache.ws; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.zip.GZIPInputStream; +import org.apache.commons.io.IOUtils; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbInputStream; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.scannercache.ScannerCache; +import org.sonar.server.user.UserSession; + +import static org.sonar.db.permission.GlobalPermission.SCAN; +import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; +import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; + +public class GetAction implements ScannerCacheWsAction { + private static final String PROJECT = "project"; + private static final String BRANCH = "branch"; + + private final DbClient dbClient; + private final UserSession userSession; + private final ComponentFinder componentFinder; + private final ScannerCache cache; + + public GetAction(DbClient dbClient, UserSession userSession, ComponentFinder componentFinder, ScannerCache cache) { + this.dbClient = dbClient; + this.userSession = userSession; + this.componentFinder = componentFinder; + this.cache = cache; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("get") + .setInternal(true) + .setDescription("Get the scanner's cached data for a branch. Requires scan permission on the project. " + + "Data is returned gzipped if the corresponding 'Accept-Encoding' header is set in the request.") + .setSince("9.4") + .setHandler(this); + + action.createParam(PROJECT) + .setDescription("Project key") + .setExampleValue(KEY_PROJECT_EXAMPLE_001) + .setRequired(true); + + action.createParam(BRANCH) + .setDescription("Branch key. If not provided, main branch will be used.") + .setExampleValue(KEY_BRANCH_EXAMPLE_001) + .setRequired(false); + } + + @Override + public void handle(Request request, Response response) throws Exception { + String projectKey = request.mandatoryParam(PROJECT); + String branchKey = request.param(BRANCH); + + try (DbSession dbSession = dbClient.openSession(false)) { + ComponentDto component = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, projectKey, branchKey, null); + checkPermission(component); + + try (DbInputStream dbInputStream = cache.get(component.uuid())) { + if (dbInputStream == null) { + throw new NotFoundException("No cache for given branch"); + } + + boolean compressed = requestedCompressedData(request); + try (OutputStream output = response.stream().output()) { + if (compressed) { + response.setHeader("Content-Encoding", "gzip"); + // data is stored compressed + IOUtils.copy(dbInputStream, output); + } else { + try (InputStream uncompressedInput = new GZIPInputStream(dbInputStream)) { + IOUtils.copy(uncompressedInput, output); + } + } + } + } + } + } + + private static boolean requestedCompressedData(Request request) { + String encoding = request.getHeaders().get("Accept-Encoding"); + if (encoding == null) { + return false; + } + return Arrays.stream(encoding.split(",")) + .map(String::trim) + .anyMatch("gzip"::equals); + } + + private void checkPermission(ComponentDto project) { + if (userSession.hasComponentPermission(UserRole.SCAN, project) || + userSession.hasComponentPermission(UserRole.ADMIN, project) || + userSession.hasPermission(SCAN)) { + return; + } + throw insufficientPrivilegesException(); + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/ScannerCacheWs.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/ScannerCacheWs.java new file mode 100644 index 00000000000..d69e26bfcc4 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/ScannerCacheWs.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.scannercache.ws; + +import org.sonar.api.server.ws.WebService; + +public class ScannerCacheWs implements WebService { + + private final ScannerCacheWsAction[] actions; + + public ScannerCacheWs(ScannerCacheWsAction... actions) { + this.actions = actions; + } + + @Override + public void define(Context context) { + NewController controller = context + .createController("api/scanner_cache") + .setSince("9.4") + .setDescription("Access the scanner cache"); + + for (ScannerCacheWsAction action : actions) { + action.define(controller); + } + + controller.done(); + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/ScannerCacheWsAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/ScannerCacheWsAction.java new file mode 100644 index 00000000000..0da2162a7c9 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/ScannerCacheWsAction.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.scannercache.ws; + +import org.sonar.server.ws.WsAction; + +/** + * Marker interface for coding rule related actions + * + */ +interface ScannerCacheWsAction extends WsAction { + // Marker interface +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/ScannerCacheWsModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/ScannerCacheWsModule.java new file mode 100644 index 00000000000..f3d14e98a5a --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/ScannerCacheWsModule.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.scannercache.ws; + +import org.sonar.core.platform.Module; + +public class ScannerCacheWsModule extends Module { + @Override + protected void configureModule() { + add( + ScannerCacheWs.class, + GetAction.class, + ClearAction.class + ); + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/package-info.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/package-info.java new file mode 100644 index 00000000000..9147763cb27 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.scannercache.ws; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/queue/CeQueueCleanerTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/queue/CeQueueCleanerTest.java index 3fd68497843..89af15825a4 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/queue/CeQueueCleanerTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/queue/CeQueueCleanerTest.java @@ -27,6 +27,7 @@ import org.sonar.api.config.internal.MapSettings; import org.sonar.api.platform.ServerUpgradeStatus; import org.sonar.api.utils.System2; import org.sonar.ce.queue.CeQueue; +import org.sonar.db.DbInputStream; import org.sonar.db.DbTester; import org.sonar.db.ce.CeQueueDto; import org.sonar.db.ce.CeTaskInputDao; @@ -89,7 +90,7 @@ public class CeQueueCleanerTest { runCleaner(); CeTaskInputDao dataDao = dbTester.getDbClient().ceTaskInputDao(); - Optional task1Data = dataDao.selectData(dbTester.getSession(), "TASK_1"); + Optional task1Data = dataDao.selectData(dbTester.getSession(), "TASK_1"); assertThat(task1Data).isPresent(); task1Data.get().close(); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/ClearActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/ClearActionTest.java new file mode 100644 index 00000000000..5bd61e4aafe --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/ClearActionTest.java @@ -0,0 +1,93 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.scannercache.ws; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.IOUtils; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.db.DbInputStream; +import org.sonar.db.DbTester; +import org.sonar.db.permission.GlobalPermission; +import org.sonar.db.project.ProjectDto; +import org.sonar.db.scannercache.ScannerCacheDao; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.scannercache.ScannerCache; +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.assertj.core.api.Assertions.assertThatThrownBy; +import static org.sonar.api.web.UserRole.SCAN; + +public class ClearActionTest { + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private final ScannerCacheDao dao = new ScannerCacheDao(); + private final ScannerCache cache = new ScannerCache(dbTester.getDbClient(), dao); + private final ClearAction ws = new ClearAction(userSession, cache); + private final WsActionTester wsTester = new WsActionTester(ws); + + @Test + public void should_clear_all_entries() throws IOException { + ProjectDto project1 = dbTester.components().insertPrivateProjectDto(); + ProjectDto project2 = dbTester.components().insertPrivateProjectDto(); + + dao.insert(dbTester.getSession(), project1.getUuid(), stringToInputStream("test data")); + dao.insert(dbTester.getSession(), project2.getUuid(), stringToInputStream("test data")); + + assertThat(dataStreamToString(dao.selectData(dbTester.getSession(), project1.getUuid()))).isEqualTo("test data"); + userSession.logIn().addPermission(GlobalPermission.ADMINISTER); + TestResponse response = wsTester.newRequest().execute(); + + response.assertNoContent(); + assertThat(dbTester.countRowsOfTable("scanner_cache")).isZero(); + } + + @Test + public void fail_if_not_global_admin() throws IOException { + ProjectDto project = dbTester.components().insertPrivateProjectDto(); + dao.insert(dbTester.getSession(), "branch1", stringToInputStream("test data")); + assertThat(dataStreamToString(dao.selectData(dbTester.getSession(), "branch1"))).isEqualTo("test data"); + userSession.logIn().addProjectPermission(SCAN, project); + TestRequest request = wsTester.newRequest(); + + assertThatThrownBy(request::execute).isInstanceOf(ForbiddenException.class); + } + + private static String dataStreamToString(DbInputStream dbInputStream) throws IOException { + try (DbInputStream ds = dbInputStream) { + return IOUtils.toString(ds, StandardCharsets.UTF_8); + } + } + + private static InputStream stringToInputStream(String str) { + return new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/GetActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/GetActionTest.java new file mode 100644 index 00000000000..7135bf77b67 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/GetActionTest.java @@ -0,0 +1,158 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.scannercache.ws; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; +import org.apache.commons.io.IOUtils; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbTester; +import org.sonar.db.component.BranchDto; +import org.sonar.db.project.ProjectDto; +import org.sonar.db.scannercache.ScannerCacheDao; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.scannercache.ScannerCache; +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 java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.sonar.api.web.UserRole.SCAN; + +public class GetActionTest { + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private final ScannerCacheDao dao = new ScannerCacheDao(); + private final ScannerCache cache = new ScannerCache(dbTester.getDbClient(), dao); + private final ComponentFinder finder = new ComponentFinder(dbTester.getDbClient(), null); + private final GetAction ws = new GetAction(dbTester.getDbClient(), userSession, finder, cache); + private final WsActionTester wsTester = new WsActionTester(ws); + + @Test + public void get_data_for_project() throws IOException { + ProjectDto project1 = dbTester.components().insertPrivateProjectDto(); + BranchDto branch = dbTester.components().insertProjectBranch(project1); + ProjectDto project2 = dbTester.components().insertPrivateProjectDto(); + + dao.insert(dbTester.getSession(), project1.getUuid(), stringToCompressedInputStream("test data1")); + dao.insert(dbTester.getSession(), branch.getUuid(), stringToCompressedInputStream("test data2")); + dao.insert(dbTester.getSession(), project2.getUuid(), stringToCompressedInputStream("test data3")); + + userSession.logIn().addProjectPermission(SCAN, project1); + TestResponse response = wsTester.newRequest() + .setParam("project", project1.getKey()) + .setHeader("Accept-Encoding", "gzip") + .execute(); + + assertThat(compressedInputStreamToString(response.getInputStream())).isEqualTo("test data1"); + assertThat(response.getHeader("Content-Encoding")).isEqualTo("gzip"); + } + + @Test + public void get_uncompressed_data_for_project() throws IOException { + ProjectDto project1 = dbTester.components().insertPrivateProjectDto(); + + dao.insert(dbTester.getSession(), project1.getUuid(), stringToCompressedInputStream("test data1")); + + userSession.logIn().addProjectPermission(SCAN, project1); + + TestResponse response = wsTester.newRequest() + .setParam("project", project1.getKey()) + .execute(); + + assertThat(response.getHeader("Content-Encoding")).isNull(); + assertThat(response.getInput()).isEqualTo("test data1"); + } + + @Test + public void get_data_for_branch() throws IOException { + ProjectDto project1 = dbTester.components().insertPrivateProjectDto(); + BranchDto branch = dbTester.components().insertProjectBranch(project1); + + dao.insert(dbTester.getSession(), project1.getUuid(), stringToCompressedInputStream("test data1")); + dao.insert(dbTester.getSession(), branch.getUuid(), stringToCompressedInputStream("test data2")); + + userSession.logIn().addProjectPermission(SCAN, project1); + TestResponse response = wsTester.newRequest() + .setParam("project", project1.getKey()) + .setParam("branch", branch.getKey()) + .setHeader("Accept-Encoding", "gzip") + .execute(); + + assertThat(compressedInputStreamToString(response.getInputStream())).isEqualTo("test data2"); + } + + @Test + public void return_not_found_if_project_not_found() { + TestRequest request = wsTester + .newRequest() + .setParam("project", "project1"); + assertThatThrownBy(request::execute).isInstanceOf(NotFoundException.class); + } + + @Test + public void return_not_found_if_cache_not_found() { + ProjectDto project1 = dbTester.components().insertPrivateProjectDto(); + + userSession.logIn().addProjectPermission(SCAN, project1); + TestRequest request = wsTester + .newRequest() + .setParam("project", "project1"); + assertThatThrownBy(request::execute).isInstanceOf(NotFoundException.class); + } + + @Test + public void fail_if_no_permissions() { + ProjectDto project = dbTester.components().insertPrivateProjectDto(); + userSession.logIn().addProjectPermission(UserRole.CODEVIEWER, project); + TestRequest request = wsTester + .newRequest() + .setParam("project", project.getKey()); + + assertThatThrownBy(request::execute).isInstanceOf(ForbiddenException.class); + } + + private static String compressedInputStreamToString(InputStream inputStream) throws IOException { + return IOUtils.toString(new GZIPInputStream(inputStream), UTF_8); + } + + private static InputStream stringToCompressedInputStream(String str) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream)) { + IOUtils.write(str.getBytes(UTF_8), gzipOutputStream); + } + return new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); + } +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/ScannerCacheWsModuleTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/ScannerCacheWsModuleTest.java new file mode 100644 index 00000000000..f17381e8876 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/ScannerCacheWsModuleTest.java @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.scannercache.ws; + +import org.junit.Test; +import org.sonar.core.platform.ListContainer; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ScannerCacheWsModuleTest { + @Test + public void verify_count_of_added_components() { + ListContainer container = new ListContainer(); + new ScannerCacheWsModule().configure(container); + assertThat(container.getAddedObjects()).hasSize(3); + } + +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/ScannerCacheWsTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/ScannerCacheWsTest.java new file mode 100644 index 00000000000..e48b4e5321a --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/ScannerCacheWsTest.java @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.scannercache.ws; + +import org.junit.Test; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ScannerCacheWsTest { + @Test + public void define_ws() { + ScannerCacheWsAction action = new FakeAction(); + ScannerCacheWs underTest = new ScannerCacheWs(action); + WebService.Context context = new WebService.Context(); + + underTest.define(context); + + WebService.Controller controller = context.controller("api/scanner_cache"); + assertThat(controller).isNotNull(); + assertThat(controller.since()).isEqualTo("9.4"); + assertThat(controller.description()).isNotEmpty(); + assertThat(controller.actions()).hasSize(1); + } + + private static class FakeAction implements ScannerCacheWsAction { + @Override + public void define(WebService.NewController newController) { + newController.createAction("fake").setHandler(this); + } + + @Override + public void handle(Request request, Response response) throws Exception { + + } + } +} diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index cded3afbfc0..1d678796df5 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -221,6 +221,8 @@ import org.sonar.server.rule.ws.RuleQueryFactory; import org.sonar.server.rule.ws.RuleWsSupport; import org.sonar.server.rule.ws.RulesWs; import org.sonar.server.rule.ws.TagsAction; +import org.sonar.server.scannercache.ScannerCache; +import org.sonar.server.scannercache.ws.ScannerCacheWsModule; import org.sonar.server.setting.ProjectConfigurationLoaderImpl; import org.sonar.server.setting.SettingsChangeNotifier; import org.sonar.server.setting.ws.SettingsWsModule; @@ -523,6 +525,10 @@ public class PlatformLevel4 extends PlatformLevel { CancelAllAction.class, PluginsWs.class, + // Scanner Cache + ScannerCache.class, + new ScannerCacheWsModule(), + // ALM integrations TimeoutConfigurationImpl.class, CredentialsEncoderHelper.class, -- 2.39.5