aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build.gradle4
-rw-r--r--gradle.properties2
-rw-r--r--server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeDaoIT.java17
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java7
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java1
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java2
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml3
-rw-r--r--server/sonar-server-common/src/it/java/org/sonar/server/component/index/EntityDefinitionIndexerIT.java73
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/component/index/EntityDefinitionIndexer.java33
9 files changed, 136 insertions, 6 deletions
diff --git a/build.gradle b/build.gradle
index a57fc2c5e5e..453320692d3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -324,8 +324,8 @@ subprojects {
dependency "org.sonarsource.api.plugin:sonar-plugin-api:$pluginApiVersion"
dependency "org.sonarsource.api.plugin:sonar-plugin-api-test-fixtures:$pluginApiVersion"
dependency 'org.sonarsource.xml:sonar-xml-plugin:2.13.0.5938'
- dependency 'org.sonarsource.iac:sonar-iac-plugin:1.45.0.14930'
- dependency 'com.sonarsource.iac:sonar-iac-enterprise-plugin:1.45.0.14930'
+ dependency 'org.sonarsource.iac:sonar-iac-plugin:1.46.0.15097'
+ dependency 'com.sonarsource.iac:sonar-iac-enterprise-plugin:1.46.0.15097'
dependency 'org.sonarsource.text:sonar-text-plugin:2.21.1.5779'
dependency 'com.sonarsource.text:sonar-text-developer-plugin:2.21.1.5779'
dependency 'com.sonarsource.text:sonar-text-enterprise-plugin:2.21.1.5779'
diff --git a/gradle.properties b/gradle.properties
index ab5ca3c6f6c..6580826a45e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -15,4 +15,4 @@ elasticSearchServerVersion=8.16.3
projectType=application
artifactoryUrl=https://repox.jfrog.io/repox
jre_release_name=jdk-17.0.13+11
-webappVersion=2025.3.0.15992
+webappVersion=2025.3.0.16221
diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeDaoIT.java
index e1fb868b7c0..10dda0d3735 100644
--- a/server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeDaoIT.java
+++ b/server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeDaoIT.java
@@ -2018,6 +2018,23 @@ oldCreationDate));
assertThat(db.countRowsOfTable(dbSession, "sca_issues_releases")).isEqualTo(1);
}
+ @Test
+ void whenDeleteBranch_thenPurgeArchitectureGraphs() {
+ ProjectDto project = db.components().insertPublicProject().getProjectDto();
+ BranchDto branch1 = db.components().insertProjectBranch(project);
+ BranchDto branch2 = db.components().insertProjectBranch(project);
+
+ db.executeInsert("architecture_graphs", Map.of("uuid", "12345", "branch_uuid", branch1.getUuid(), "source", "xoo", "type", "file_graph", "graph_data", "{}"));
+ db.executeInsert("architecture_graphs", Map.of("uuid", "123456", "branch_uuid", branch1.getUuid(), "source", "xoo", "type", "class_graph", "graph_data", "{}"));
+ db.executeInsert("architecture_graphs", Map.of("uuid", "1234567", "branch_uuid", branch2.getUuid(), "source", "xoo", "type", "file_graph", "graph_data", "{}"));
+
+ assertThat(db.countRowsOfTable(dbSession, "architecture_graphs")).isEqualTo(3);
+ underTest.deleteBranch(dbSession, branch1.getUuid());
+ assertThat(db.countRowsOfTable(dbSession, "architecture_graphs")).isEqualTo(1);
+ underTest.deleteBranch(dbSession, branch2.getUuid());
+ assertThat(db.countRowsOfTable(dbSession, "architecture_graphs")).isZero();
+ }
+
private AnticipatedTransitionDto getAnticipatedTransitionsDto(String uuid, String projectUuid, Date creationDate) {
return new AnticipatedTransitionDto(uuid, projectUuid, "userUuid", "transition", null, null, null, null, "rule:key", "filepath",
creationDate.getTime());
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java
index 230d9aff010..e0fb94fa09a 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java
@@ -510,6 +510,13 @@ class PurgeCommands {
profiler.stop();
}
+ public void deleteArchitectureGraphs(String branchUuid) {
+ profiler.start("deleteArchitectureGraphs (architecture_graphs)");
+ purgeMapper.deleteArchitectureGraphsByBranchUuid(branchUuid);
+ session.commit();
+ profiler.stop();
+ }
+
public void deleteAnticipatedTransitions(String projectUuid, long createdAt) {
profiler.start("deleteAnticipatedTransitions (anticipated_transitions)");
purgeMapper.deleteAnticipatedTransitionsByProjectUuidAndCreationDate(projectUuid, createdAt);
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java
index ce5e0cf5e70..79ebb206068 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java
@@ -281,6 +281,7 @@ public class PurgeDao implements Dao {
commands.deleteReportSubscriptions(branchUuid);
commands.deleteIssuesFixed(branchUuid);
commands.deleteScaActivity(branchUuid);
+ commands.deleteArchitectureGraphs(branchUuid);
}
private static void deleteProject(String projectUuid, PurgeMapper mapper, PurgeCommands commands) {
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java
index 5ca08a12d7a..085b28cef6c 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java
@@ -200,4 +200,6 @@ public interface PurgeMapper {
void deleteScaIssuesReleasesByComponentUuid(@Param("componentUuid") String componentUuid);
void deleteScaReleasesByComponentUuid(@Param("componentUuid") String componentUuid);
+
+ void deleteArchitectureGraphsByBranchUuid(@Param("branchUuid") String branchUuid);
}
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml
index 4a64f3cdeab..f8d213b6a3b 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml
@@ -670,6 +670,9 @@
delete from issues_fixed where pull_request_uuid = #{branchUuid,jdbcType=VARCHAR}
</delete>
+ <delete id="deleteArchitectureGraphsByBranchUuid">
+ delete from architecture_graphs where branch_uuid = #{branchUuid,jdbcType=VARCHAR}
+ </delete>
<delete id="deleteScaDependenciesByComponentUuid">
delete from sca_dependencies where sca_release_uuid in (select uuid from sca_releases where component_uuid = #{componentUuid,jdbcType=VARCHAR})
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/component/index/EntityDefinitionIndexerIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/component/index/EntityDefinitionIndexerIT.java
index e898a27dbb7..2f624caebef 100644
--- a/server/sonar-server-common/src/it/java/org/sonar/server/component/index/EntityDefinitionIndexerIT.java
+++ b/server/sonar-server-common/src/it/java/org/sonar/server/component/index/EntityDefinitionIndexerIT.java
@@ -21,10 +21,14 @@ package org.sonar.server.component.index;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Optional;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.slf4j.event.Level;
+import org.sonar.api.testfixtures.log.LogTester;
import org.sonar.api.utils.System2;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
@@ -33,15 +37,18 @@ import org.sonar.db.component.BranchDto;
import org.sonar.db.component.ProjectData;
import org.sonar.db.entity.EntityDto;
import org.sonar.db.es.EsQueueDto;
+import org.sonar.db.portfolio.PortfolioDto;
import org.sonar.db.project.ProjectDto;
import org.sonar.server.es.EsClient;
import org.sonar.server.es.EsTester;
import org.sonar.server.es.Indexers;
import org.sonar.server.es.IndexingResult;
+import static java.lang.String.format;
import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatException;
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
import static org.sonar.db.component.ComponentQualifiers.PROJECT;
import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_NAME;
@@ -60,10 +67,18 @@ public class EntityDefinitionIndexerIT {
public EsTester es = EsTester.create();
@Rule
public DbTester db = DbTester.create(system2);
+ @Rule
+ public LogTester logTester = new LogTester();
private DbClient dbClient = db.getDbClient();
private DbSession dbSession = db.getSession();
- private EntityDefinitionIndexer underTest = new EntityDefinitionIndexer(db.getDbClient(), es.client());
+ private EntityDefinitionIndexer underTest;
+
+ @Before
+ public void setup() {
+ underTest = new EntityDefinitionIndexer(db.getDbClient(), es.client());
+ logTester.setLevel(Level.DEBUG);
+ }
@Test
public void test_getIndexTypes() {
@@ -121,6 +136,62 @@ public class EntityDefinitionIndexerIT {
}
@Test
+ public void indexOnStartup_fixes_corrupted_portfolios_if_possible_and_then_indexes_them() throws Exception {
+ underTest = new EntityDefinitionIndexer(db.getDbClient(), es.client());
+ String uuid = "portfolioUuid1";
+ ProjectDto project = db.components().insertPrivateProject().getProjectDto();
+ PortfolioDto corruptedPortfolio = new PortfolioDto()
+ .setKey("portfolio1")
+ .setName("My Portfolio")
+ .setSelectionMode(PortfolioDto.SelectionMode.NONE)
+ .setUuid(uuid)
+ .setRootUuid(uuid);
+ db.getDbClient().portfolioDao().insert(dbSession, corruptedPortfolio, false);
+
+ // corrupt the portfolio in a fixable way (root portfolio with self-referential parent_uuid)
+ dbSession.getSqlSession().getConnection().prepareStatement(format("UPDATE portfolios SET parent_uuid = '%s' where uuid = '%s'", uuid, uuid))
+ .execute();
+ dbSession.commit();
+ Optional<EntityDto> entity = dbClient.entityDao().selectByUuid(dbSession, uuid);
+
+ assertThat(entity).isPresent();
+ assertThat(entity.get().getAuthUuid()).isNull();
+
+ underTest.indexOnStartup(emptySet());
+
+ assertThat(logTester.logs()).contains("Fixing corrupted portfolio tree for root portfolio " + corruptedPortfolio.getUuid());
+ assertThatIndexContainsOnly(project, corruptedPortfolio);
+ }
+
+ @Test
+ public void indexOnStartup_logs_warning_about_corrupted_portfolios_that_cannot_be_fixed_automatically() throws Exception {
+ underTest = new EntityDefinitionIndexer(db.getDbClient(), es.client());
+ String uuid = "portfolioUuid1";
+ PortfolioDto corruptedPortfolio = new PortfolioDto()
+ .setKey("portfolio1")
+ .setName("My Portfolio")
+ .setSelectionMode(PortfolioDto.SelectionMode.NONE)
+ .setUuid(uuid)
+ .setRootUuid(uuid);
+ db.getDbClient().portfolioDao().insert(dbSession, corruptedPortfolio, false);
+
+ // corrupt the portfolio in an un-fixable way (non-existent parent)
+ dbSession.getSqlSession().getConnection().prepareStatement(format("UPDATE portfolios SET parent_uuid = 'junk_uuid' where uuid = '%s'", uuid))
+ .execute();
+ dbSession.commit();
+ Optional<EntityDto> entity = dbClient.entityDao().selectByUuid(dbSession, uuid);
+
+ assertThat(entity).isPresent();
+ assertThat(entity.get().getAuthUuid()).isNull();
+
+ assertThatException()
+ .isThrownBy(() -> underTest.indexOnStartup(emptySet()));
+
+ assertThat(logTester.logs()).contains("Detected portfolio tree corruption for portfolio " + corruptedPortfolio.getUuid());
+
+ }
+
+ @Test
public void indexOnAnalysis_indexes_project() {
ProjectData project = db.components().insertPrivateProject();
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/component/index/EntityDefinitionIndexer.java b/server/sonar-server-common/src/main/java/org/sonar/server/component/index/EntityDefinitionIndexer.java
index 9a9a4cb96f3..a2a2a55ae9c 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/component/index/EntityDefinitionIndexer.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/component/index/EntityDefinitionIndexer.java
@@ -30,6 +30,8 @@ import java.util.stream.Collectors;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.BranchDto;
@@ -56,7 +58,7 @@ import static org.sonar.server.component.index.ComponentIndexDefinition.TYPE_COM
* Indexes the definition of all entities: projects, applications, portfolios and sub-portfolios.
*/
public class EntityDefinitionIndexer implements EventIndexer, AnalysisIndexer, NeedAuthorizationIndexer {
-
+ private static final Logger LOG = LoggerFactory.getLogger(EntityDefinitionIndexer.class);
private static final AuthorizationScope AUTHORIZATION_SCOPE = new AuthorizationScope(TYPE_COMPONENT, entity -> true);
private static final Set<IndexType> INDEX_TYPES = Set.of(TYPE_COMPONENT);
@@ -172,16 +174,43 @@ public class EntityDefinitionIndexer implements EventIndexer, AnalysisIndexer, N
private void doIndexByEntityUuid(Size bulkSize) {
BulkIndexer bulk = new BulkIndexer(esClient, TYPE_COMPONENT, bulkSize);
bulk.start();
+ Set<EntityDto> corruptedEntities = new HashSet<>();
try (DbSession dbSession = dbClient.openSession(false)) {
dbClient.entityDao().scrollForIndexing(dbSession, context -> {
EntityDto dto = context.getResultObject();
- bulk.add(toDocument(dto).toIndexRequest());
+ if (dto.getAuthUuid() == null) {
+ corruptedEntities.add(dto);
+ } else {
+ bulk.add(toDocument(dto).toIndexRequest());
+ }
});
+ if (!corruptedEntities.isEmpty()) {
+ attemptToFixCorruptedEntities(dbSession, corruptedEntities);
+ List<EntityDto> fixedEntities = dbClient.entityDao().selectByUuids(dbSession, corruptedEntities.stream().map(EntityDto::getUuid).toList());
+ fixedEntities.forEach(entity -> bulk.add(toDocument(entity).toIndexRequest()));
+ }
}
bulk.stop();
}
+ private void attemptToFixCorruptedEntities(DbSession dbSession, Set<EntityDto> corruptedEntities) {
+ for (EntityDto entity : corruptedEntities) {
+ dbClient.portfolioDao().selectByUuid(dbSession, entity.getUuid()).ifPresent(portfolio -> {
+ String portfolioUuid = portfolio.getUuid();
+ String rootUuid = portfolio.getRootUuid();
+ String parentUuid = portfolio.getParentUuid();
+ if (portfolioUuid.equals(rootUuid) && portfolioUuid.equals(parentUuid)) {
+ LOG.warn("Fixing corrupted portfolio tree for root portfolio {}", portfolioUuid);
+ portfolio.setParentUuid(null);
+ dbClient.portfolioDao().update(dbSession, portfolio);
+ } else {
+ LOG.warn("Detected portfolio tree corruption for portfolio {}", portfolioUuid);
+ }
+ });
+ }
+ }
+
private static void addProjectDeletionToBulkIndexer(BulkIndexer bulkIndexer, String projectUuid) {
SearchRequest searchRequest = EsClient.prepareSearch(TYPE_COMPONENT.getMainType())
.source(new SearchSourceBuilder().query(QueryBuilders.termQuery(ComponentIndexDefinition.FIELD_UUID, projectUuid)))