From 4199e21d19a6b98a7935b628cdbc1de50094ae38 Mon Sep 17 00:00:00 2001 From: psmf22 <117990501+psmf22@users.noreply.github.com> Date: Mon, 12 Feb 2024 10:53:37 +0100 Subject: [PATCH] Scan command writejson (#193) * Delete supported_formats.json * add json-path parameter * update flag description * added test --------- Co-authored-by: Michael Chernov <4ernovm@gmail.com> --- internal/cmd/scan/scan.go | 4 ++ internal/scan/scanner.go | 11 ++++++ internal/scan/scanner_test.go | 72 +++++++++++++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/internal/cmd/scan/scan.go b/internal/cmd/scan/scan.go index 491f8b38..736fd846 100644 --- a/internal/cmd/scan/scan.go +++ b/internal/cmd/scan/scan.go @@ -19,6 +19,7 @@ var branchName string var commitAuthor string var repositoryUrl string var integrationName string +var jsonFilePath string var exclusions = file.Exclusions() var verbose bool var regenerate int @@ -47,6 +48,7 @@ const ( CallGraphUploadTimeoutFlag = "callgraph-upload-timeout" CallGraphGenerateTimeoutFlag = "callgraph-generate-timeout" NpmPreferredFlag = "prefer-npm" + JsonFilePathFlag = "json-path" ) var scanCmdError error @@ -74,6 +76,7 @@ If the given path contains a git repository all flags but "integration" will be "CLI", `name of integration used to trigger scan. For example "GitHub Actions"`, ) + cmd.Flags().StringVarP(&jsonFilePath, JsonFilePathFlag, "j", "", "write upload result as json to provided path") fileExclusionExample := filepath.Join("*", "**.lock") dirExclusionExample := filepath.Join("**", "node_modules", "**") @@ -159,6 +162,7 @@ func RunE(s *scan.IScanner) func(_ *cobra.Command, args []string) error { CommitAuthor: viper.GetString(CommitAuthorFlag), RepositoryUrl: viper.GetString(RepositoryUrlFlag), IntegrationName: viper.GetString(IntegrationFlag), + JsonFilePath: viper.GetString(JsonFilePathFlag), NpmPreferred: viper.GetBool(NpmPreferredFlag), PassOnTimeOut: viper.GetBool(PassOnTimeOut), CallGraph: viper.GetBool(CallGraphFlag), diff --git a/internal/scan/scanner.go b/internal/scan/scanner.go index c95298a0..e6555dba 100644 --- a/internal/scan/scanner.go +++ b/internal/scan/scanner.go @@ -1,6 +1,7 @@ package scan import ( + "encoding/json" "errors" "fmt" "os" @@ -55,6 +56,7 @@ type DebrickedOptions struct { CommitAuthor string RepositoryUrl string IntegrationName string + JsonFilePath string NpmPreferred bool PassOnTimeOut bool CallGraphUploadTimeout int @@ -118,6 +120,8 @@ func (dScanner *DebrickedScanner) Scan(o IOptions) error { return nil } + WriteApiReplyToJsonFile(dOptions, result) + fmt.Printf("\n%d vulnerabilities found\n", result.VulnerabilitiesFound) fmt.Println("") failPipeline := false @@ -259,3 +263,10 @@ func MapEnvToOptions(o *DebrickedOptions, env env.Env) { o.Path = env.Filepath } } + +func WriteApiReplyToJsonFile(options DebrickedOptions, result *upload.UploadResult) { + if options.JsonFilePath != "" { + file, _ := json.MarshalIndent(result, "", " ") + _ = os.WriteFile(options.JsonFilePath, file, 0600) + } +} diff --git a/internal/scan/scanner_test.go b/internal/scan/scanner_test.go index 4fe695ae..8aaea036 100644 --- a/internal/scan/scanner_test.go +++ b/internal/scan/scanner_test.go @@ -37,6 +37,8 @@ import ( "github.com/stretchr/testify/assert" ) +const windowsOS = "windows" + var testdataNpm = filepath.Join("testdata", "npm") var ciService ci.IService = ci.NewService([]ci.ICi{ @@ -64,7 +66,7 @@ func TestNewDebrickedScanner(t *testing.T) { } func TestScan(t *testing.T) { - if runtime.GOOS == "windows" { + if runtime.GOOS == windowsOS { t.Skipf("TestScan is skipped due to Windows env") } clientMock := testdata.NewDebClientMock() @@ -109,7 +111,6 @@ func TestScan(t *testing.T) { outputAssertions := []string{ "Working directory: /", - "cli/internal/scan", "Successfully uploaded", "package.json\n", "Successfully initialized scan\n", @@ -126,6 +127,71 @@ func TestScan(t *testing.T) { } } +func TestScanWithJsonPath(t *testing.T) { + if runtime.GOOS == windowsOS { + t.Skipf("TestScan is skipped due to Windows env") + } + clientMock := testdata.NewDebClientMock() + addMockedFormatsResponse(clientMock, "package\\.json") + addMockedFileUploadResponse(clientMock) + addMockedFinishResponse(clientMock, http.StatusNoContent) + addMockedStatusResponse(clientMock, http.StatusOK, 50) + addMockedStatusResponse(clientMock, http.StatusOK, 100) + scanner := makeScanner(clientMock, nil, nil) + + path := testdataNpm + repositoryName := path + cwd, _ := os.Getwd() + + // reset working directory that has been manipulated in scanner.Scan + defer resetWd(t, cwd) + opts := DebrickedOptions{ + Path: path, + Exclusions: nil, + RepositoryName: repositoryName, + CommitName: "commit", + BranchName: "", + CommitAuthor: "", + RepositoryUrl: "", + IntegrationName: "", + CallGraphUploadTimeout: 10 * 60, + CallGraphGenerateTimeout: 10 * 60, + JsonFilePath: "result.json", + } + + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + err := scanner.Scan(opts) + + _ = w.Close() + output, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + if err != nil { + t.Error("failed to assert that scan ran without errors. Error:", err) + } + + outputAssertions := []string{ + "Working directory: /", + "Successfully uploaded", + "package.json", + "Successfully initialized scan\n", + "Scanning...", + "0% |", + "50% |", + "100% |", + "32m✔", + "0 vulnerabilities found\n\n", + "For full details, visit:", + } + for _, assertion := range outputAssertions { + assert.Contains(t, string(output), assertion) + } + assert.FileExists(t, filepath.Join(cwd, path, "result.json")) +} + func TestScanFailingMetaObject(t *testing.T) { var debClient client.IDebClient = testdata.NewDebClientMock() scanner := NewDebrickedScanner(&debClient, nil, nil, ciService, nil, nil, nil) @@ -184,7 +250,7 @@ func TestScanBadOpts(t *testing.T) { } func TestScanEmptyResult(t *testing.T) { - if runtime.GOOS == "windows" { + if runtime.GOOS == windowsOS { t.Skipf("TestScan is skipped due to Windows env") } clientMock := testdata.NewDebClientMock()