From 3294c8fab734520bc3e72d84363e8aebd6951020 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Tue, 29 Oct 2019 15:14:54 +0100 Subject: [PATCH] SONAR-12512 Migrate GitHub ALM settings to new tables --- .../db/migration/version/v81/DbVersion81.java | 4 +- .../version/v81/MigrateGithubAlmSettings.java | 335 ++++++++++++++++++ .../version/v81/DbVersion81Test.java | 2 +- .../v81/MigrateGithubAlmSettingsTest.java | 309 ++++++++++++++++ .../MigrateGithubAlmSettingsTest/schema.sql | 86 +++++ 5 files changed, 734 insertions(+), 2 deletions(-) create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/MigrateGithubAlmSettings.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/MigrateGithubAlmSettingsTest.java create mode 100644 server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/MigrateGithubAlmSettingsTest/schema.sql diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81.java index f946622d850..10a725d8a48 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81.java @@ -27,6 +27,8 @@ public class DbVersion81 implements DbVersion { public void addSteps(MigrationStepRegistry registry) { registry .add(3100, "Create ALM_SETTINGS table", CreateAlmSettingsTable.class) - .add(3101, "Create PROJECT_ALM_SETTINGS table", CreateProjectAlmSettingsTable.class); + .add(3101, "Create PROJECT_ALM_SETTINGS table", CreateProjectAlmSettingsTable.class) + .add(3102, "Migrate GitHub ALM settings from PROPERTIES to ALM_SETTINGS tables", MigrateGithubAlmSettings.class) + ; } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/MigrateGithubAlmSettings.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/MigrateGithubAlmSettings.java new file mode 100644 index 00000000000..b3e64bee15e --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/MigrateGithubAlmSettings.java @@ -0,0 +1,335 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.platform.db.migration.version.v81; + +import com.google.common.collect.ImmutableSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.MassUpdate; +import org.sonar.server.platform.db.migration.step.Select; +import org.sonar.server.platform.db.migration.step.SqlStatement; +import org.sonar.server.platform.db.migration.step.Upsert; + +public class MigrateGithubAlmSettings extends DataChange { + + private static final String PROVIDER = "sonar.pullrequest.provider"; + + // Global settings + private static final String GITHUB_ENDPOINT = "sonar.pullrequest.github.endpoint"; + private static final String GITHUB_APP_ID = "sonar.alm.github.app.id"; + private static final String GITHUB_APP_PRIVATE_KEY = "sonar.alm.github.app.privateKeyContent.secured"; + private static final String GITHUB_APP_NAME = "sonar.alm.github.app.name"; + // Project setting + private static final String GITHUB_REPOSITORY = "sonar.pullrequest.github.repository"; + + private static final Set MANDATORY_GLOBAL_KEYS = ImmutableSet.of(GITHUB_ENDPOINT, GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY); + + private static final String PROVIDER_VALUE = "GitHub"; + + private static final String ALM_SETTING_GITHUB_ID = "github"; + + private static final String INSERT_SQL = "insert into project_alm_settings (uuid, project_uuid, alm_setting_uuid, alm_repo, created_at, updated_at) values (?, ?, ?, ?, ?, ?)"; + private static final String DELETE_SQL = "delete from properties where prop_key = ? and resource_id = ?"; + + private final UuidFactory uuidFactory; + private final System2 system2; + + public MigrateGithubAlmSettings(Database db, UuidFactory uuidFactory, System2 system2) { + super(db); + this.uuidFactory = uuidFactory; + this.system2 = system2; + } + + @Override + protected void execute(Context context) throws SQLException { + Map globalPropertiesByKey = loadGlobalProperties(context); + + if (globalPropertiesByKey.keySet().containsAll(MANDATORY_GLOBAL_KEYS)) { + String gitHubAlmSettingUuid = loadOrInsertAlmSetting(context, globalPropertiesByKey); + Property globalProviderProperty = globalPropertiesByKey.getOrDefault(PROVIDER, null); + String globalProvider = globalProviderProperty != null ? globalProviderProperty.getValue() : null; + + insertProjectAlmSettings(context, globalProvider, gitHubAlmSettingUuid); + } + + context.prepareUpsert("delete from properties where prop_key in (?, ?, ?, ?, ?)") + .setString(1, GITHUB_ENDPOINT) + .setString(2, GITHUB_APP_ID) + .setString(3, GITHUB_APP_PRIVATE_KEY) + .setString(4, GITHUB_APP_NAME) + .setString(5, GITHUB_REPOSITORY) + .execute() + .commit(); + } + + private static Map loadGlobalProperties(Context context) throws SQLException { + return context + .prepareSelect("select prop_key, text_value from properties where prop_key in (?, ?, ?, ?, ?) " + + "and resource_id is null " + + "and text_value is not null ") + .setString(1, PROVIDER) + .setString(2, GITHUB_ENDPOINT) + .setString(3, GITHUB_APP_ID) + .setString(4, GITHUB_APP_PRIVATE_KEY) + .setString(5, GITHUB_APP_NAME) + .list(Property::new) + .stream() + .collect(Collectors.toMap(Property::getKey, Function.identity())); + } + + private String loadOrInsertAlmSetting(Context context, Map globalPropertiesByKey) throws SQLException { + String gitHubAlmSettingUuid = loadAlmSetting(context); + if (gitHubAlmSettingUuid != null) { + return gitHubAlmSettingUuid; + } + return insertAlmSetting(context, globalPropertiesByKey); + } + + @CheckForNull + private static String loadAlmSetting(Context context) throws SQLException { + List list = context.prepareSelect("select uuid from alm_settings where alm_id=?") + .setString(1, ALM_SETTING_GITHUB_ID) + .list(row -> row.getString(1)); + if (list.isEmpty()) { + return null; + } + return list.get(0); + } + + private String insertAlmSetting(Context context, Map globalPropertiesByKey) throws SQLException { + String gitHubAlmSettingUuid = uuidFactory.create(); + Property appName = globalPropertiesByKey.get(GITHUB_APP_NAME); + context.prepareUpsert("insert into alm_settings (uuid, alm_id, kee, url, app_id, private_key, updated_at, created_at) values (?, ?, ?, ?, ?, ?, ?, ?)") + .setString(1, gitHubAlmSettingUuid) + .setString(2, ALM_SETTING_GITHUB_ID) + .setString(3, appName == null ? PROVIDER_VALUE : appName.getValue()) + .setString(4, globalPropertiesByKey.get(GITHUB_ENDPOINT).getValue()) + .setString(5, globalPropertiesByKey.get(GITHUB_APP_ID).getValue()) + .setString(6, globalPropertiesByKey.get(GITHUB_APP_PRIVATE_KEY).getValue()) + .setLong(7, system2.now()) + .setLong(8, system2.now()) + .execute() + .commit(); + return gitHubAlmSettingUuid; + } + + private void insertProjectAlmSettings(Context context, @Nullable String globalProvider, String almSettingUuid) throws SQLException { + final Buffer buffer = new Buffer(globalProvider); + MassUpdate massUpdate = context.prepareMassUpdate(); + massUpdate.select("select prop_key, text_value, prj.uuid, prj.id from properties prop " + + "inner join projects prj on prj.id=prop.resource_id " + + "where prop.prop_key in (?, ?) " + + "order by prj.id asc") + .setString(1, PROVIDER) + .setString(2, GITHUB_REPOSITORY); + massUpdate.update(INSERT_SQL); + massUpdate.update(DELETE_SQL); + massUpdate.execute((row, update, updateIndex) -> { + boolean shouldExecuteUpdate = false; + String projectUuid = row.getString(3); + Long projectId = row.getLong(4); + // Set last projectUuid the first time + if (buffer.getLastProjectUuid() == null) { + buffer.setLastProject(projectUuid, projectId); + } + // When current projectUuid is different from last processed projectUuid, feed the prepared statement + if (!projectUuid.equals(buffer.getLastProjectUuid())) { + if (updateIndex == 0) { + // Insert new row in PROJECT_ALM_SETTINGS + shouldExecuteUpdate = updateStatementIfNeeded(buffer.getLastProjectUuid(), buffer, update, almSettingUuid); + } else { + // Delete old property + shouldExecuteUpdate = deleteProperty(buffer.getLastProjectId(), buffer, update); + buffer.clear(); + // Update last projectUuid in buffer only when it has changed + buffer.setLastProject(projectUuid, projectId); + } + } + // Update remaining buffer values only once, and only after delete + if (updateIndex == 1) { + String propertyKey = row.getString(1); + String propertyValue = row.getString(2); + if (propertyKey.equals(PROVIDER)) { + buffer.setProvider(propertyValue); + } else if (propertyKey.equals(GITHUB_REPOSITORY)) { + buffer.setRepository(propertyValue); + } + buffer.setCurrentProject(projectUuid, projectId); + } + return shouldExecuteUpdate; + }); + String projectUuid = buffer.getCurrentProjectUuid(); + if (projectUuid == null) { + return; + } + // Process last entry + Upsert upsert = context.prepareUpsert(INSERT_SQL); + if (updateStatementIfNeeded(projectUuid, buffer, upsert, almSettingUuid)) { + upsert.execute().commit(); + } + if (buffer.shouldDelete()) { + context.prepareUpsert(DELETE_SQL) + .setString(1, GITHUB_REPOSITORY) + .setLong(2, buffer.getCurrentProjectId()) + .execute() + .commit(); + } + } + + private static boolean deleteProperty(long effectiveProjectId, Buffer buffer, SqlStatement update) throws SQLException { + if (buffer.shouldDelete()) { + update.setString(1, GITHUB_REPOSITORY); + update.setLong(2, effectiveProjectId); + return true; + } + return false; + } + + private boolean updateStatementIfNeeded(String effectiveProjectUuid, Buffer buffer, SqlStatement update, String almSettingUuid) + throws SQLException { + if (!buffer.shouldUpdate()) { + return false; + } + update.setString(1, uuidFactory.create()); + update.setString(2, effectiveProjectUuid); + update.setString(3, almSettingUuid); + update.setString(4, buffer.getRepository()); + update.setLong(5, system2.now()); + update.setLong(6, system2.now()); + return true; + } + + private static class Property { + private final String key; + private final String value; + + Property(Select.Row row) throws SQLException { + this.key = row.getString(1); + this.value = row.getString(2); + } + + String getKey() { + return key; + } + + String getValue() { + return value; + } + } + + private static class Buffer { + private final String globalProvider; + private String lastProjectUuid; + private String currentProjectUuid; + private Long lastProjectId; + private Long currentProjectId; + private String provider; + private String repository; + + public Buffer(@Nullable String globalProvider) { + this.globalProvider = globalProvider; + } + + Buffer setLastProject(@Nullable String projectUuid, @Nullable Long projectId) { + this.lastProjectUuid = projectUuid; + this.lastProjectId = projectId; + return this; + } + + @CheckForNull + String getLastProjectUuid() { + return lastProjectUuid; + } + + @CheckForNull + Long getLastProjectId() { + return lastProjectId; + } + + Buffer setCurrentProject(@Nullable String projectUuid, @Nullable Long projectId) { + this.currentProjectUuid = projectUuid; + this.currentProjectId = projectId; + return this; + } + + @CheckForNull + String getCurrentProjectUuid() { + return currentProjectUuid; + } + + @CheckForNull + Long getCurrentProjectId() { + return currentProjectId; + } + + Buffer setProvider(@Nullable String provider) { + this.provider = provider; + return this; + } + + @CheckForNull + String getRepository() { + return repository; + } + + Buffer setRepository(@Nullable String repository) { + this.repository = repository; + return this; + } + + boolean shouldUpdate() { + if (repository == null) { + return false; + } + if (Objects.equals(provider, PROVIDER_VALUE)) { + return true; + } + return provider == null && Objects.equals(globalProvider, PROVIDER_VALUE); + } + + boolean shouldDelete() { + if (provider != null) { + return provider.equals(PROVIDER_VALUE); + } + return Objects.equals(globalProvider, PROVIDER_VALUE); + } + + void clear() { + this.lastProjectUuid = null; + this.currentProjectUuid = null; + this.lastProjectId = null; + this.currentProjectId = null; + this.provider = null; + this.repository = null; + } + } + +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81Test.java index 8e6e0fa4f1e..7c9ac08216f 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81Test.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81Test.java @@ -37,7 +37,7 @@ public class DbVersion81Test { @Test public void verify_migration_count() { - verifyMigrationCount(underTest, 2); + verifyMigrationCount(underTest, 3); } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/MigrateGithubAlmSettingsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/MigrateGithubAlmSettingsTest.java new file mode 100644 index 00000000000..6bcc05a6271 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/MigrateGithubAlmSettingsTest.java @@ -0,0 +1,309 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.platform.db.migration.version.v81; + +import java.sql.SQLException; +import javax.annotation.Nullable; +import org.assertj.core.groups.Tuple; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.impl.utils.TestSystem2; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.core.util.UuidFactoryFast; +import org.sonar.db.CoreDbTester; +import org.sonar.server.platform.db.migration.step.DataChange; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static org.apache.commons.lang.math.RandomUtils.nextInt; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +public class MigrateGithubAlmSettingsTest { + + private final static long PAST = 10_000_000_000L; + private static final long NOW = 50_000_000_000L; + private System2 system2 = new TestSystem2().setNow(NOW); + + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(MigrateGithubAlmSettingsTest.class, "schema.sql"); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private UuidFactory uuidFactory = UuidFactoryFast.getInstance(); + + private DataChange underTest = new MigrateGithubAlmSettings(db.database(), uuidFactory, system2); + + @Test + public void migrate_settings_when_global_provider_is_set_to_github() throws SQLException { + insertProperty("sonar.pullrequest.provider", "GitHub", null); + insertProperty("sonar.pullrequest.github.endpoint", "https://enterprise.github.com", null); + insertProperty("sonar.alm.github.app.id", "12345", null); + insertProperty("sonar.alm.github.app.privateKeyContent.secured", "", null); + long projectId1 = insertProject("PROJECT_1"); + insertProperty("sonar.pullrequest.github.repository", "Repository1", projectId1); + long projectId2 = insertProject("PROJECT_2"); + insertProperty("sonar.pullrequest.github.repository", "Repository2", projectId2); + + underTest.execute(); + + assertAlmSettings(tuple("github", "GitHub", "https://enterprise.github.com", "12345", "", NOW, NOW)); + String gitHubAlmSettingUuid = selectAlmSettingUuid("GitHub"); + assertProjectAlmSettings( + tuple("PROJECT_1", gitHubAlmSettingUuid, "Repository1", NOW, NOW), + tuple("PROJECT_2", gitHubAlmSettingUuid, "Repository2", NOW, NOW)); + assertProperties(tuple("sonar.pullrequest.provider", "GitHub", null)); + } + + @Test + public void migrate_settings_when_project_provider_is_set_to_github_and_global_provider_is_set_to_something_else() throws SQLException { + insertProperty("sonar.pullrequest.provider", "Azure DevOps", null); + insertProperty("sonar.pullrequest.github.endpoint", "https://enterprise.github.com", null); + insertProperty("sonar.alm.github.app.id", "12345", null); + insertProperty("sonar.alm.github.app.privateKeyContent.secured", "", null); + long projectId1 = insertProject("PROJECT_1"); + insertProperty("sonar.pullrequest.provider", "GitHub", projectId1); + insertProperty("sonar.pullrequest.github.repository", "Repository1", projectId1); + long projectId2 = insertProject("PROJECT_2"); + insertProperty("sonar.pullrequest.provider", "GitHub", projectId2); + insertProperty("sonar.pullrequest.github.repository", "Repository2", projectId2); + + underTest.execute(); + + assertAlmSettings(tuple("github", "GitHub", "https://enterprise.github.com", "12345", "", NOW, NOW)); + String gitHubAlmSettingUuid = selectAlmSettingUuid("GitHub"); + assertProjectAlmSettings( + tuple("PROJECT_1", gitHubAlmSettingUuid, "Repository1", NOW, NOW), + tuple("PROJECT_2", gitHubAlmSettingUuid, "Repository2", NOW, NOW)); + assertProperties( + tuple("sonar.pullrequest.provider", "Azure DevOps", null), + tuple("sonar.pullrequest.provider", "GitHub", projectId1), + tuple("sonar.pullrequest.provider", "GitHub", projectId2)); + } + + @Test + public void delete_github_settings_when_project_provider_is_not_set_to_github() throws SQLException { + insertProperty("sonar.pullrequest.provider", "GitHub", null); + insertProperty("sonar.pullrequest.github.endpoint", "https://enterprise.github.com", null); + insertProperty("sonar.alm.github.app.id", "12345", null); + insertProperty("sonar.alm.github.app.privateKeyContent.secured", "", null); + long projectId1 = insertProject("PROJECT_1"); + // Project provider is set to something else + insertProperty("sonar.pullrequest.provider", "Azure", projectId1); + insertProperty("sonar.pullrequest.github.repository", "Repository1", projectId1); + + underTest.execute(); + + assertNoProjectAlmSettings(); + assertProperties( + tuple("sonar.pullrequest.provider", "GitHub", null), + tuple("sonar.pullrequest.provider", "Azure", projectId1)); + } + + @Test + public void use_existing_alm_setting() throws SQLException { + db.executeInsert("alm_settings", + "uuid", "ABCD", + "alm_id", "github", + "kee", "GitHub", + "url", "https://enterprise.github.com", + "app_id", "12345", + "private_key", "", + "created_at", PAST, + "updated_at", PAST); + insertProperty("sonar.pullrequest.provider", "GitHub", null); + insertProperty("sonar.pullrequest.github.endpoint", "https://enterprise.github.com", null); + insertProperty("sonar.alm.github.app.id", "12345", null); + insertProperty("sonar.alm.github.app.privateKeyContent.secured", "", null); + long projectId1 = insertProject("PROJECT_1"); + insertProperty("sonar.pullrequest.github.repository", "Repository1", projectId1); + + underTest.execute(); + + assertProjectAlmSettings(tuple("PROJECT_1", "ABCD", "Repository1", NOW, NOW)); + assertProperties(tuple("sonar.pullrequest.provider", "GitHub", null)); + } + + @Test + public void ignore_none_github_project_settings() throws SQLException { + insertProperty("sonar.pullrequest.provider", "GitHub", null); + insertProperty("sonar.pullrequest.github.endpoint", "https://enterprise.github.com", null); + insertProperty("sonar.alm.github.app.id", "12345", null); + insertProperty("sonar.alm.github.app.privateKeyContent.secured", "", null); + long projectId1 = insertProject("PROJECT_1"); + insertProperty("sonar.pullrequest.provider", "Bitbucket", projectId1); + long projectId2 = insertProject("PROJECT_2"); + insertProperty("sonar.pullrequest.provider", "Bitbucket", projectId2); + + underTest.execute(); + + assertAlmSettings(tuple("github", "GitHub", "https://enterprise.github.com", "12345", "", NOW, NOW)); + assertNoProjectAlmSettings(); + assertProperties( + tuple("sonar.pullrequest.provider", "GitHub", null), + tuple("sonar.pullrequest.provider", "Bitbucket", projectId1), + tuple("sonar.pullrequest.provider", "Bitbucket", projectId2)); + } + + @Test + public void do_not_create_alm_settings_when_missing_some_global_properties() throws SQLException { + insertProperty("sonar.pullrequest.provider", "GitHub", null); + // No endpoint + insertProperty("sonar.alm.github.app.id", "12345", null); + insertProperty("sonar.alm.github.app.privateKeyContent.secured", "", null); + long projectId1 = insertProject("PROJECT_1"); + insertProperty("sonar.pullrequest.github.repository", "Repository1", projectId1); + + underTest.execute(); + + assertNoAlmSettings(); + assertNoProjectAlmSettings(); + assertProperties(tuple("sonar.pullrequest.provider", "GitHub", null)); + } + + @Test + public void do_not_create_project_alm_settings_when_missing_some_project_properties() throws SQLException { + insertProperty("sonar.pullrequest.provider", "GitHub", null); + insertProperty("sonar.pullrequest.github.endpoint", "https://enterprise.github.com", null); + insertProperty("sonar.alm.github.app.id", "12345", null); + insertProperty("sonar.alm.github.app.privateKeyContent.secured", "", null); + long projectId1 = insertProject("PROJECT_1"); + // No repository + insertProperty("sonar.pullrequest.provider", "GitHub", projectId1); + + underTest.execute(); + + assertAlmSettings(tuple("github", "GitHub", "https://enterprise.github.com", "12345", "", NOW, NOW)); + assertNoProjectAlmSettings(); + assertProperties( + tuple("sonar.pullrequest.provider", "GitHub", null), + tuple("sonar.pullrequest.provider", "GitHub", projectId1)); + } + + @Test + public void do_nothing_when_no_alm_properties() throws SQLException { + insertProperty("sonar.other.property", "Something", null); + + underTest.execute(); + + assertNoAlmSettings(); + assertNoProjectAlmSettings(); + assertProperties(tuple("sonar.other.property", "Something", null)); + } + + @Test + public void migration_is_reentrant() throws SQLException { + insertProperty("sonar.pullrequest.provider", "GitHub", null); + insertProperty("sonar.pullrequest.github.endpoint", "https://enterprise.github.com", null); + insertProperty("sonar.alm.github.app.id", "12345", null); + insertProperty("sonar.alm.github.app.privateKeyContent.secured", "", null); + long projectId1 = insertProject("PROJECT_1"); + insertProperty("sonar.pullrequest.provider", "GitHub", projectId1); + insertProperty("sonar.pullrequest.github.repository", "Repository1", projectId1); + underTest.execute(); + + // Global settings have been removed, let's re-create them + insertProperty("sonar.pullrequest.github.endpoint", "https://enterprise.github.com", null); + insertProperty("sonar.alm.github.app.id", "12345", null); + insertProperty("sonar.alm.github.app.privateKeyContent.secured", "", null); + long projectId2 = insertProject("PROJECT_2"); + insertProperty("sonar.pullrequest.github.repository", "Repository2", projectId2); + underTest.execute(); + + assertAlmSettings(tuple("github", "GitHub", "https://enterprise.github.com", "12345", "", NOW, NOW)); + assertProperties( + tuple("sonar.pullrequest.provider", "GitHub", null), + tuple("sonar.pullrequest.provider", "GitHub", projectId1)); + } + + private void assertAlmSettings(Tuple... expectedTuples) { + assertThat(db.select("SELECT alm_id, kee, url, app_id, private_key, created_at, updated_at FROM alm_settings") + .stream() + .map(map -> new Tuple(map.get("ALM_ID"), map.get("KEE"), map.get("URL"), map.get("APP_ID"), map.get("PRIVATE_KEY"), map.get("CREATED_AT"), + map.get("UPDATED_AT"))) + .collect(toList())) + .containsExactlyInAnyOrder(expectedTuples); + } + + private void assertNoAlmSettings() { + assertAlmSettings(); + } + + private void assertProjectAlmSettings(Tuple... expectedTuples) { + assertThat(db.select("SELECT project_uuid, alm_setting_uuid, alm_repo, created_at, updated_at FROM project_alm_settings") + .stream() + .map(map -> new Tuple(map.get("PROJECT_UUID"), map.get("ALM_SETTING_UUID"), map.get("ALM_REPO"), map.get("CREATED_AT"), map.get("UPDATED_AT"))) + .collect(toList())) + .containsExactlyInAnyOrder(expectedTuples); + } + + private void assertNoProjectAlmSettings() { + assertProjectAlmSettings(); + } + + private void assertProperties(Tuple... expectedTuples) { + assertThat(db.select("SELECT prop_key, text_value, resource_id FROM properties") + .stream() + .map(map -> new Tuple(map.get("PROP_KEY"), map.get("TEXT_VALUE"), map.get("RESOURCE_ID"))) + .collect(toSet())) + .containsExactlyInAnyOrder(expectedTuples); + } + + private void assertNoProperties() { + assertProperties(); + } + + private String selectAlmSettingUuid(String almSettingKey) { + return (String) db.selectFirst("select uuid from alm_settings where kee='" + almSettingKey + "'").get("UUID"); + } + + private void insertProperty(String key, String value, @Nullable Long projectId) { + db.executeInsert( + "PROPERTIES", + "PROP_KEY", key, + "RESOURCE_ID", projectId, + "USER_ID", null, + "IS_EMPTY", false, + "TEXT_VALUE", value, + "CLOB_VALUE", null, + "CREATED_AT", System2.INSTANCE.now()); + } + + private long insertProject(String uuid) { + int id = nextInt(); + db.executeInsert("PROJECTS", + "ID", id, + "ORGANIZATION_UUID", "default", + "KEE", uuid + "-key", + "UUID", uuid, + "PROJECT_UUID", uuid, + "MAIN_BRANCH_PROJECT_UUID", uuid, + "UUID_PATH", ".", + "ROOT_UUID", uuid, + "PRIVATE", Boolean.toString(false), + "SCOPE", "PRJ", + "QUALIFIER", "PRJ"); + return id; + } + +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/MigrateGithubAlmSettingsTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/MigrateGithubAlmSettingsTest/schema.sql new file mode 100644 index 00000000000..7fe0d588e81 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/MigrateGithubAlmSettingsTest/schema.sql @@ -0,0 +1,86 @@ +CREATE TABLE ALM_SETTINGS( + UUID VARCHAR(40) NOT NULL, + ALM_ID VARCHAR(40) NOT NULL, + KEE VARCHAR(200) NOT NULL, + URL VARCHAR(2000), + APP_ID VARCHAR(80), + PRIVATE_KEY VARCHAR(2000), + PAT VARCHAR(2000), + UPDATED_AT BIGINT NOT NULL, + CREATED_AT BIGINT NOT NULL +); +ALTER TABLE ALM_SETTINGS ADD CONSTRAINT PK_ALM_SETTINGS PRIMARY KEY(UUID); +CREATE UNIQUE INDEX UNIQ_ALM_SETTINGS ON ALM_SETTINGS(KEE); + +CREATE TABLE PROJECT_ALM_SETTINGS( + UUID VARCHAR(40) NOT NULL, + ALM_SETTING_UUID VARCHAR(40) NOT NULL, + PROJECT_UUID VARCHAR(50) NOT NULL, + ALM_REPO VARCHAR(256), + ALM_SLUG VARCHAR(256), + UPDATED_AT BIGINT NOT NULL, + CREATED_AT BIGINT NOT NULL +); +ALTER TABLE PROJECT_ALM_SETTINGS ADD CONSTRAINT PK_PROJECT_ALM_SETTINGS PRIMARY KEY(UUID); +CREATE UNIQUE INDEX UNIQ_PROJECT_ALM_SETTINGS ON PROJECT_ALM_SETTINGS(PROJECT_UUID); +CREATE INDEX PROJECT_ALM_SETTINGS_ALM ON PROJECT_ALM_SETTINGS(ALM_SETTING_UUID); + +CREATE TABLE "PROPERTIES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "PROP_KEY" VARCHAR(512) NOT NULL, + "RESOURCE_ID" INTEGER, + "USER_ID" INTEGER, + "IS_EMPTY" BOOLEAN NOT NULL, + "TEXT_VALUE" VARCHAR(4000), + "CLOB_VALUE" CLOB, + "CREATED_AT" BIGINT +); +CREATE INDEX "PROPERTIES_KEY" ON "PROPERTIES" ("PROP_KEY"); + +CREATE TABLE PROJECTS( + ID INTEGER NOT NULL AUTO_INCREMENT (1,1), + UUID VARCHAR(50) NOT NULL, + ORGANIZATION_UUID VARCHAR(40) NOT NULL, + KEE VARCHAR(400), + DEPRECATED_KEE VARCHAR(400), + NAME VARCHAR(2000), + LONG_NAME VARCHAR(2000), + DESCRIPTION VARCHAR(2000), + ENABLED BOOLEAN DEFAULT TRUE NOT NULL, + SCOPE VARCHAR(3), + QUALIFIER VARCHAR(10), + PRIVATE BOOLEAN NOT NULL, + ROOT_UUID VARCHAR(50) NOT NULL, + LANGUAGE VARCHAR(20), + COPY_COMPONENT_UUID VARCHAR(50), + DEVELOPER_UUID VARCHAR(50), + PATH VARCHAR(2000), + UUID_PATH VARCHAR(1500) NOT NULL, + PROJECT_UUID VARCHAR(50) NOT NULL, + MODULE_UUID VARCHAR(50), + MODULE_UUID_PATH VARCHAR(1500), + AUTHORIZATION_UPDATED_AT BIGINT, + TAGS VARCHAR(500), + MAIN_BRANCH_PROJECT_UUID VARCHAR(50), + B_CHANGED BOOLEAN, + B_NAME VARCHAR(500), + B_LONG_NAME VARCHAR(500), + B_DESCRIPTION VARCHAR(2000), + B_ENABLED BOOLEAN, + B_QUALIFIER VARCHAR(10), + B_LANGUAGE VARCHAR(20), + B_COPY_COMPONENT_UUID VARCHAR(50), + B_PATH VARCHAR(2000), + B_UUID_PATH VARCHAR(1500), + B_MODULE_UUID VARCHAR(50), + B_MODULE_UUID_PATH VARCHAR(1500), + CREATED_AT TIMESTAMP +); +ALTER TABLE PROJECTS ADD CONSTRAINT PK_PROJECTS PRIMARY KEY(ID); +CREATE INDEX PROJECTS_ORGANIZATION ON PROJECTS(ORGANIZATION_UUID); +CREATE UNIQUE INDEX PROJECTS_KEE ON PROJECTS(KEE); +CREATE INDEX PROJECTS_MODULE_UUID ON PROJECTS(MODULE_UUID); +CREATE INDEX PROJECTS_PROJECT_UUID ON PROJECTS(PROJECT_UUID); +CREATE INDEX PROJECTS_QUALIFIER ON PROJECTS(QUALIFIER); +CREATE INDEX PROJECTS_ROOT_UUID ON PROJECTS(ROOT_UUID); +CREATE INDEX PROJECTS_UUID ON PROJECTS(UUID); -- 2.39.5