-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: snyk controller extension point
Add an extension point to allow third-party plugins to interact with Snyk in the IDE.
- Loading branch information
Showing
9 changed files
with
305 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# Snyk Extensions | ||
|
||
This plugin offers an extension point for integrating Snyk with other Jetbrains IDE Plugins. | ||
|
||
## What is the Snyk Controller? | ||
|
||
The Snyk Controller is, put simply, a way to control Snyk from other plugins. | ||
|
||
## Why would I use the Snyk Controller extension? | ||
|
||
There are a few of use cases for the Snyk controller: | ||
|
||
### Initiating a Snyk scan | ||
|
||
There may be situations in which a plugin wants to initiate a security scan, especially at the end of a workflow which | ||
introduces changes to the project source code, manifest dependencies, OCI container builds, infrastructure files, | ||
etc. -- anything Snyk can scan. | ||
|
||
### Determining whether Snyk is authenticated to a user | ||
|
||
It might be useful to know whether the user has authenticated with Snyk; is Snyk ready to run scans? | ||
|
||
## How do I use the extension in my plugin? | ||
|
||
### Add a dependency on this plugin to your plugin's `plugin.xml` file. | ||
|
||
Release >= 2.7.0 provides the extension point. | ||
|
||
```xml | ||
<depends>io.snyk.snyk-intellij-plugin</depends> | ||
``` | ||
|
||
### Declare the extension | ||
|
||
Add an extension for `io.snyk.snyk-intellij-plugin.controllerManager` to your plugin's `plugin.xml` file. | ||
|
||
```xml | ||
<extensions defaultExtensionNs="io.snyk.snyk-intellij-plugin"> | ||
<controllerManager implementation="com.example.demointellij.MySnykControllerManager"/> | ||
</extensions> | ||
``` | ||
|
||
### Optional dependency | ||
|
||
The dependency on Snyk can also be optional: | ||
|
||
```xml | ||
<depends optional="true" config-file="optional/withSnyk.xml">io.snyk.snyk-intellij-plugin</depends> | ||
``` | ||
|
||
Declare the controller extension in the dependency's config file, located relative to the `plugin.xml`: | ||
|
||
```xml | ||
<!-- Contents of optional/withSnyk.xml --> | ||
<idea-plugin> | ||
<extensions defaultExtensionNs="io.snyk.snyk-intellij-plugin"> | ||
<controllerManager implementation="com.example.demointellij.HelloWorldControllerManager" /> | ||
</extensions> | ||
</idea-plugin> | ||
``` | ||
|
||
### Implement the controller manager interface | ||
|
||
The manager will be instantiated and passed an instance of the controller when the Snyk plugin is initialized. | ||
|
||
```kotlin | ||
package com.example.demointellij | ||
|
||
import io.snyk.plugin.extensions.SnykController | ||
import io.snyk.plugin.extensions.SnykControllerManager | ||
|
||
class HelloWorldControllerManager : SnykControllerManager { | ||
override fun register(controller: SnykController) { | ||
Utils().getSnykControllerService().setController(controller) | ||
} | ||
} | ||
``` | ||
|
||
Snyk recommends building a [service](https://plugins.jetbrains.com/docs/intellij/plugin-services.html) to receive the | ||
controller instance and provide it to other parts of your plugin. | ||
|
||
### Use the controller in your plugin | ||
|
||
See [SnykController](https://github.com/snyk/snyk-intellij-plugin/blob/main/src/main/kotlin/io/snyk/plugin/extensions/SnykController.kt) | ||
for the current Snyk methods supported. | ||
|
||
#### Initiating a scan | ||
|
||
```kotlin | ||
Utils().getSnykControllerService().getController()?.scan() | ||
``` | ||
|
||
## What compatibility guarantees are there, for consumers of this extension? | ||
|
||
Our [semantic version](https://semver.org/) releases indicate the compatibility of the extension API with respect to | ||
past releases. | ||
|
||
With a minor version increment, new methods may be added to interfaces, but existing methods will not be removed or | ||
their prototypes changed. | ||
|
||
With a major version increment, there are no compatibility guarantees. Snyk suggests checking the release notes, source | ||
changes, and compatibility testing before upgrading. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
src/main/kotlin/io/snyk/plugin/extensions/SnykController.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package io.snyk.plugin.extensions | ||
|
||
/** | ||
* SnykController is used by third-party plugins to interact with the Snyk plugin. | ||
*/ | ||
interface SnykController { | ||
|
||
/** | ||
* scan enqueues a scan of the project for vulnerabilities. | ||
*/ | ||
fun scan() | ||
|
||
/** | ||
* userId returns the current authenticated Snyk user's ID. | ||
* | ||
* If no user is authenticated, this will return null. | ||
*/ | ||
fun userId(): String? | ||
} |
27 changes: 27 additions & 0 deletions
27
src/main/kotlin/io/snyk/plugin/extensions/SnykControllerImpl.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package io.snyk.plugin.extensions | ||
|
||
import com.intellij.openapi.project.Project | ||
import io.snyk.plugin.getSnykApiService | ||
import io.snyk.plugin.getSnykTaskQueueService | ||
|
||
/** | ||
* SnykController is used by third-party plugins to interact with the Snyk plugin. | ||
*/ | ||
class SnykControllerImpl(val project: Project) : SnykController { | ||
|
||
/** | ||
* scan enqueues a scan of the project for vulnerabilities. | ||
*/ | ||
override fun scan() { | ||
getSnykTaskQueueService(project)?.scan() | ||
} | ||
|
||
/** | ||
* userId returns the current authenticated Snyk user's ID. | ||
* | ||
* If no user is authenticated, this will return null. | ||
*/ | ||
override fun userId(): String? { | ||
return getSnykApiService().userId | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
src/main/kotlin/io/snyk/plugin/extensions/SnykControllerManager.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package io.snyk.plugin.extensions | ||
|
||
/** | ||
* SnykControllerManager is the extension point interface | ||
* which other plugins can implement in order to integrate with Snyk. | ||
*/ | ||
interface SnykControllerManager { | ||
|
||
/** | ||
* register is called by the Snyk IntelliJ plugin to pass the #SnykController to extension point implementers. | ||
*/ | ||
fun register(controller: SnykController) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
src/test/kotlin/io/snyk/plugin/extensions/SnykControllerImplTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package io.snyk.plugin.extensions | ||
|
||
import com.intellij.openapi.application.ApplicationManager | ||
import com.intellij.openapi.components.service | ||
import com.intellij.testFramework.LightPlatformTestCase | ||
import com.intellij.testFramework.PlatformTestUtil | ||
import com.intellij.testFramework.replaceService | ||
import io.mockk.every | ||
import io.mockk.mockk | ||
import io.mockk.mockkStatic | ||
import io.mockk.spyk | ||
import io.mockk.unmockkAll | ||
import io.mockk.verify | ||
import io.snyk.plugin.extensions.SnykControllerImpl | ||
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.isCliInstalled | ||
import io.snyk.plugin.isContainerEnabled | ||
import io.snyk.plugin.isIacEnabled | ||
import io.snyk.plugin.net.CliConfigSettings | ||
import io.snyk.plugin.net.LocalCodeEngine | ||
import io.snyk.plugin.pluginSettings | ||
import io.snyk.plugin.removeDummyCliFile | ||
import io.snyk.plugin.resetSettings | ||
import io.snyk.plugin.services.SnykApiService | ||
import io.snyk.plugin.services.SnykTaskQueueService | ||
import io.snyk.plugin.services.download.CliDownloader | ||
import io.snyk.plugin.services.download.LatestReleaseInfo | ||
import io.snyk.plugin.services.download.SnykCliDownloaderService | ||
import io.snyk.plugin.setupDummyCliFile | ||
import org.awaitility.Awaitility.await | ||
import snyk.container.ContainerResult | ||
import snyk.iac.IacResult | ||
import snyk.oss.OssResult | ||
import snyk.oss.OssService | ||
import snyk.trust.confirmScanningAndSetWorkspaceTrustedStateIfNeeded | ||
import java.util.concurrent.TimeUnit | ||
|
||
@Suppress("FunctionName") | ||
class SnykControllerImplTest : LightPlatformTestCase() { | ||
|
||
private lateinit var ossServiceMock: OssService | ||
private lateinit var downloaderServiceMock: SnykCliDownloaderService | ||
|
||
override fun setUp() { | ||
super.setUp() | ||
unmockkAll() | ||
resetSettings(project) | ||
ossServiceMock = mockk(relaxed = true) | ||
project.replaceService(OssService::class.java, ossServiceMock, project) | ||
mockkStatic("io.snyk.plugin.UtilsKt") | ||
mockkStatic("snyk.trust.TrustedProjectsKt") | ||
downloaderServiceMock = spyk(SnykCliDownloaderService()) | ||
every { downloaderServiceMock.requestLatestReleasesInformation() } returns LatestReleaseInfo( | ||
"http://testUrl", | ||
"testReleaseInfo", | ||
"testTag" | ||
) | ||
every { getSnykCliDownloaderService() } returns downloaderServiceMock | ||
every { downloaderServiceMock.isFourDaysPassedSinceLastCheck() } returns false | ||
every { confirmScanningAndSetWorkspaceTrustedStateIfNeeded(any(), any()) } returns true | ||
} | ||
|
||
override fun tearDown() { | ||
unmockkAll() | ||
resetSettings(project) | ||
removeDummyCliFile() | ||
super.tearDown() | ||
} | ||
|
||
fun testControllerCanTriggerScan() { | ||
mockkStatic("io.snyk.plugin.UtilsKt") | ||
every { isCliInstalled() } returns true | ||
val fakeResult = OssResult(emptyList()) | ||
every { getOssService(project)?.scan() } returns fakeResult | ||
|
||
val settings = pluginSettings() | ||
settings.ossScanEnable = true | ||
settings.snykCodeSecurityIssuesScanEnable = false | ||
settings.snykCodeQualityIssuesScanEnable = false | ||
settings.iacScanEnabled = false | ||
settings.containerScanEnabled = false | ||
|
||
getSnykCachedResults(project)?.currentContainerResult = null | ||
|
||
val controller = SnykControllerImpl(project) | ||
controller.scan() | ||
|
||
PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() | ||
|
||
await().atMost(2, TimeUnit.SECONDS).until { | ||
getSnykCachedResults(project)?.currentOssResults != null | ||
} | ||
} | ||
} |