Skip to content

Commit

Permalink
feat: ignore-on-exit flag to control the error code (#162)
Browse files Browse the repository at this point in the history
Fixes #155

Reference: [`ignore-on-exit`](https://docs.kics.io/latest/commands/) in
Kics

Waits for #188

## Proposed Changes

<!--
- Assumed that the flag is relevant only for "plugins" sub-commands.
Therefore added a Bool flag during the creation of the sub commands in
the `main.go` file.

- The exit status for the mentioned scenarios is set in the `postRun`
function hence added a bool variable to determine whether secrets should
be ignored.
-->

- Ran manual tests with the confluence plugin:  
  | cmd | exit code |
  |--------|--------|
| `2ms confluence --url <url> --spaces <space> --token <token>
--username <>` | 1 |
| `2ms confluence --url <url> --spaces <space> --token <token>
--username <> --ignore-secrets` | 0 |

---------

Co-authored-by: nirmo <nirmoshe711@gmail>
Co-authored-by: Baruch Odem <[email protected]>
  • Loading branch information
3 people authored Sep 28, 2023
1 parent 8d71105 commit faba298
Show file tree
Hide file tree
Showing 14 changed files with 304 additions and 118 deletions.
27 changes: 15 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,18 +117,21 @@ Additional Commands:
rules List all rules

Flags:
--add-special-rule strings special (non-default) rules to apply.
This list is not affected by the --rule and --ignore-rule flags.
--config string config file path
-h, --help help for 2ms
--ignore-result strings ignore specific result by id
--ignore-rule strings ignore rules by name or tag
--log-level string log level (trace, debug, info, warn, error, fatal) (default "info")
--regex stringArray custom regexes to apply to the scan, must be valid Go regex
--report-path strings path to generate report files. The output format will be determined by the file extension (.json, .yaml, .sarif)
--rule strings select rules by name or tag to apply to this scan
--stdout-format string stdout output format, available formats are: json, yaml, sarif (default "yaml")
-v, --version version for 2ms
--add-special-rule strings special (non-default) rules to apply.
This list is not affected by the --rule and --ignore-rule flags.
--config string config file path
-h, --help help for 2ms
--ignore-on-exit ignoreOnExit defines which kind of non-zero exits code should be ignored
accepts: all, results, errors, none
example: if 'results' is set, only engine errors will make 2ms exit code different from 0 (default none)
--ignore-result strings ignore specific result by id
--ignore-rule strings ignore rules by name or tag
--log-level string log level (trace, debug, info, warn, error, fatal) (default "info")
--regex stringArray custom regexes to apply to the scan, must be valid Go regex
--report-path strings path to generate report files. The output format will be determined by the file extension (.json, .yaml, .sarif)
--rule strings select rules by name or tag to apply to this scan
--stdout-format string stdout output format, available formats are: json, yaml, sarif (default "yaml")
-v, --version version for 2ms

Use "2ms [command] --help" for more information about a command.
```
Expand Down
57 changes: 57 additions & 0 deletions cmd/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package cmd

import (
"fmt"
"path/filepath"
"regexp"
"strings"

"github.com/checkmarx/2ms/lib"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)

func initialize() {
configFilePath, err := rootCmd.Flags().GetString(configFileFlag)
if err != nil {
cobra.CheckErr(err)
}
cobra.CheckErr(lib.LoadConfig(vConfig, configFilePath))
cobra.CheckErr(lib.BindFlags(rootCmd, vConfig, envPrefix))

logLevel := zerolog.InfoLevel
switch strings.ToLower(logLevelVar) {
case "trace":
logLevel = zerolog.TraceLevel
case "debug":
logLevel = zerolog.DebugLevel
case "info":
logLevel = zerolog.InfoLevel
case "warn":
logLevel = zerolog.WarnLevel
case "err", "error":
logLevel = zerolog.ErrorLevel
case "fatal":
logLevel = zerolog.FatalLevel
}
zerolog.SetGlobalLevel(logLevel)
log.Logger = log.Logger.Level(logLevel)
}

func validateFormat(stdout string, reportPath []string) error {
r := regexp.MustCompile(outputFormatRegexpPattern)
if !(r.MatchString(stdout)) {
return fmt.Errorf(`invalid output format: %s, available formats are: json, yaml and sarif`, stdout)
}

for _, path := range reportPath {
fileExtension := filepath.Ext(path)
format := strings.TrimPrefix(fileExtension, ".")
if !(r.MatchString(format)) {
return fmt.Errorf(`invalid report extension: %s, available extensions are: json, yaml and sarif`, format)
}
}

return nil
}
37 changes: 37 additions & 0 deletions cmd/enum_flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cmd

import (
"flag"
"fmt"
)

type ignoreOnExit string

const (
ignoreOnExitNone ignoreOnExit = "none"
ignoreOnExitAll ignoreOnExit = "all"
ignoreOnExitResults ignoreOnExit = "results"
ignoreOnExitErrors ignoreOnExit = "errors"
)

// verify that ignoreOnExit implements flag.Value interface
// https://github.com/uber-go/guide/blob/master/style.md#verify-interface-compliance
var _ flag.Value = (*ignoreOnExit)(nil)

func (i *ignoreOnExit) String() string {
return string(*i)
}

func (i *ignoreOnExit) Set(value string) error {
switch value {
case "none", "all", "results", "errors":
*i = ignoreOnExit(value)
return nil
default:
return fmt.Errorf("invalid value %s", value)
}
}

func (i *ignoreOnExit) Type() string {
return "ignoreOnExit"
}
53 changes: 53 additions & 0 deletions cmd/exit_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package cmd

import (
"os"
)

const (
errorCode = 1
resultsCode = 2
)

func isNeedReturnErrorCodeFor(kind ignoreOnExit) bool {
if ignoreOnExitVar == ignoreOnExitNone {
return true
}

if ignoreOnExitVar == ignoreOnExitAll {
return false
}

if ignoreOnExitVar != ignoreOnExit(kind) {
return true
}

return false
}

func exitCodeIfError(err error) int {
if err != nil && isNeedReturnErrorCodeFor("errors") {
return errorCode
}

return 0
}

func exitCodeIfResults(resultsCount int) int {
if resultsCount > 0 && isNeedReturnErrorCodeFor("results") {
return resultsCode
}

return 0
}

func Exit(resultsCount int, err error) {
os.Exit(exitCodeIfError(err) + exitCodeIfResults(resultsCount))
}

func listenForErrors(errors chan error) {
go func() {
err := <-errors
Exit(0, err)
}()
}
73 changes: 73 additions & 0 deletions cmd/exit_handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package cmd

import (
"fmt"
"testing"
)

func TestExitHandler_IsNeedReturnErrorCode(t *testing.T) {

var onErrorsTests = []struct {
userInput ignoreOnExit
expectedResult bool
}{
{
userInput: ignoreOnExitNone,
expectedResult: true,
},
{
userInput: ignoreOnExitAll,
expectedResult: false,
},
{
userInput: ignoreOnExitResults,
expectedResult: true,
},
{
userInput: ignoreOnExitErrors,
expectedResult: false,
},
}

for idx, testCase := range onErrorsTests {
t.Run(fmt.Sprintf("Print test case %d", idx), func(t *testing.T) {
ignoreOnExitVar = testCase.userInput
result := isNeedReturnErrorCodeFor("errors")
if result != testCase.expectedResult {
t.Errorf("Expected %v, got %v", testCase.expectedResult, result)
}
})
}

var onResultsTests = []struct {
userInput ignoreOnExit
expectedResult bool
}{
{
userInput: ignoreOnExitNone,
expectedResult: true,
},
{
userInput: ignoreOnExitAll,
expectedResult: false,
},
{
userInput: ignoreOnExitResults,
expectedResult: false,
},
{
userInput: ignoreOnExitErrors,
expectedResult: true,
},
}

for idx, testCase := range onResultsTests {
t.Run(fmt.Sprintf("Print test case %d", idx), func(t *testing.T) {
ignoreOnExitVar = testCase.userInput
result := isNeedReturnErrorCodeFor("results")
if result != testCase.expectedResult {
t.Errorf("Expected %v, got %v", testCase.expectedResult, result)
}
})
}
}
Loading

0 comments on commit faba298

Please sign in to comment.