aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-db-migration
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@sonarsource.com>2019-10-29 15:17:11 +0100
committersonartech <sonartech@sonarsource.com>2019-11-06 10:04:31 +0100
commitec7dd4d0e7a1e26d1d0f9536aaa9255cf4cc6b3e (patch)
tree4f7600a393942c6124c2992c5004561544813cf3 /server/sonar-db-migration
parent60f700191bcadfe3d638e73b9e2ebfeeaa562c96 (diff)
downloadsonarqube-ec7dd4d0e7a1e26d1d0f9536aaa9255cf4cc6b3e.tar.gz
sonarqube-ec7dd4d0e7a1e26d1d0f9536aaa9255cf4cc6b3e.zip
SONAR-12514 Migrate Azure ALM settings to new tables
Diffstat (limited to 'server/sonar-db-migration')
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81.java5
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/MigrateAzureAlmSettings.java353
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81Test.java2
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/MigrateAzureAlmSettingsTest.java376
-rw-r--r--server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/DeleteSonarPullRequestProviderPropertyTest/schema.sql11
-rw-r--r--server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/MigrateAzureAlmSettingsTest/schema.sql86
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);