diff --git a/.circleci/config.yml b/.circleci/config.yml index d2916cc6fd6..de6f833c8e8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,11 +42,23 @@ commands: description: Install maven steps: - run: choco install maven + install_sbt_windows: + description: Install SBT + steps: + - run: choco install sbt install_maven_unix: description: Install maven steps: - run: sudo apt update - run: sudo apt install -y maven + install_sbt_unix: + description: Install SBT + steps: + - run: sudo apt install apt-transport-https ca-certificates + - run: echo "deb https://dl.bintray.com/sbt/debian /" | sudo tee -a /etc/apt/sources.list.d/sbt.list + - run: curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | sudo apt-key add + - run: sudo apt update + - run: sudo apt install -y sbt install_node_npm: description: Install correct Node version parameters: @@ -105,6 +117,7 @@ jobs: steps: - run: git config --global core.autocrlf false - install_maven_windows + - install_sbt_windows - install_node_npm: node_version: << parameters.node_version >> - show_node_version @@ -129,6 +142,7 @@ jobs: - image: circleci/node:<< parameters.node_version >> steps: - install_maven_unix + - install_sbt_unix - show_node_version - checkout - attach_workspace: diff --git a/src/lib/detect.ts b/src/lib/detect.ts index 4605d5f0468..187d4cc1628 100644 --- a/src/lib/detect.ts +++ b/src/lib/detect.ts @@ -51,6 +51,7 @@ export const AUTO_DETECTABLE_FILES: string[] = [ 'vendor.json', 'Pipfile', 'requirements.txt', + 'build.sbt', ]; // when file is specified with --file, we look it up here diff --git a/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts b/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts index 4bb744822b1..868c023cd4e 100644 --- a/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts +++ b/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts @@ -76,6 +76,7 @@ export const AllProjectsTests: AcceptanceTests = { t.ok(loadPlugin.withArgs('nuget').calledOnce, 'calls nuget plugin'); t.ok(loadPlugin.withArgs('paket').calledOnce, 'calls nuget plugin'); t.ok(loadPlugin.withArgs('pip').calledOnce, 'calls pip plugin'); + t.ok(loadPlugin.withArgs('sbt').calledOnce, 'calls sbt plugin'); t.match( result, @@ -87,23 +88,25 @@ export const AllProjectsTests: AcceptanceTests = { t.match(result, 'nuget/some/project-id', 'nuget project in output'); t.match(result, 'paket/some/project-id', 'paket project in output'); t.match(result, 'pip/some/project-id', 'python project in output '); + t.match(result, 'sbt/graph/some/project-id', 'sbt project in output '); // Pop all calls to server and filter out calls to `featureFlag` endpoint const requests = params.server - .popRequests(7) + .popRequests(9) .filter((req) => req.url.includes('/monitor/')); - t.equal(requests.length, 6, 'correct amount of monitor requests'); + t.equal(requests.length, 7, 'correct amount of monitor requests'); const pluginsWithoutTargetFileInBody = [ 'snyk-nodejs-lockfile-parser', 'bundled:maven', 'bundled:rubygems', + 'snyk:sbt', ]; requests.forEach((req) => { t.match( req.url, - /\/api\/v1\/monitor\/(npm\/graph|rubygems|maven|nuget|paket|pip)/, + /\/api\/v1\/monitor\/(npm\/graph|rubygems|maven|nuget|paket|pip|sbt)/, 'puts at correct url', ); if (pluginsWithoutTargetFileInBody.includes(req.body.meta.pluginName)) { @@ -244,7 +247,7 @@ export const AllProjectsTests: AcceptanceTests = { // Pop all calls to server and filter out calls to `featureFlag` endpoint const requests = params.server - .popRequests(7) + .popRequests(9) .filter((req) => req.url.includes('/monitor/')); // find each type of request const rubyAll = requests.find((req) => req.url.indexOf('rubygems') > -1); @@ -253,6 +256,7 @@ export const AllProjectsTests: AcceptanceTests = { const nugetAll = requests.find((req) => req.url.indexOf('nuget') > -1); const paketAll = requests.find((req) => req.url.indexOf('paket') > -1); const mavenAll = requests.find((req) => req.url.indexOf('maven') > -1); + const sbtAll = requests.find((req) => req.url.indexOf('sbt') > -1); await params.cli.monitor('mono-repo-project', { file: 'Gemfile.lock', @@ -287,6 +291,11 @@ export const AllProjectsTests: AcceptanceTests = { }); const mavenFile = params.server.popRequest(); + await params.cli.monitor('mono-repo-project', { + file: 'build.sbt', + }); + const sbtFile = params.server.popRequest(); + t.same( rubyAll.body, rubyFile.body, @@ -322,6 +331,12 @@ export const AllProjectsTests: AcceptanceTests = { mavenFile.body, 'same body for --all-projects and --file=pom.xml', ); + + t.same( + sbtAll.body, + sbtFile.body, + 'same body for --all-projects and --file=build.sbt', + ); }, '`monitor composer-app with --all-projects sends same payload as --file`': ( params, @@ -379,15 +394,15 @@ export const AllProjectsTests: AcceptanceTests = { detectionDepth: 1, }); - const requests = params.server.popRequests(7); - const ffRequests = params.server.popRequests(3); + const requests = params.server.popRequests(9); + const ffRequests = params.server.popRequests(4); t.equal( ffRequests.every((req) => req.url.includes('experimentalDepGraph')), true, 'all left requests are feature flag requests', ); - t.equal(requests.length, 7, 'sends expected # requests'); // extra feature-flags request + t.equal(requests.length, 9, 'sends expected # requests'); // extra feature-flags request t.equal( params.server._reqLog.length, 0, @@ -397,7 +412,7 @@ export const AllProjectsTests: AcceptanceTests = { const jsonResponse = JSON.parse(response); t.equal( jsonResponse.length, - 6, + 7, 'json response array has expected # elements', ); diff --git a/test/acceptance/cli-test/cli-test.all-projects.spec.ts b/test/acceptance/cli-test/cli-test.all-projects.spec.ts index cdaa3022ce2..82ce39dfeab 100644 --- a/test/acceptance/cli-test/cli-test.all-projects.spec.ts +++ b/test/acceptance/cli-test/cli-test.all-projects.spec.ts @@ -74,7 +74,7 @@ export const AllProjectsTests: AcceptanceTests = { ) => async (t) => { utils.chdirWorkspaces(); - // mock python plugin becuase CI tooling doesn't have pipenv installed + // mock python plugin because CI tooling doesn't have pipenv installed const mockPlugin = { async inspect() { return { @@ -102,8 +102,9 @@ export const AllProjectsTests: AcceptanceTests = { t.ok(loadPlugin.withArgs('nuget').calledOnce, 'calls nuget plugin'); t.ok(loadPlugin.withArgs('paket').calledOnce, 'calls nuget plugin'); t.ok(loadPlugin.withArgs('pip').calledOnce, 'calls pip plugin'); + t.ok(loadPlugin.withArgs('sbt').calledOnce, 'calls pip plugin'); - params.server.popRequests(6).forEach((req) => { + params.server.popRequests(7).forEach((req) => { t.equal(req.method, 'POST', 'makes POST request'); t.equal( req.headers['x-snyk-cli-version'], @@ -114,7 +115,7 @@ export const AllProjectsTests: AcceptanceTests = { t.ok(req.body.depGraph, 'body contains depGraph'); t.match( req.body.depGraph.pkgManager.name, - /(npm|rubygems|maven|nuget|paket|pip)/, + /(npm|rubygems|maven|nuget|paket|pip|sbt)/, 'depGraph has package manager', ); }); @@ -160,6 +161,11 @@ export const AllProjectsTests: AcceptanceTests = { 'Target file: Pipfile', 'contains target file Pipfile', ); + t.match( + result.getDisplayResults(), + 'Target file: build.sbt', + 'contains target file build.sbt', + ); }, '`test mono-repo-project --all-projects --detection-depth=3`': ( @@ -219,9 +225,10 @@ export const AllProjectsTests: AcceptanceTests = { t.ok(loadPlugin.withArgs('maven').calledOnce, 'calls maven plugin'); t.ok(loadPlugin.withArgs('nuget').calledOnce, 'calls nuget plugin'); t.ok(loadPlugin.withArgs('paket').calledOnce, 'calls nuget plugin'); + t.ok(loadPlugin.withArgs('sbt').calledOnce, 'calls sbt plugin'); t.equals(loadPlugin.withArgs('pip').callCount, 2, 'calls pip plugin'); - params.server.popRequests(9).forEach((req) => { + params.server.popRequests(10).forEach((req) => { t.equal(req.method, 'POST', 'makes POST request'); t.equal( req.headers['x-snyk-cli-version'], @@ -232,7 +239,7 @@ export const AllProjectsTests: AcceptanceTests = { t.ok(req.body.depGraph, 'body contains depGraph'); t.match( req.body.depGraph.pkgManager.name, - /(npm|rubygems|maven|nuget|paket|pip)/, + /(npm|rubygems|maven|nuget|paket|pip|sbt)/, 'depGraph has package manager', ); }); @@ -333,6 +340,13 @@ export const AllProjectsTests: AcceptanceTests = { `Target file: python-app-with-req-file${path.sep}requirements.txt`, `contains target file python-app-with-req-file${path.sep}requirements.txt`, ); + + // sbt + t.match( + result.getDisplayResults(), + 'Target file: build.sbt', + 'contains target file build.sbt', + ); }, '`test mono-repo-project --all-projects and --file payloads are the same`': ( @@ -363,7 +377,7 @@ export const AllProjectsTests: AcceptanceTests = { detectionDepth: 1, }); - const requests = params.server.popRequests(6); + const requests = params.server.popRequests(7); // find each type of request const rubyAll = requests.find( @@ -384,6 +398,9 @@ export const AllProjectsTests: AcceptanceTests = { const mavenAll = requests.find( (req) => req.body.depGraph.pkgManager.name === 'maven', ); + const sbtAll = requests.find( + (req) => req.body.depGraph.pkgManager.name === 'sbt', + ); await params.cli.test('mono-repo-project', { file: 'Gemfile.lock', @@ -415,6 +432,11 @@ export const AllProjectsTests: AcceptanceTests = { }); const mavenFile = params.server.popRequest(); + await params.cli.test('mono-repo-project', { + file: 'build.sbt', + }); + const sbtFile = params.server.popRequest(); + t.same( pipAll.body, pipFile.body, @@ -449,6 +471,12 @@ export const AllProjectsTests: AcceptanceTests = { mavenFile.body, 'Same body for --all-projects and --file=pom.xml', ); + + t.same( + sbtAll.body, + sbtFile.body, + 'Same body for --all-projects and --file=build.sbt', + ); }, '`test maven-multi-app --all-projects --detection-depth=2`': ( diff --git a/test/acceptance/workspaces/mono-repo-project/.gitignore b/test/acceptance/workspaces/mono-repo-project/.gitignore new file mode 100644 index 00000000000..9c108a10687 --- /dev/null +++ b/test/acceptance/workspaces/mono-repo-project/.gitignore @@ -0,0 +1,2 @@ +target +project/target diff --git a/test/acceptance/workspaces/mono-repo-project/build.sbt b/test/acceptance/workspaces/mono-repo-project/build.sbt new file mode 100644 index 00000000000..41ac8a4a00d --- /dev/null +++ b/test/acceptance/workspaces/mono-repo-project/build.sbt @@ -0,0 +1,9 @@ +name := "small-app" + +version := "1.0-SNAPSHOT" + +scalaVersion := "2.10.4" + +libraryDependencies ++= Seq( + "org.apache.struts" % "struts2-core" % "2.3.20" +) diff --git a/test/acceptance/workspaces/mono-repo-project/project/build.properties b/test/acceptance/workspaces/mono-repo-project/project/build.properties new file mode 100644 index 00000000000..c0bab04941d --- /dev/null +++ b/test/acceptance/workspaces/mono-repo-project/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.8 diff --git a/test/acceptance/workspaces/mono-repo-project/project/plugins.sbt b/test/acceptance/workspaces/mono-repo-project/project/plugins.sbt new file mode 100644 index 00000000000..72331581261 --- /dev/null +++ b/test/acceptance/workspaces/mono-repo-project/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.0")