diff options
19 files changed, 198 insertions, 92 deletions
diff --git a/gradle.properties b/gradle.properties index ec5fb845e29..91ec695bb14 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.2.0.13338 +webappVersion=2025.2.0.13464 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 f93eaafd3aa..a01c48130d9 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 @@ -89,6 +89,10 @@ import org.sonar.db.property.PropertyDto; import org.sonar.db.report.ReportScheduleDto; import org.sonar.db.report.ReportSubscriptionDto; import org.sonar.db.rule.RuleDto; +import org.sonar.db.sca.ScaDependenciesDbTester; +import org.sonar.db.sca.ScaIssueReleaseDto; +import org.sonar.db.sca.ScaReleasesDbTester; +import org.sonar.db.sca.ScaSeverity; import org.sonar.db.source.FileSourceDto; import org.sonar.db.user.UserDismissedMessageDto; import org.sonar.db.user.UserDto; @@ -1960,6 +1964,40 @@ oldCreationDate)); } + @Test + void deleteBranch_purgesScaActivity() { + ProjectDto project = db.components().insertPublicProject().getProjectDto(); + BranchDto branch1 = db.components().insertProjectBranch(project); + BranchDto branch2 = db.components().insertProjectBranch(project); + + ScaReleasesDbTester scaReleasesDbTester = new ScaReleasesDbTester(db); + var release1 = scaReleasesDbTester.insertScaRelease(branch1.getUuid(), "1"); + var release2 = scaReleasesDbTester.insertScaRelease(branch2.getUuid(), "2"); + + ScaDependenciesDbTester scaDependenciesDbTester = new ScaDependenciesDbTester(db); + scaDependenciesDbTester.insertScaDependency(branch1.getUuid(), release1.uuid(), "1",false); + scaDependenciesDbTester.insertScaDependency(branch2.getUuid(), release2.uuid(), "2", false); + + ScaIssueReleaseDto issueRelease1 = new ScaIssueReleaseDto.Builder().setUuid("foo1").setScaIssueUuid("baz").setSeverity(ScaSeverity.LOW).setScaReleaseUuid(release1.uuid()).build(); + ScaIssueReleaseDto issueRelease2 = new ScaIssueReleaseDto.Builder().setUuid("foo2").setScaIssueUuid("baz").setSeverity(ScaSeverity.LOW).setScaReleaseUuid(release2.uuid()).build(); + dbClient.scaIssuesReleasesDao().insert(dbSession, issueRelease1); + dbClient.scaIssuesReleasesDao().insert(dbSession, issueRelease2); + + assertThat(dbClient.scaReleasesDao().selectByBranchUuid(dbSession, branch1.getUuid())).isNotEmpty(); + assertThat(dbClient.scaDependenciesDao().selectByBranchUuid(dbSession, branch1.getUuid())).isNotEmpty(); + assertThat(dbClient.scaIssuesReleasesDao().selectByBranchUuid(dbSession, branch1.getUuid())).isNotEmpty(); + + underTest.deleteBranch(dbSession, branch1.getUuid()); + + assertThat(dbClient.scaReleasesDao().selectByBranchUuid(dbSession, branch1.getUuid())).isEmpty(); + assertThat(dbClient.scaDependenciesDao().selectByBranchUuid(dbSession, branch1.getUuid())).isEmpty(); + assertThat(dbClient.scaIssuesReleasesDao().selectByBranchUuid(dbSession, branch1.getUuid())).isEmpty(); + + assertThat(dbClient.scaReleasesDao().selectByBranchUuid(dbSession, branch2.getUuid())).isNotEmpty(); + assertThat(dbClient.scaDependenciesDao().selectByBranchUuid(dbSession, branch2.getUuid())).isNotEmpty(); + assertThat(dbClient.scaIssuesReleasesDao().selectByBranchUuid(dbSession, branch2.getUuid())).isNotEmpty(); + } + 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/it/java/org/sonar/db/sca/ScaIssuesReleasesDetailsDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaIssuesReleasesDetailsDaoIT.java index ddce535af50..cfd4604660b 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaIssuesReleasesDetailsDaoIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaIssuesReleasesDetailsDaoIT.java @@ -74,7 +74,8 @@ class ScaIssuesReleasesDetailsDaoIT { ScaIssueDto.NULL_VALUE, ScaSeverity.INFO, List.of("cwe1"), - new BigDecimal("7.1")); + new BigDecimal("7.1"), + 1L); ScaIssueReleaseDetailsDto expected2 = new ScaIssueReleaseDetailsDto( issue2.scaIssueReleaseUuid(), issue2.severity(), @@ -87,7 +88,8 @@ class ScaIssuesReleasesDetailsDaoIT { "0BSD", null, null, - null); + null, + 1L); assertThat(foundPage).hasSize(1).isSubsetOf(expected1, expected2); var foundAllIssues = scaIssuesReleasesDetailsDao.selectByBranchUuid(db.getSession(), componentDto.branchUuid(), Pagination.forPage(1).andSize(10)); @@ -565,7 +567,8 @@ class ScaIssuesReleasesDetailsDaoIT { ScaIssueDto.NULL_VALUE, ScaSeverity.INFO, List.of("cwe1"), - new BigDecimal("7.1")); + new BigDecimal("7.1"), + 1L); var foundIssue = scaIssuesReleasesDetailsDao.selectByScaIssueReleaseUuid(db.getSession(), issue1.scaIssueReleaseUuid()); assertThat(foundIssue).isEqualTo(expected); 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 9847cbc75a3..230d9aff010 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 @@ -523,4 +523,23 @@ class PurgeCommands { session.commit(); profiler.stop(); } + + public void deleteScaActivity(String componentUuid) { + profiler.start("deleteScaDependencies (sca_dependencies)"); + purgeMapper.deleteScaDependenciesByComponentUuid(componentUuid); + session.commit(); + profiler.stop(); + + profiler.start("deleteScaIssuesReleases (sca_issues_releases)"); + purgeMapper.deleteScaIssuesReleasesByComponentUuid(componentUuid); + session.commit(); + profiler.stop(); + + // sca_releases MUST be deleted last because dependencies and + // issues_releases only join to the component through sca_releases + profiler.start("deleteScaReleases (sca_releases)"); + purgeMapper.deleteScaReleasesByComponentUuid(componentUuid); + session.commit(); + profiler.stop(); + } } 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 0d0e13b89ab..ce5e0cf5e70 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 @@ -280,6 +280,7 @@ public class PurgeDao implements Dao { commands.deleteReportSchedules(branchUuid); commands.deleteReportSubscriptions(branchUuid); commands.deleteIssuesFixed(branchUuid); + commands.deleteScaActivity(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 c7a312c905a..5ca08a12d7a 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 @@ -194,4 +194,10 @@ public interface PurgeMapper { void deleteAnticipatedTransitionsByProjectUuidAndCreationDate(@Param("projectUuid") String projectUuid, @Param("createdAtBefore") Long createdAtBefore); void deleteIssuesFixedByBranchUuid(@Param("branchUuid") String branchUuid); + + void deleteScaDependenciesByComponentUuid(@Param("componentUuid") String componentUuid); + + void deleteScaIssuesReleasesByComponentUuid(@Param("componentUuid") String componentUuid); + + void deleteScaReleasesByComponentUuid(@Param("componentUuid") String componentUuid); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaIssueReleaseDetailsDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaIssueReleaseDetailsDto.java index 3176985c7e3..827ceddf64f 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaIssueReleaseDetailsDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaIssueReleaseDetailsDto.java @@ -22,6 +22,7 @@ package org.sonar.db.sca; import java.math.BigDecimal; import java.util.List; import javax.annotation.Nullable; +import org.sonar.api.utils.DateUtils; /** * <p>A "read-only" DTO used to query the join of sca_issues_releases, sca_issues, and sca_*_issues. @@ -51,7 +52,14 @@ public record ScaIssueReleaseDetailsDto(String scaIssueReleaseUuid, String spdxLicenseId, @Nullable ScaSeverity vulnerabilityBaseSeverity, @Nullable List<String> cweIds, - @Nullable BigDecimal cvssScore) implements ScaIssueIdentity { + @Nullable BigDecimal cvssScore, + long createdAt) implements ScaIssueIdentity { + + // DateUtils says that this returns an RFC 822 timestamp + // but it is really a ISO 8601 timestamp. + public String createdAtIso8601() { + return DateUtils.formatDateTime(createdAt); + } public Builder toBuilder() { return new Builder() @@ -66,7 +74,8 @@ public record ScaIssueReleaseDetailsDto(String scaIssueReleaseUuid, .setSpdxLicenseId(spdxLicenseId) .setVulnerabilityBaseSeverity(vulnerabilityBaseSeverity) .setCweIds(cweIds) - .setCvssScore(cvssScore); + .setCvssScore(cvssScore) + .setCreatedAt(createdAt); } public static class Builder { @@ -82,6 +91,7 @@ public record ScaIssueReleaseDetailsDto(String scaIssueReleaseUuid, private ScaSeverity vulnerabilityBaseSeverity; private List<String> cweIds; private BigDecimal cvssScore; + private long createdAt; public Builder setScaIssueReleaseUuid(String scaIssueReleaseUuid) { this.scaIssueReleaseUuid = scaIssueReleaseUuid; @@ -143,9 +153,14 @@ public record ScaIssueReleaseDetailsDto(String scaIssueReleaseUuid, return this; } + public Builder setCreatedAt(long createdAt) { + this.createdAt = createdAt; + return this; + } + public ScaIssueReleaseDetailsDto build() { return new ScaIssueReleaseDetailsDto(scaIssueReleaseUuid, severity, scaIssueUuid, scaReleaseUuid, scaIssueType, - newInPullRequest, packageUrl, vulnerabilityId, spdxLicenseId, vulnerabilityBaseSeverity, cweIds, cvssScore); + newInPullRequest, packageUrl, vulnerabilityId, spdxLicenseId, vulnerabilityBaseSeverity, cweIds, cvssScore, createdAt); } } } 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 3700a8df647..4a64f3cdeab 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 @@ -669,4 +669,15 @@ <delete id="deleteIssuesFixedByBranchUuid"> delete from issues_fixed where pull_request_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}) + </delete> + <delete id="deleteScaIssuesReleasesByComponentUuid"> + delete from sca_issues_releases where sca_release_uuid in (select uuid from sca_releases where component_uuid = #{componentUuid,jdbcType=VARCHAR}) + </delete> + <delete id="deleteScaReleasesByComponentUuid"> + delete from sca_releases where component_uuid = #{componentUuid,jdbcType=VARCHAR} + </delete> </mapper> diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/sca/ScaIssuesReleasesDetailsMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/sca/ScaIssuesReleasesDetailsMapper.xml index 253050069b9..4c3189af393 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/sca/ScaIssuesReleasesDetailsMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/sca/ScaIssuesReleasesDetailsMapper.xml @@ -18,6 +18,8 @@ <arg column="cwe_ids" typeHandler="org.sonar.db.sca.ListOfStringsTypeHandler" jdbcType="VARCHAR" javaType="java.util.List"/> <arg column="cvss_score" javaType="java.math.BigDecimal"/> + <!-- the underscore prefix means to use the primitive type instead of boxed type --> + <arg column="created_at" javaType="_long"/> </constructor> </resultMap> @@ -34,7 +36,8 @@ si.spdx_license_id, svi.base_severity, svi.cwe_ids, - svi.cvss_score + svi.cvss_score, + sir.created_at </sql> <sql id="sqlBaseJoins"> diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaIssueReleaseDetailsDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaIssueReleaseDetailsDtoTest.java index 4511f4b468e..de7a64448a1 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaIssueReleaseDetailsDtoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaIssueReleaseDetailsDtoTest.java @@ -39,7 +39,8 @@ class ScaIssueReleaseDetailsDtoTest { "spdxLicenseId", ScaSeverity.BLOCKER, List.of("cwe1"), - BigDecimal.ONE); + BigDecimal.ONE, + 42L); assertThat(dto.toBuilder().build()).isEqualTo(dto); } } diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/sca/ScaIssuesReleasesDetailsDbTester.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/sca/ScaIssuesReleasesDetailsDbTester.java index c4c184df38d..7c29799376e 100644 --- a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/sca/ScaIssuesReleasesDetailsDbTester.java +++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/sca/ScaIssuesReleasesDetailsDbTester.java @@ -41,7 +41,8 @@ public class ScaIssuesReleasesDetailsDbTester { newInPullRequest, scaIssueDto.packageUrl(), scaIssueDto.vulnerabilityId(), scaIssueDto.spdxLicenseId(), scaVulnerabilityIssueDtoOptional.map(ScaVulnerabilityIssueDto::baseSeverity).orElse(null), scaVulnerabilityIssueDtoOptional.map(ScaVulnerabilityIssueDto::cweIds).orElse(null), - scaVulnerabilityIssueDtoOptional.map(ScaVulnerabilityIssueDto::cvssScore).orElse(null)); + scaVulnerabilityIssueDtoOptional.map(ScaVulnerabilityIssueDto::cvssScore).orElse(null), + scaIssueReleaseDto.createdAt()); } private ScaIssueReleaseDetailsDto insertIssue(ScaIssueDto scaIssue, Optional<ScaVulnerabilityIssueDto> scaVulnerabilityIssueDtoOptional, diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryDaemon.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryDaemon.java index 8d6ac35d955..e0dd552ec97 100644 --- a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryDaemon.java +++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryDaemon.java @@ -116,27 +116,28 @@ public class TelemetryDaemon extends AbstractStoppableScheduledExecutorServiceIm private Runnable telemetryCommand() { return () -> { - if (!lockManager.tryLock(LOCK_NAME, lockDuration())) { - return; - } - - long now = system2.now(); try { + if (!lockManager.tryLock(LOCK_NAME, lockDuration())) { + return; + } + + long now = system2.now(); if (shouldUploadStatistics(now)) { uploadLegacyTelemetry(); uploadMetrics(); - writeTelemetrySequence(); + + updateTelemetryProps(now); } } catch (Exception e) { LOG.debug("Error while checking SonarQube statistics: {}", e.getMessage(), e); - } finally { - writeLastPing(now); } // do not check at start up to exclude test instance which are not up for a long time }; } - private void writeTelemetrySequence() { + private void updateTelemetryProps(long now) { + internalProperties.write(I_PROP_LAST_PING, String.valueOf(now)); + Optional<String> currentSequence = internalProperties.read(I_PROP_MESSAGE_SEQUENCE); if (currentSequence.isEmpty()) { internalProperties.write(I_PROP_MESSAGE_SEQUENCE, String.valueOf(1)); diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryDaemonTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryDaemonTest.java index d0028acc40f..8a1b9471019 100644 --- a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryDaemonTest.java +++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryDaemonTest.java @@ -49,6 +49,7 @@ import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.after; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; @@ -115,32 +116,23 @@ class TelemetryDaemonTest { void start_whenLastPingEarlierThanOneDayAgo_shouldNotSendData() throws IOException { initTelemetrySettingsToDefaultValues(); when(lockManager.tryLock(any(), anyInt())).thenReturn(true); - settings.setProperty("sonar.telemetry.frequencyInSeconds", "1"); long now = system2.now(); long twentyHoursAgo = now - (ONE_HOUR * 20L); - mockDataJsonWriterDoingSomething(); - + long moreThanOneDayAgo = now - ONE_DAY - ONE_HOUR; internalProperties.write("telemetry.lastPing", String.valueOf(twentyHoursAgo)); - underTest.start(); - - verify(client, after(2_000).never()).upload(anyString()); - } - - @Test - void start_whenExceptionThrown_shouldNotRepeatedlySendDataAndLastPingPropIsStillSet() throws IOException { - initTelemetrySettingsToDefaultValues(); - when(lockManager.tryLock(any(), anyInt())).thenReturn(true); settings.setProperty("sonar.telemetry.frequencyInSeconds", "1"); - long today = parseDate("2017-08-01").getTime(); - system2.setNow(today); - settings.removeProperty("telemetry.lastPing"); + when(dataLoader.load()).thenReturn(SOME_TELEMETRY_DATA); mockDataJsonWriterDoingSomething(); - when(dataLoader.load()).thenThrow(new IllegalStateException("Some error was thrown.")); underTest.start(); - verify(client, after(2_000).never()).upload(anyString()); - verify(internalProperties, timeout(4_000).atLeastOnce()).write("telemetry.lastPing", String.valueOf(today)); + verify(dataJsonWriter, after(2_000).never()).writeTelemetryData(any(JsonWriter.class), same(SOME_TELEMETRY_DATA)); + verify(client, never()).upload(anyString()); + + internalProperties.write("telemetry.lastPing", String.valueOf(moreThanOneDayAgo)); + + verify(client, timeout(2_000)).upload(anyString()); + verify(dataJsonWriter).writeTelemetryData(any(JsonWriter.class), same(SOME_TELEMETRY_DATA)); } @Test @@ -208,6 +200,12 @@ class TelemetryDaemonTest { verify(internalProperties, timeout(4_000)).write("telemetry.messageSeq", "11"); } + private void initTelemetrySettingsToDefaultValues() { + settings.setProperty(SONAR_TELEMETRY_ENABLE.getKey(), SONAR_TELEMETRY_ENABLE.getDefaultValue()); + settings.setProperty(SONAR_TELEMETRY_URL.getKey(), SONAR_TELEMETRY_URL.getDefaultValue()); + settings.setProperty(SONAR_TELEMETRY_FREQUENCY_IN_SECONDS.getKey(), SONAR_TELEMETRY_FREQUENCY_IN_SECONDS.getDefaultValue()); + } + private void mockDataJsonWriterDoingSomething() { doAnswer(t -> { JsonWriter json = t.getArgument(0); @@ -218,10 +216,4 @@ class TelemetryDaemonTest { .writeTelemetryData(any(), any()); } - private void initTelemetrySettingsToDefaultValues() { - settings.setProperty(SONAR_TELEMETRY_ENABLE.getKey(), SONAR_TELEMETRY_ENABLE.getDefaultValue()); - settings.setProperty(SONAR_TELEMETRY_URL.getKey(), SONAR_TELEMETRY_URL.getDefaultValue()); - settings.setProperty(SONAR_TELEMETRY_FREQUENCY_IN_SECONDS.getKey(), SONAR_TELEMETRY_FREQUENCY_IN_SECONDS.getDefaultValue()); - } - } diff --git a/sonar-core/src/main/java/org/sonar/core/config/AiCodeFixEnablementConstants.java b/sonar-core/src/main/java/org/sonar/core/config/AiCodeFixEnablementConstants.java index 36837a75065..c60222b3114 100644 --- a/sonar-core/src/main/java/org/sonar/core/config/AiCodeFixEnablementConstants.java +++ b/sonar-core/src/main/java/org/sonar/core/config/AiCodeFixEnablementConstants.java @@ -21,8 +21,8 @@ package org.sonar.core.config; public final class AiCodeFixEnablementConstants { public static final String SUGGESTION_FEATURE_ENABLED_PROPERTY = "sonar.ai.suggestions.enabled"; - public static final String SUGGESTION_PROVIDER_NAME_PROPERTY = "sonar.ai.suggestions.provider.name"; - public static final String SUGGESTION_PROVIDER_ENDPOINT_URL_PROPERTY = "sonar.ai.suggestions.provider.endpointUrl"; + public static final String SUGGESTION_PROVIDER_KEY_PROPERTY = "sonar.ai.suggestions.provider.key"; + public static final String SUGGESTION_PROVIDER_ENDPOINT_PROPERTY = "sonar.ai.suggestions.provider.endpoint"; public static final String SUGGESTION_PROVIDER_API_KEY_INTERNAL_PROPERTY = "sonar.ai.suggestions.provider.apiKey"; private AiCodeFixEnablementConstants() { diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java index 39f46bb00b7..c2a474c4e52 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java @@ -30,6 +30,7 @@ import java.util.function.Consumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.fs.internal.DefaultInputModule; +import org.sonar.api.platform.Server; import org.sonar.api.utils.System2; import org.sonar.core.util.ProcessWrapperFactory; import org.sonar.scanner.config.DefaultConfiguration; @@ -46,11 +47,13 @@ public class CliService { private final ProcessWrapperFactory processWrapperFactory; private final TelemetryCache telemetryCache; private final System2 system2; + private final Server server; - public CliService(ProcessWrapperFactory processWrapperFactory, TelemetryCache telemetryCache, System2 system2) { + public CliService(ProcessWrapperFactory processWrapperFactory, TelemetryCache telemetryCache, System2 system2, Server server) { this.processWrapperFactory = processWrapperFactory; this.telemetryCache = telemetryCache; this.system2 = system2; + this.server = server; } public File generateManifestsZip(DefaultInputModule module, File cliExecutable, DefaultConfiguration configuration) throws IOException, IllegalStateException { @@ -89,6 +92,8 @@ public class CliService { // sending this will tell the CLI to skip checking for the latest available version on startup envProperties.put("TIDELIFT_SKIP_UPDATE_CHECK", "1"); envProperties.put("TIDELIFT_ALLOW_MANIFEST_FAILURES", "1"); + envProperties.put("TIDELIFT_CLI_INSIDE_SCANNER_ENGINE", "1"); + envProperties.put("TIDELIFT_CLI_SQ_SERVER_VERSION", server.getVersion()); envProperties.putAll(ScaProperties.buildFromScannerProperties(configuration)); LOG.debug("Environment properties: {}", envProperties); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/SpringProjectScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/SpringProjectScanContainer.java index ed1909c0b7c..ead791bdeaf 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/SpringProjectScanContainer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/SpringProjectScanContainer.java @@ -19,9 +19,9 @@ */ package org.sonar.scanner.scan; +import jakarta.annotation.Priority; import java.util.Collection; import java.util.Set; -import jakarta.annotation.Priority; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.Plugin; @@ -139,8 +139,7 @@ public class SpringProjectScanContainer extends SpringComponentContainer { // SCA CliService.class, CliCacheService.class, - ScaExecutor.class - ); + ScaExecutor.class); } static ExtensionMatcher getScannerProjectExtensionsFilter() { diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliServiceTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliServiceTest.java index 6b6dde4efcf..b8bb1a26961 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliServiceTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliServiceTest.java @@ -34,6 +34,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.io.TempDir; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.fs.internal.DefaultInputModule; +import org.sonar.api.platform.Server; import org.sonar.api.testfixtures.log.LogTesterJUnit5; import org.sonar.api.utils.System2; import org.sonar.core.util.ProcessWrapperFactory; @@ -48,29 +49,25 @@ import static org.slf4j.event.Level.INFO; class CliServiceTest { private TelemetryCache telemetryCache; + private DefaultInputModule rootInputModule; + private final Server server = mock(Server.class); @RegisterExtension private final LogTesterJUnit5 logTester = new LogTesterJUnit5(); + @TempDir + Path rootModuleDir; private CliService underTest; @BeforeEach void setup() { telemetryCache = new TelemetryCache(); - underTest = new CliService(new ProcessWrapperFactory(), telemetryCache, System2.INSTANCE); + rootInputModule = new DefaultInputModule( + ProjectDefinition.create().setBaseDir(rootModuleDir.toFile()).setWorkDir(rootModuleDir.toFile())); + underTest = new CliService(new ProcessWrapperFactory(), telemetryCache, System2.INSTANCE, server); } @Test - void generateZip_shouldCallProcessCorrectly_andRegisterTelemetry(@TempDir Path rootModuleDir) throws IOException, URISyntaxException { - DefaultInputModule root = new DefaultInputModule( - ProjectDefinition.create().setBaseDir(rootModuleDir.toFile()).setWorkDir(rootModuleDir.toFile())); - - // There is a custom test Bash script available in src/test/resources/org/sonar/scanner/sca that - // will serve as our "CLI". This script will output some messages about what arguments were passed - // to it and will try to generate a zip file in the location the process specifies. This allows us - // to simulate a real CLI call without needing an OS specific CLI executable to run on a real project. - URL scriptUrl = CliServiceTest.class.getResource(SystemUtils.IS_OS_WINDOWS ? "echo_args.bat" : "echo_args.sh"); - assertThat(scriptUrl).isNotNull(); - File scriptDir = new File(scriptUrl.toURI()); + void generateZip_shouldCallProcessCorrectly_andRegisterTelemetry() throws IOException, URISyntaxException { assertThat(rootModuleDir.resolve("test_file").toFile().createNewFile()).isTrue(); // We need to set the logging level to debug in order to be able to view the shell script's output @@ -81,9 +78,9 @@ class CliServiceTest { "save-lockfiles", "--zip", "--zip-filename", - root.getWorkDir().resolve("dependency-files.zip").toString(), + rootInputModule.getWorkDir().resolve("dependency-files.zip").toString(), "--directory", - root.getBaseDir().toString(), + rootInputModule.getBaseDir().toString(), "--debug"); String argumentOutput = "Arguments Passed In: " + String.join(" ", args); @@ -91,7 +88,7 @@ class CliServiceTest { when(configuration.getProperties()).thenReturn(Map.of("sonar.sca.recursiveManifestSearch", "true")); when(configuration.get("sonar.sca.recursiveManifestSearch")).thenReturn(Optional.of("true")); - File producedZip = underTest.generateManifestsZip(root, scriptDir, configuration); + File producedZip = underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration); assertThat(producedZip).exists(); assertThat(logTester.logs(DEBUG)) @@ -106,17 +103,7 @@ class CliServiceTest { } @Test - void generateZip_whenDebugLogLevel_shouldCallProcessCorrectly(@TempDir Path rootModuleDir) throws IOException, URISyntaxException { - DefaultInputModule root = new DefaultInputModule( - ProjectDefinition.create().setBaseDir(rootModuleDir.toFile()).setWorkDir(rootModuleDir.toFile())); - - // There is a custom test Bash script available in src/test/resources/org/sonar/scanner/sca that - // will serve as our "CLI". This script will output some messages about what arguments were passed - // to it and will try to generate a zip file in the location the process specifies. This allows us - // to simulate a real CLI call without needing an OS specific CLI executable to run on a real project. - URL scriptUrl = CliServiceTest.class.getResource(SystemUtils.IS_OS_WINDOWS ? "echo_args.bat" : "echo_args.sh"); - assertThat(scriptUrl).isNotNull(); - File scriptDir = new File(scriptUrl.toURI()); + void generateZip_whenDebugLogLevel_shouldCallProcessCorrectly() throws IOException, URISyntaxException { assertThat(rootModuleDir.resolve("test_file").toFile().createNewFile()).isTrue(); // We need to set the logging level to debug in order to be able to view the shell script's output @@ -127,9 +114,9 @@ class CliServiceTest { "save-lockfiles", "--zip", "--zip-filename", - root.getWorkDir().resolve("dependency-files.zip").toString(), + rootInputModule.getWorkDir().resolve("dependency-files.zip").toString(), "--directory", - root.getBaseDir().toString(), + rootInputModule.getBaseDir().toString(), "--debug"); String argumentOutput = "Arguments Passed In: " + String.join(" ", args); @@ -137,24 +124,14 @@ class CliServiceTest { when(configuration.getProperties()).thenReturn(Map.of("sonar.sca.recursiveManifestSearch", "true")); when(configuration.get("sonar.sca.recursiveManifestSearch")).thenReturn(Optional.of("true")); - underTest.generateManifestsZip(root, scriptDir, configuration); + underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration); assertThat(logTester.logs(DEBUG)) .contains(argumentOutput); } @Test - void generateZip_whenScaDebugEnabled_shouldCallProcessCorrectly(@TempDir Path rootModuleDir) throws IOException, URISyntaxException { - DefaultInputModule root = new DefaultInputModule( - ProjectDefinition.create().setBaseDir(rootModuleDir.toFile()).setWorkDir(rootModuleDir.toFile())); - - // There is a custom test Bash script available in src/test/resources/org/sonar/scanner/sca that - // will serve as our "CLI". This script will output some messages about what arguments were passed - // to it and will try to generate a zip file in the location the process specifies. This allows us - // to simulate a real CLI call without needing an OS specific CLI executable to run on a real project. - URL scriptUrl = CliServiceTest.class.getResource(SystemUtils.IS_OS_WINDOWS ? "echo_args.bat" : "echo_args.sh"); - assertThat(scriptUrl).isNotNull(); - File scriptDir = new File(scriptUrl.toURI()); + void generateZip_whenScaDebugEnabled_shouldCallProcessCorrectly() throws IOException, URISyntaxException { assertThat(rootModuleDir.resolve("test_file").toFile().createNewFile()).isTrue(); // Set the logging level to info so that we don't automatically set --debug flag @@ -165,9 +142,9 @@ class CliServiceTest { "save-lockfiles", "--zip", "--zip-filename", - root.getWorkDir().resolve("dependency-files.zip").toString(), + rootInputModule.getWorkDir().resolve("dependency-files.zip").toString(), "--directory", - root.getBaseDir().toString(), + rootInputModule.getBaseDir().toString(), "--debug"); String argumentOutput = "Arguments Passed In: " + String.join(" ", args); @@ -176,9 +153,39 @@ class CliServiceTest { when(configuration.get("sonar.sca.recursiveManifestSearch")).thenReturn(Optional.of("true")); when(configuration.getBoolean("sonar.sca.debug")).thenReturn(Optional.of(true)); - underTest.generateManifestsZip(root, scriptDir, configuration); + underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration); assertThat(logTester.logs(INFO)) .contains(argumentOutput); } + + @Test + void generateZip_shouldSendSQEnvVars() throws IOException, URISyntaxException { + // We need to set the logging level to debug in order to be able to view the shell script's output + logTester.setLevel(DEBUG); + + var version = "1.0.0"; + when(server.getVersion()).thenReturn(version); + + DefaultConfiguration configuration = mock(DefaultConfiguration.class); + underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration); + + assertThat(logTester.logs(DEBUG)) + .contains("TIDELIFT_CLI_INSIDE_SCANNER_ENGINE=1") + .contains("TIDELIFT_CLI_SQ_SERVER_VERSION=" + version); + } + + private URL scriptUrl() { + // There is a custom test Bash script available in src/test/resources/org/sonar/scanner/sca that + // will serve as our "CLI". This script will output some messages about what arguments were passed + // to it and will try to generate a zip file in the location the process specifies. This allows us + // to simulate a real CLI call without needing an OS specific CLI executable to run on a real project. + URL scriptUrl = CliServiceTest.class.getResource(SystemUtils.IS_OS_WINDOWS ? "echo_args.bat" : "echo_args.sh"); + assertThat(scriptUrl).isNotNull(); + return scriptUrl; + } + + private File scriptDir() throws URISyntaxException { + return new File(scriptUrl().toURI()); + } } diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scanner/sca/echo_args.bat b/sonar-scanner-engine/src/test/resources/org/sonar/scanner/sca/echo_args.bat index e2a4fd8687b..5677cf5c437 100644 --- a/sonar-scanner-engine/src/test/resources/org/sonar/scanner/sca/echo_args.bat +++ b/sonar-scanner-engine/src/test/resources/org/sonar/scanner/sca/echo_args.bat @@ -20,5 +20,7 @@ goto loop echo TIDELIFT_SKIP_UPDATE_CHECK=%TIDELIFT_SKIP_UPDATE_CHECK% echo TIDELIFT_ALLOW_MANIFEST_FAILURES=%TIDELIFT_ALLOW_MANIFEST_FAILURES% echo TIDELIFT_RECURSIVE_MANIFEST_SEARCH=%TIDELIFT_RECURSIVE_MANIFEST_SEARCH% +echo TIDELIFT_CLI_INSIDE_SCANNER_ENGINE=%TIDELIFT_CLI_INSIDE_SCANNER_ENGINE% +echo TIDELIFT_CLI_SQ_SERVER_VERSION=%TIDELIFT_CLI_SQ_SERVER_VERSION% echo ZIP FILE LOCATION = %FILENAME% echo. > %FILENAME% diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scanner/sca/echo_args.sh b/sonar-scanner-engine/src/test/resources/org/sonar/scanner/sca/echo_args.sh index 3875827056f..881be2eaac5 100755 --- a/sonar-scanner-engine/src/test/resources/org/sonar/scanner/sca/echo_args.sh +++ b/sonar-scanner-engine/src/test/resources/org/sonar/scanner/sca/echo_args.sh @@ -24,6 +24,8 @@ set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters echo "TIDELIFT_SKIP_UPDATE_CHECK=${TIDELIFT_SKIP_UPDATE_CHECK}" echo "TIDELIFT_ALLOW_MANIFEST_FAILURES=${TIDELIFT_ALLOW_MANIFEST_FAILURES}" echo "TIDELIFT_RECURSIVE_MANIFEST_SEARCH=${TIDELIFT_RECURSIVE_MANIFEST_SEARCH}" +echo "TIDELIFT_CLI_INSIDE_SCANNER_ENGINE=${TIDELIFT_CLI_INSIDE_SCANNER_ENGINE}" +echo "TIDELIFT_CLI_SQ_SERVER_VERSION=${TIDELIFT_CLI_SQ_SERVER_VERSION}" # print filename location for debug purposes echo "ZIP FILE LOCATION = ${FILENAME}" |