diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2019-10-29 15:17:11 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2019-11-06 10:04:31 +0100 |
commit | ec7dd4d0e7a1e26d1d0f9536aaa9255cf4cc6b3e (patch) | |
tree | 4f7600a393942c6124c2992c5004561544813cf3 /server/sonar-db-migration | |
parent | 60f700191bcadfe3d638e73b9e2ebfeeaa562c96 (diff) | |
download | sonarqube-ec7dd4d0e7a1e26d1d0f9536aaa9255cf4cc6b3e.tar.gz sonarqube-ec7dd4d0e7a1e26d1d0f9536aaa9255cf4cc6b3e.zip |
SONAR-12514 Migrate Azure ALM settings to new tables
Diffstat (limited to 'server/sonar-db-migration')
6 files changed, 831 insertions, 2 deletions
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 83ca3a50c78..37c9cda8c40 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 @@ -29,6 +29,9 @@ public class DbVersion81 implements DbVersion { .add(3100, "Create ALM_SETTINGS table", CreateAlmSettingsTable.class) .add(3101, "Create PROJECT_ALM_SETTINGS table", CreateProjectAlmSettingsTable.class) .add(3102, "Migrate GitHub ALM settings from PROPERTIES to ALM_SETTINGS tables", MigrateGithubAlmSettings.class) - .add(3103, "Migrate Bitbucket ALM settings from PROPERTIES to ALM_SETTINGS tables", MigrateBitbucketAlmSettings.class); + .add(3103, "Migrate Bitbucket ALM settings from PROPERTIES to ALM_SETTINGS tables", MigrateBitbucketAlmSettings.class) + .add(3104, "Migrate Azure ALM settings from PROPERTIES to ALM_SETTINGS tables", MigrateAzureAlmSettings.class) + .add(3105, "Delete 'sonar.pullrequest.provider' property", DeleteSonarPullRequestProviderProperty.class) + ; } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/MigrateAzureAlmSettings.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/MigrateAzureAlmSettings.java new file mode 100644 index 00000000000..c749365e61c --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/MigrateAzureAlmSettings.java @@ -0,0 +1,353 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.TreeSet; +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 MigrateAzureAlmSettings extends DataChange { + + private static final String PROVIDER = "sonar.pullrequest.provider"; + private static final String AZURE_TOKEN = "sonar.pullrequest.vsts.token.secured"; + private static final String PROVIDER_VALUE = "Azure DevOps"; + private static final String ALM_SETTING_ID = "azure_devops"; + + private static final String INSERT_SQL = "insert into project_alm_settings (uuid, project_uuid, alm_setting_uuid, 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 MigrateAzureAlmSettings(Database db, UuidFactory uuidFactory, System2 system2) { + super(db); + this.uuidFactory = uuidFactory; + this.system2 = system2; + } + + @Override + protected void execute(Context context) throws SQLException { + List<String> tokens = loadTokens(context); + if (tokens.isEmpty()) { + return; + } + Map<String, Property> globalPropertiesByKey = loadGlobalProperties(context); + Map<String, String> almSettingUuidByTokens = loadOrInsertAlmSettings(context, tokens); + + String globalProvider = getGlobalProperty(globalPropertiesByKey, PROVIDER); + String globalToken = getGlobalProperty(globalPropertiesByKey, AZURE_TOKEN); + + insertProjectAlmSettings(context, globalProvider, globalToken, almSettingUuidByTokens); + context.prepareUpsert("delete from properties where prop_key = ?") + .setString(1, AZURE_TOKEN) + .execute() + .commit(); + } + + @CheckForNull + private static String getGlobalProperty(Map<String, Property> globalPropertiesByKey, String propertyKey) { + Property globalProperty = globalPropertiesByKey.getOrDefault(propertyKey, null); + return globalProperty != null ? globalProperty.getValue() : null; + } + + private static Map<String, Property> 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, AZURE_TOKEN) + .list(Property::new) + .stream() + .collect(Collectors.toMap(Property::getKey, Function.identity())); + } + + private static List<String> loadTokens(Context context) throws SQLException { + return new ArrayList<>(context + .prepareSelect("select distinct text_value from properties where prop_key = ?") + .setString(1, AZURE_TOKEN) + .list(row -> row.getString(1))); + } + + private Map<String, String> loadOrInsertAlmSettings(Context context, List<String> tokens) throws SQLException { + Map<String, String> almSettingUuidByTokens = loadExistingAlmSettingUuidByToken(context); + TreeSet<String> tokensWithNoAlmSetting = new TreeSet<>(tokens); + tokensWithNoAlmSetting.removeAll(almSettingUuidByTokens.keySet()); + if (tokensWithNoAlmSetting.isEmpty()) { + return almSettingUuidByTokens; + } + int index = almSettingUuidByTokens.isEmpty() ? 0 : (almSettingUuidByTokens.size() + 1); + if (tokensWithNoAlmSetting.size() == 1) { + String token = tokensWithNoAlmSetting.first(); + String almSettingUuid = insertAlmSetting(context, token, index); + almSettingUuidByTokens.put(token, almSettingUuid); + return almSettingUuidByTokens; + } + for (String token : tokensWithNoAlmSetting) { + String almSettingUuid = insertAlmSetting(context, token, index); + almSettingUuidByTokens.put(token, almSettingUuid); + index++; + } + return almSettingUuidByTokens; + } + + private static Map<String, String> loadExistingAlmSettingUuidByToken(Context context) throws SQLException { + Select select = context.prepareSelect("select uuid, pat from alm_settings where alm_id=?") + .setString(1, ALM_SETTING_ID); + return select + .list(row -> new AlmSetting(row.getString(1), row.getString(2))) + .stream() + .collect(Collectors.toMap(AlmSetting::getPersonalAccessToken, AlmSetting::getUuid)); + } + + private String insertAlmSetting(Context context, String token, int index) throws SQLException { + String almSettingUuid = uuidFactory.create(); + context.prepareUpsert("insert into alm_settings (uuid, alm_id, kee, pat, updated_at, created_at) values (?, ?, ?, ?, ?, ?)") + .setString(1, almSettingUuid) + .setString(2, ALM_SETTING_ID) + .setString(3, index == 0 ? PROVIDER_VALUE : (PROVIDER_VALUE + " " + index)) + .setString(4, token) + .setLong(5, system2.now()) + .setLong(6, system2.now()) + .execute() + .commit(); + return almSettingUuid; + } + + private void insertProjectAlmSettings(Context context, @Nullable String globalProvider, @Nullable String globalToken, + Map<String, String> almSettingUuidByTokens) 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, AZURE_TOKEN); + 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, buffer.getLastProjectUuid(), update, globalToken, almSettingUuidByTokens); + } 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(AZURE_TOKEN)) { + buffer.setToken(propertyValue); + } + buffer.setCurrentProject(projectUuid, projectId); + } + return shouldExecuteUpdate; + }); + String projectUuid = buffer.getCurrentProjectUuid(); + if (projectUuid == null) { + return; + } + // Process last entry + Upsert upsert = context.prepareUpsert(INSERT_SQL); + boolean shouldExecuteInsert = updateStatementIfNeeded(buffer, projectUuid, upsert, globalToken, almSettingUuidByTokens); + if (shouldExecuteInsert) { + upsert.execute().commit(); + } + if (buffer.shouldUpdate()) { + context.prepareUpsert(DELETE_SQL) + .setString(1, AZURE_TOKEN) + .setLong(2, buffer.getCurrentProjectId()) + .execute() + .commit(); + } + } + + private static boolean deleteProperty(long effectiveProjectId, Buffer buffer, SqlStatement update) throws SQLException { + if (buffer.shouldUpdate()) { + update.setString(1, AZURE_TOKEN); + update.setLong(2, effectiveProjectId); + return true; + } + return false; + } + + private boolean updateStatementIfNeeded(Buffer buffer, String projectUuid, SqlStatement update, @Nullable String globalToken, + Map<String, String> almSettingUuidByTokens) + throws SQLException { + String token = buffer.getToken(); + String almSettingUuid = token == null ? almSettingUuidByTokens.get(globalToken) : almSettingUuidByTokens.get(token); + if (!buffer.shouldUpdate() || almSettingUuid == null) { + return false; + } + update.setString(1, uuidFactory.create()); + update.setString(2, projectUuid); + update.setString(3, almSettingUuid); + update.setLong(4, system2.now()); + update.setLong(5, system2.now()); + return true; + } + + private static class AlmSetting { + private final String uuid; + private final String personalAccessToken; + + AlmSetting(String uuid, String personalAccessToken) { + this.uuid = uuid; + this.personalAccessToken = personalAccessToken; + } + + String getUuid() { + return uuid; + } + + String getPersonalAccessToken() { + return personalAccessToken; + } + } + + 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 token; + + private 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 getToken() { + return token; + } + + Buffer setToken(@Nullable String token) { + this.token = token; + return this; + } + + boolean shouldUpdate() { + 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.token = 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 563fca41f54..772a5c35882 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, 4); + verifyMigrationCount(underTest, 6); } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/MigrateAzureAlmSettingsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/MigrateAzureAlmSettingsTest.java new file mode 100644 index 00000000000..9022126d81c --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/MigrateAzureAlmSettingsTest.java @@ -0,0 +1,376 @@ +/* + * 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 MigrateAzureAlmSettingsTest { + + 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(MigrateAzureAlmSettingsTest.class, "schema.sql"); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private UuidFactory uuidFactory = UuidFactoryFast.getInstance(); + + private DataChange underTest = new MigrateAzureAlmSettings(db.database(), uuidFactory, system2); + + @Test + public void migrate_settings_when_project_provider_is_set_to_azure_and_global_provider_is_set_to_something_else() throws SQLException { + insertProperty("sonar.pullrequest.provider", "Bitbucket Server", null); + long projectId1 = insertProject("PROJECT_1"); + insertProperty("sonar.pullrequest.provider", "Azure DevOps", projectId1); + insertProperty("sonar.pullrequest.vsts.token.secured", "12345", projectId1); + long projectId2 = insertProject("PROJECT_2"); + insertProperty("sonar.pullrequest.provider", "Azure DevOps", projectId2); + insertProperty("sonar.pullrequest.vsts.token.secured", "12345", projectId2); + + underTest.execute(); + + assertAlmSettings(tuple("azure_devops", "Azure DevOps", "12345", NOW, NOW)); + String almSettingUuid = selectAlmSettingUuid("Azure DevOps"); + assertProjectAlmSettings( + tuple("PROJECT_1", almSettingUuid, NOW, NOW), + tuple("PROJECT_2", almSettingUuid, NOW, NOW)); + assertProperties( + tuple("sonar.pullrequest.provider", "Bitbucket Server", null), + tuple("sonar.pullrequest.provider", "Azure DevOps", projectId1), + tuple("sonar.pullrequest.provider", "Azure DevOps", projectId2)); + } + + @Test + public void migrate_settings_when_token_is_global_and_provider_is_per_project() throws SQLException { + insertProperty("sonar.pullrequest.vsts.token.secured", "12345", null); + long projectId1 = insertProject("PROJECT_1"); + insertProperty("sonar.pullrequest.provider", "Azure DevOps", projectId1); + long projectId2 = insertProject("PROJECT_2"); + insertProperty("sonar.pullrequest.provider", "Azure DevOps", projectId2); + + underTest.execute(); + + assertAlmSettings(tuple("azure_devops", "Azure DevOps", "12345", NOW, NOW)); + String almSettingUuid = selectAlmSettingUuid("Azure DevOps"); + assertProjectAlmSettings( + tuple("PROJECT_1", almSettingUuid, NOW, NOW), + tuple("PROJECT_2", almSettingUuid, NOW, NOW)); + assertProperties( + tuple("sonar.pullrequest.provider", "Azure DevOps", projectId1), + tuple("sonar.pullrequest.provider", "Azure DevOps", projectId2)); + } + + @Test + public void migrate_settings_when_provider_is_global() throws SQLException { + insertProperty("sonar.pullrequest.provider", "Azure DevOps", null); + long projectId1 = insertProject("PROJECT_1"); + insertProperty("sonar.pullrequest.vsts.token.secured", "12345", projectId1); + long projectId2 = insertProject("PROJECT_2"); + insertProperty("sonar.pullrequest.provider", "Azure DevOps", projectId2); + insertProperty("sonar.pullrequest.vsts.token.secured", "12345", projectId2); + + underTest.execute(); + + assertAlmSettings(tuple("azure_devops", "Azure DevOps", "12345", NOW, NOW)); + String almSettingUuid = selectAlmSettingUuid("Azure DevOps"); + assertProjectAlmSettings( + tuple("PROJECT_1", almSettingUuid, NOW, NOW), + tuple("PROJECT_2", almSettingUuid, NOW, NOW)); + assertProperties( + tuple("sonar.pullrequest.provider", "Azure DevOps", null), + tuple("sonar.pullrequest.provider", "Azure DevOps", projectId2)); + } + + @Test + public void create_one_alm_setting_for_each_token_found() throws SQLException { + insertProperty("sonar.pullrequest.provider", "Azure DevOps", null); + insertProperty("sonar.pullrequest.vsts.token.secured", "100", null); + long projectId1 = insertProject("PROJECT_1"); + // No token defined on project 1 -> use global token + insertProperty("sonar.pullrequest.provider", "Azure DevOps", projectId1); + long projectId2 = insertProject("PROJECT_2"); + insertProperty("sonar.pullrequest.vsts.token.secured", "200", projectId2); + long projectId3 = insertProject("PROJECT_3"); + insertProperty("sonar.pullrequest.provider", "Azure DevOps", projectId3); + insertProperty("sonar.pullrequest.vsts.token.secured", "300", projectId3); + + underTest.execute(); + + assertAlmSettings( + tuple("azure_devops", "Azure DevOps", "100", NOW, NOW), + tuple("azure_devops", "Azure DevOps 1", "200", NOW, NOW), + tuple("azure_devops", "Azure DevOps 2", "300", NOW, NOW)); + assertProjectAlmSettings( + tuple("PROJECT_1", selectAlmSettingUuid("Azure DevOps"), NOW, NOW), + tuple("PROJECT_2", selectAlmSettingUuid("Azure DevOps 1"), NOW, NOW), + tuple("PROJECT_3", selectAlmSettingUuid("Azure DevOps 2"), NOW, NOW)); + assertProperties( + tuple("sonar.pullrequest.provider", "Azure DevOps", null), + tuple("sonar.pullrequest.provider", "Azure DevOps", projectId1), + tuple("sonar.pullrequest.provider", "Azure DevOps", projectId3)); + } + + @Test + public void ignore_when_project_is_missing_token() throws SQLException { + insertProperty("sonar.pullrequest.provider", "Azure DevOps", null); + long projectId1 = insertProject("PROJECT_1"); + // No token in project 1 + insertProperty("sonar.pullrequest.provider", "Azure DevOps", projectId1); + long projectId2 = insertProject("PROJECT_2"); + insertProperty("sonar.pullrequest.provider", "Azure DevOps", projectId2); + insertProperty("sonar.pullrequest.vsts.token.secured", "200", projectId2); + underTest.execute(); + + assertAlmSettings(tuple("azure_devops", "Azure DevOps", "200", NOW, NOW)); + assertProjectAlmSettings( + tuple("PROJECT_2", selectAlmSettingUuid("Azure DevOps"), NOW, NOW)); + assertProperties( + tuple("sonar.pullrequest.provider", "Azure DevOps", null), + tuple("sonar.pullrequest.provider", "Azure DevOps", projectId1), + tuple("sonar.pullrequest.provider", "Azure DevOps", projectId2)); + } + + @Test + public void use_existing_alm_setting() throws SQLException { + db.executeInsert("alm_settings", + "uuid", "ABCD", + "alm_id", "azure_devops", + "kee", "Azure DevOps", + "pat", "12345", + "created_at", PAST, + "updated_at", PAST); + long projectId1 = insertProject("PROJECT_1"); + insertProperty("sonar.pullrequest.provider", "Azure DevOps", projectId1); + insertProperty("sonar.pullrequest.vsts.token.secured", "12345", projectId1); + + underTest.execute(); + + assertProjectAlmSettings(tuple("PROJECT_1", "ABCD", NOW, NOW)); + assertProperties(tuple("sonar.pullrequest.provider", "Azure DevOps", projectId1)); + } + + @Test + public void delete_azure_settings_when_project_provider_is_not_set_to_azure() throws SQLException { + insertProperty("sonar.pullrequest.provider", "Azure DevOps", null); + insertProperty("sonar.pullrequest.vsts.token.secured", "12345", null); + long projectId1 = insertProject("PROJECT_1"); + // Project provider is set to something else + insertProperty("sonar.pullrequest.provider", "Bitbucket Server", projectId1); + insertProperty("sonar.pullrequest.vsts.token.secured", "12345", projectId1); + + underTest.execute(); + + assertAlmSettings(tuple("azure_devops", "Azure DevOps", "12345", NOW, NOW)); + assertNoProjectAlmSettings(); + assertProperties( + tuple("sonar.pullrequest.provider", "Azure DevOps", null), + tuple("sonar.pullrequest.provider", "Bitbucket Server", projectId1)); + } + + @Test + public void ignore_none_azure_project_settings() throws SQLException { + insertProperty("sonar.pullrequest.provider", "Azure DevOps", null); + insertProperty("sonar.pullrequest.vsts.token.secured", "12345", 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("azure_devops", "Azure DevOps", "12345", NOW, NOW)); + assertNoProjectAlmSettings(); + assertProperties( + tuple("sonar.pullrequest.provider", "Azure DevOps", null), + tuple("sonar.pullrequest.provider", "Bitbucket", projectId1), + tuple("sonar.pullrequest.provider", "Bitbucket", projectId2)); + } + + @Test + public void ignore_azure_setting_when_project_provider_is_not_azure() throws SQLException { + insertProperty("sonar.pullrequest.provider", "Azure DevOps", null); + insertProperty("sonar.pullrequest.vsts.token.secured", "12345", null); + long projectId1 = insertProject("PROJECT_1"); + insertProperty("sonar.pullrequest.provider", "Bitbucket", projectId1); + insertProperty("sonar.pullrequest.vsts.token.secured", "12345", projectId1); + + underTest.execute(); + + assertAlmSettings(tuple("azure_devops", "Azure DevOps", "12345", NOW, NOW)); + assertNoProjectAlmSettings(); + assertProperties( + tuple("sonar.pullrequest.provider", "Azure DevOps", null), + tuple("sonar.pullrequest.provider", "Bitbucket", projectId1)); + } + + @Test + public void do_not_create_project_settings_when_only_global_provider_is_set() throws SQLException { + insertProperty("sonar.pullrequest.provider", "Azure DevOps", null); + insertProperty("sonar.pullrequest.vsts.token.secured", "12345", null); + insertProject("PROJECT_1"); + insertProject("PROJECT_2"); + + underTest.execute(); + + assertAlmSettings(tuple("azure_devops", "Azure DevOps", "12345", NOW, NOW)); + assertNoProjectAlmSettings(); + assertProperties(tuple("sonar.pullrequest.provider", "Azure DevOps", null)); + } + + @Test + public void do_not_create_project_settings_when_missing_some_properties() throws SQLException { + long projectId1 = insertProject("PROJECT_1"); + // No provider + insertProperty("sonar.pullrequest.vsts.token.secured", "12345", projectId1); + + underTest.execute(); + + assertAlmSettings(tuple("azure_devops", "Azure DevOps", "12345", NOW, NOW)); + assertNoProjectAlmSettings(); + assertNoProperties(); + } + + @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", "Azure DevOps", null); + long projectId1 = insertProject("PROJECT_1"); + insertProperty("sonar.pullrequest.provider", "Azure DevOps", projectId1); + insertProperty("sonar.pullrequest.vsts.token.secured", "100", projectId1); + underTest.execute(); + + long projectId2 = insertProject("PROJECT_2"); + insertProperty("sonar.pullrequest.provider", "Azure DevOps", projectId2); + insertProperty("sonar.pullrequest.vsts.token.secured", "200", projectId2); + underTest.execute(); + + assertAlmSettings( + tuple("azure_devops", "Azure DevOps", "100", NOW, NOW), + tuple("azure_devops", "Azure DevOps 2", "200", NOW, NOW)); + assertProjectAlmSettings( + tuple("PROJECT_1", selectAlmSettingUuid("Azure DevOps"), NOW, NOW), + tuple("PROJECT_2", selectAlmSettingUuid("Azure DevOps 2"), NOW, NOW)); + assertProperties( + tuple("sonar.pullrequest.provider", "Azure DevOps", null), + tuple("sonar.pullrequest.provider", "Azure DevOps", projectId1), + tuple("sonar.pullrequest.provider", "Azure DevOps", projectId2)); + } + + private void assertAlmSettings(Tuple... expectedTuples) { + assertThat(db.select("SELECT alm_id, kee, pat, created_at, updated_at FROM alm_settings") + .stream() + .map(map -> new Tuple(map.get("ALM_ID"), map.get("KEE"), map.get("PAT"), 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, created_at, updated_at FROM project_alm_settings") + .stream() + .map(map -> new Tuple(map.get("PROJECT_UUID"), map.get("ALM_SETTING_UUID"), 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", null, + "UUID_PATH", ".", + "ROOT_UUID", uuid, + "PRIVATE", Boolean.toString(false), + "SCOPE", "PRJ", + "QUALIFIER", "TRK"); + return id; + } + +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/DeleteSonarPullRequestProviderPropertyTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/DeleteSonarPullRequestProviderPropertyTest/schema.sql new file mode 100644 index 00000000000..d84c238cd48 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/DeleteSonarPullRequestProviderPropertyTest/schema.sql @@ -0,0 +1,11 @@ +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"); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/MigrateAzureAlmSettingsTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/MigrateAzureAlmSettingsTest/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/MigrateAzureAlmSettingsTest/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); |