From eefaa7ece61635202051295cd625b446840cefbb Mon Sep 17 00:00:00 2001 From: Teodora Sandu <81559517+teodora-sandu@users.noreply.github.com> Date: Thu, 18 Apr 2024 14:01:00 +0100 Subject: [PATCH] fix: trigger scan on startup for non-code (#513) --- CHANGELOG.md | 1 + .../io/snyk/plugin/SnykPostStartupActivity.kt | 4 +- src/main/kotlin/io/snyk/plugin/Utils.kt | 5 + .../plugin/extensions/SnykControllerImpl.kt | 2 +- .../plugin/services/SnykTaskQueueService.kt | 18 +++- .../plugin/ui/actions/SnykRunScanAction.kt | 2 +- .../ui/settings/IssueViewOptionsPanel.kt | 4 +- .../ui/toolwindow/SnykToolWindowPanel.kt | 2 +- .../snyk/common/lsp/LanguageServerWrapper.kt | 6 +- .../services/SnykTaskQueueServiceTest.kt | 95 +++++++++++++++++-- .../SnykToolWindowPanelIntegTest.kt | 14 +-- .../ui/toolwindow/SnykToolWindowPanelTest.kt | 4 +- 12 files changed, 128 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1e13e2c0..b6d9dd620 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## [2.7.15] ### Fixed - Re-enable scan results when re-enabling different scan types +- (LS Preview) do not trigger scan on startup for Snyk Code multiple times ## [2.7.14] ### Added diff --git a/src/main/kotlin/io/snyk/plugin/SnykPostStartupActivity.kt b/src/main/kotlin/io/snyk/plugin/SnykPostStartupActivity.kt index 74caf9ac9..8b3845666 100644 --- a/src/main/kotlin/io/snyk/plugin/SnykPostStartupActivity.kt +++ b/src/main/kotlin/io/snyk/plugin/SnykPostStartupActivity.kt @@ -115,8 +115,8 @@ class SnykPostStartupActivity : ProjectActivity { getKubernetesImageCache(project)?.cacheKubernetesFileFromProject() } - if (settings.scanOnSave && isSnykCodeLSEnabled()) { - getSnykTaskQueueService(project)?.scan() + if (settings.scanOnSave && (isSnykCodeLSEnabled() || isSnykOSSLSEnabled() || isSnykIaCLSEnabled())) { + getSnykTaskQueueService(project)?.scan(true) } ExtensionPointsUtil.controllerManager.extensionList.forEach { diff --git a/src/main/kotlin/io/snyk/plugin/Utils.kt b/src/main/kotlin/io/snyk/plugin/Utils.kt index 52b0fa57d..257b00e6f 100644 --- a/src/main/kotlin/io/snyk/plugin/Utils.kt +++ b/src/main/kotlin/io/snyk/plugin/Utils.kt @@ -262,6 +262,11 @@ fun isFileListenerEnabled(): Boolean = pluginSettings().fileListenerEnabled fun isSnykCodeLSEnabled(): Boolean = Registry.`is`("snyk.preview.snyk.code.ls.enabled", true) +fun isSnykOSSLSEnabled(): Boolean = false + +fun isSnykIaCLSEnabled(): Boolean = false + + fun getWaitForResultsTimeout(): Long = Registry.intValue( "snyk.timeout.results.waiting", diff --git a/src/main/kotlin/io/snyk/plugin/extensions/SnykControllerImpl.kt b/src/main/kotlin/io/snyk/plugin/extensions/SnykControllerImpl.kt index 3365b1611..2cd3d15b1 100644 --- a/src/main/kotlin/io/snyk/plugin/extensions/SnykControllerImpl.kt +++ b/src/main/kotlin/io/snyk/plugin/extensions/SnykControllerImpl.kt @@ -13,7 +13,7 @@ class SnykControllerImpl(val project: Project) : SnykController { * scan enqueues a scan of the project for vulnerabilities. */ override fun scan() { - getSnykTaskQueueService(project)?.scan() + getSnykTaskQueueService(project)?.scan(false) } /** diff --git a/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt b/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt index 7eadf0cbc..3766e9222 100644 --- a/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt +++ b/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt @@ -28,6 +28,8 @@ import io.snyk.plugin.isContainerEnabled import io.snyk.plugin.isIacEnabled import io.snyk.plugin.isSnykCodeLSEnabled import io.snyk.plugin.isSnykCodeRunning +import io.snyk.plugin.isSnykIaCLSEnabled +import io.snyk.plugin.isSnykOSSLSEnabled import io.snyk.plugin.net.ClientException import io.snyk.plugin.pluginSettings import io.snyk.plugin.refreshAnnotationsForOpenFiles @@ -98,7 +100,7 @@ class SnykTaskQueueService(val project: Project) { } } - fun scan() { + fun scan(isStartup: Boolean) { taskQueue.run(object : Task.Backgroundable(project, "Snyk: initializing...", true) { override fun run(indicator: ProgressIndicator) { if (!confirmScanningAndSetWorkspaceTrustedStateIfNeeded(project)) return @@ -114,14 +116,22 @@ class SnykTaskQueueService(val project: Project) { if (!isSnykCodeLSEnabled()) { scheduleSnykCodeScan() } else { - LanguageServerWrapper.getInstance().sendScanCommand(project) + // the LS deals with triggering scans at startup + // TODO: Refactor when more than Snyk Code is available in LS for IntelliJ + if (!isStartup) { + LanguageServerWrapper.getInstance().sendScanCommand(project) + } } } if (settings.ossScanEnable) { - scheduleOssScan() + if (!isSnykOSSLSEnabled()) { + scheduleOssScan() + } } if (isIacEnabled() && settings.iacScanEnabled) { - scheduleIacScan() + if (!isSnykIaCLSEnabled()) { + scheduleIacScan() + } } if (isContainerEnabled() && settings.containerScanEnabled) { scheduleContainerScan() diff --git a/src/main/kotlin/io/snyk/plugin/ui/actions/SnykRunScanAction.kt b/src/main/kotlin/io/snyk/plugin/ui/actions/SnykRunScanAction.kt index 99b84aef2..bdfd4b710 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/actions/SnykRunScanAction.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/actions/SnykRunScanAction.kt @@ -19,7 +19,7 @@ import snyk.analytics.AnalysisIsTriggered class SnykRunScanAction : AnAction(AllIcons.Actions.Execute), DumbAware { override fun actionPerformed(actionEvent: AnActionEvent) { - getSnykTaskQueueService(actionEvent.project!!)?.scan() + getSnykTaskQueueService(actionEvent.project!!)?.scan(false) getSnykAnalyticsService().logAnalysisIsTriggered( AnalysisIsTriggered.builder() diff --git a/src/main/kotlin/io/snyk/plugin/ui/settings/IssueViewOptionsPanel.kt b/src/main/kotlin/io/snyk/plugin/ui/settings/IssueViewOptionsPanel.kt index 13151615d..68e121776 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/settings/IssueViewOptionsPanel.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/settings/IssueViewOptionsPanel.kt @@ -28,7 +28,7 @@ class IssueViewOptionsPanel( .actionListener{ event, it -> if (canBeChanged(it, it.isSelected)) { currentOpenIssuesEnabled = it.isSelected - getSnykTaskQueueService(project)?.scan() + getSnykTaskQueueService(project)?.scan(false) } } // bindSelected is needed to trigger apply() on the settings dialog that this panel is rendered in @@ -44,7 +44,7 @@ class IssueViewOptionsPanel( .actionListener{ event, it -> if (canBeChanged(it, it.isSelected)) { currentIgnoredIssuesEnabled = it.isSelected - getSnykTaskQueueService(project)?.scan() + getSnykTaskQueueService(project)?.scan(false) } } .bindSelected(settings::ignoredIssuesEnabled) diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanel.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanel.kt index 8c0124d69..c8044e0f6 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanel.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanel.kt @@ -521,7 +521,7 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable { .build() ) - getSnykTaskQueueService(project)?.scan() + getSnykTaskQueueService(project)?.scan(false) } fun displayAuthPanel() { diff --git a/src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt b/src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt index f257d03b5..7f25161e5 100644 --- a/src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt +++ b/src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt @@ -12,6 +12,8 @@ import io.snyk.plugin.getSnykTaskQueueService import io.snyk.plugin.getUserAgentString import io.snyk.plugin.isCliInstalled import io.snyk.plugin.isSnykCodeLSEnabled +import io.snyk.plugin.isSnykIaCLSEnabled +import io.snyk.plugin.isSnykOSSLSEnabled import io.snyk.plugin.pluginSettings import io.snyk.plugin.toLanguageServerURL import io.snyk.plugin.ui.SnykBalloonNotificationHelper @@ -306,10 +308,10 @@ class LanguageServerWrapper( fun getSettings(): LanguageServerSettings { val ps = pluginSettings() return LanguageServerSettings( - activateSnykOpenSource = false.toString(), + activateSnykOpenSource = isSnykOSSLSEnabled().toString(), activateSnykCodeSecurity = (isSnykCodeLSEnabled() && ps.snykCodeSecurityIssuesScanEnable).toString(), activateSnykCodeQuality = (isSnykCodeLSEnabled() && ps.snykCodeQualityIssuesScanEnable).toString(), - activateSnykIac = false.toString(), + activateSnykIac = isSnykIaCLSEnabled().toString(), organization = ps.organization, insecure = ps.ignoreUnknownCA.toString(), endpoint = getEndpointUrl(), diff --git a/src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt b/src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt index a309bf1c2..66b59e97c 100644 --- a/src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt +++ b/src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt @@ -2,6 +2,7 @@ package io.snyk.plugin.services import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.service +import com.intellij.openapi.util.registry.Registry import com.intellij.testFramework.LightPlatformTestCase import com.intellij.testFramework.PlatformTestUtil import com.intellij.testFramework.replaceService @@ -12,12 +13,14 @@ import io.mockk.mockkStatic import io.mockk.spyk import io.mockk.unmockkAll import io.mockk.verify +import io.snyk.plugin.DEFAULT_TIMEOUT_FOR_SCAN_WAITING_MS import io.snyk.plugin.getCliFile import io.snyk.plugin.getContainerService import io.snyk.plugin.getIacService import io.snyk.plugin.getOssService import io.snyk.plugin.getSnykCachedResults import io.snyk.plugin.getSnykCliDownloaderService +import io.snyk.plugin.getSnykCode import io.snyk.plugin.isCliInstalled import io.snyk.plugin.isContainerEnabled import io.snyk.plugin.isIacEnabled @@ -92,7 +95,7 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { setupDummyCliFile() val snykTaskQueueService = project.service() - snykTaskQueueService.scan() + snykTaskQueueService.scan(false) PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() assertTrue(snykTaskQueueService.getTaskQueue().isEmpty) @@ -103,13 +106,12 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { assertTrue(snykTaskQueueService.getTaskQueue().isEmpty) assertNull(snykTaskQueueService.ossScanProgressIndicator) } - fun testCliDownloadBeforeScanIfNeeded() { setupAppSettingsForDownloadTests() every { isCliInstalled() } returns true val snykTaskQueueService = project.service() - snykTaskQueueService.scan() + snykTaskQueueService.scan(false) PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() assertTrue(snykTaskQueueService.getTaskQueue().isEmpty) @@ -123,7 +125,7 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { val snykTaskQueueService = project.service() every { isCliInstalled() } returns true - snykTaskQueueService.scan() + snykTaskQueueService.scan(false) PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() assertTrue(snykTaskQueueService.getTaskQueue().isEmpty) @@ -169,7 +171,7 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { settings.snykCodeQualityIssuesScanEnable = true settings.token = "testToken" - snykTaskQueueService.scan() + snykTaskQueueService.scan(false) PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() verify { snykApiServiceMock.getSastSettings() } @@ -202,7 +204,7 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { every { isCliInstalled() } returns true every { getIacService(project)?.scan() } returns fakeIacResult - snykTaskQueueService.scan() + snykTaskQueueService.scan(false) PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() assertEquals(fakeIacResult, getSnykCachedResults(project)?.currentIacResult) @@ -225,11 +227,90 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { getSnykCachedResults(project)?.currentContainerResult = null - snykTaskQueueService.scan() + snykTaskQueueService.scan(false) + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() + + await().atMost(2, TimeUnit.SECONDS).until { + getSnykCachedResults(project)?.currentContainerResult != null + } + } + + fun testSnykTaskQueueServiceScanCodeOnStartupAndFailsWhenLS() { + val registryValue = Registry.get("snyk.preview.snyk.code.ls.enabled") + registryValue.setValue(false) + + val snykTaskQueueService = project.service() + val settings = pluginSettings() + settings.ossScanEnable = true + settings.snykCodeSecurityIssuesScanEnable = true + settings.snykCodeQualityIssuesScanEnable = true + settings.token = "testToken" + settings.iacScanEnabled = true + settings.containerScanEnabled = true + getSnykCachedResults(project)?.currentOssResults = null + getSnykCachedResults(project)?.currentIacResult = null + getSnykCachedResults(project)?.currentContainerResult = null + getSnykCachedResults(project)?.currentSnykCodeResults = null + + val fakeOSSResult = OssResult(emptyList()) + val fakeIacResult = IacResult(emptyList()) + val fakeContainerResult = ContainerResult(emptyList()) + + mockkStatic("io.snyk.plugin.UtilsKt") + every { isIacEnabled() } returns true + every { isCliInstalled() } returns true + every { getSnykCode(project)?.scan() } returns null + every { getOssService(project)?.scan() } returns fakeOSSResult + every { getIacService(project)?.scan() } returns fakeIacResult + every { getContainerService(project)?.scan() } returns fakeContainerResult + + snykTaskQueueService.scan(true) PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() + assertEquals(fakeOSSResult, getSnykCachedResults(project)?.currentOssResults) + assertEquals(fakeIacResult, getSnykCachedResults(project)?.currentIacResult) await().atMost(2, TimeUnit.SECONDS).until { getSnykCachedResults(project)?.currentContainerResult != null } + verify(exactly = 1) { getSnykCode(project)?.scan() } } + fun testSnykTaskQueueServiceDoesNotScanCodeOnStartupWhenLS() { + val registryValue = Registry.get("snyk.preview.snyk.code.ls.enabled") + registryValue.setValue(true) + + val snykTaskQueueService = project.service() + val settings = pluginSettings() + settings.ossScanEnable = true + settings.snykCodeSecurityIssuesScanEnable = true + settings.snykCodeQualityIssuesScanEnable = true + settings.token = "testToken" + settings.iacScanEnabled = true + settings.containerScanEnabled = true + getSnykCachedResults(project)?.currentOssResults = null + getSnykCachedResults(project)?.currentIacResult = null + getSnykCachedResults(project)?.currentContainerResult = null + getSnykCachedResults(project)?.currentSnykCodeResults = null + + val fakeOSSResult = OssResult(emptyList()) + val fakeIacResult = IacResult(emptyList()) + val fakeContainerResult = ContainerResult(emptyList()) + + mockkStatic("io.snyk.plugin.UtilsKt") + every { isIacEnabled() } returns true + every { isCliInstalled() } returns true + every { getOssService(project)?.scan() } returns fakeOSSResult + every { getIacService(project)?.scan() } returns fakeIacResult + every { getContainerService(project)?.scan() } returns fakeContainerResult + + snykTaskQueueService.scan(true) + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() + + assertEquals(fakeOSSResult, getSnykCachedResults(project)?.currentOssResults) + assertEquals(fakeIacResult, getSnykCachedResults(project)?.currentIacResult) + await().atMost(2, TimeUnit.SECONDS).until { + getSnykCachedResults(project)?.currentContainerResult != null + } + verify(exactly = 0) { getSnykCode(project)?.scan() } + } + } diff --git a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelIntegTest.kt b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelIntegTest.kt index a58cfd388..1b0f7087a 100644 --- a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelIntegTest.kt +++ b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelIntegTest.kt @@ -177,7 +177,7 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { } returns (iacGoofJson) getIacService(project)?.setConsoleCommandRunner(mockRunner) - project.service().scan() + project.service().scan(false) PlatformTestUtil.waitWhileBusy(toolWindowPanel.getTree()) } @@ -598,7 +598,7 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { every { getIacService(project)?.scan() } returns iacResultWithError // actual test run - project.service().scan() + project.service().scan(false) PlatformTestUtil.waitWhileBusy(toolWindowPanel.getTree()) @@ -704,7 +704,7 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { setUpContainerTest(containerResultWithError) // actual test run - project.service().scan() + project.service().scan(false) PlatformTestUtil.waitWhileBusy(toolWindowPanel.getTree()) @@ -731,7 +731,7 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { setUpContainerTest(fakeContainerResult) // actual test run - project.service().scan() + project.service().scan(false) PlatformTestUtil.waitWhileBusy(toolWindowPanel.getTree()) // Assertions @@ -765,7 +765,7 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { mockkObject(SnykBalloonNotificationHelper) // actual test run - project.service().scan() + project.service().scan(false) PlatformTestUtil.waitWhileBusy(toolWindowPanel.getTree()) // Assertions @@ -797,7 +797,7 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { setUpContainerTest(fakeContainerResult) // actual test run - project.service().scan() + project.service().scan(false) PlatformTestUtil.waitWhileBusy(toolWindowPanel.getTree()) // Assertions @@ -879,7 +879,7 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { setUpContainerTest(containerResult) // actual test run - project.service().scan() + project.service().scan(false) PlatformTestUtil.waitWhileBusy(toolWindowPanel.getTree()) return containerResult diff --git a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelTest.kt b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelTest.kt index 14e982583..f8c7e4e4e 100644 --- a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelTest.kt +++ b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelTest.kt @@ -126,7 +126,7 @@ class SnykToolWindowPanelTest : LightPlatform4TestCase() { true, LocalCodeEngine(false, "", false), ) - justRun { taskQueueService.scan() } + justRun { taskQueueService.scan(false) } cut = SnykToolWindowPanel(project) @@ -135,7 +135,7 @@ class SnykToolWindowPanelTest : LightPlatform4TestCase() { assertNotNull(descriptionPanel) assertEquals(findOnePixelSplitter(vulnerabilityTree), descriptionPanel!!.parent) - verify(exactly = 1) { taskQueueService.scan() } + verify(exactly = 1) { taskQueueService.scan(false) } verify(exactly = 1) { analyticsService.logAnalysisIsTriggered(any()) } }