diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..b6b6a6cc --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index edabd2ff..196676da 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -19,11 +19,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Go uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 @@ -38,11 +38,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Go uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 @@ -60,11 +60,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Go uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 @@ -84,11 +84,11 @@ jobs: contents: write steps: - name: Harden Runner - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 203e826b..14455e3c 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -25,11 +25,11 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Harden Runner - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version: ${{ matrix.go }} diff --git a/.github/workflows/goreportcard.yaml b/.github/workflows/goreportcard.yaml index 987d286c..45589d70 100644 --- a/.github/workflows/goreportcard.yaml +++ b/.github/workflows/goreportcard.yaml @@ -18,7 +18,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Harden Runner - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -28,7 +28,7 @@ jobs: go-version: ${{ matrix.go }} cache: false - name: Checkout gojp/goreportcard repo - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: gojp/goreportcard path: goreportcard @@ -51,7 +51,7 @@ jobs: # Install goreportcard-cli binary go install ./cmd/goreportcard-cli - name: Checkout Boeing/config-file-validator repo - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Run goreportcard run: | # Failure threshold is set to 100% to fail at any errors. Default is 75%. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index db0ca691..ff364adc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,20 +30,41 @@ jobs: contents: write steps: - - name: Harden Runner - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 - with: - egress-policy: audit - - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - - uses: wangyoucao577/go-release-action@8fa1e8368c8465264d64e0198208e10f71474c87 # v1.50 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - goos: ${{ matrix.goos }} - goarch: ${{ matrix.goarch }} - go_version: 1.22 - binary_name: "validator" - ldflags: -w -s -extldflags "-static" -X github.com/Boeing/config-file-validator.version=${{ github.event.release.tag_name }} - build_tags: -tags netgo - project_path: cmd/validator - extra_files: LICENSE README.md \ No newline at end of file + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: wangyoucao577/go-release-action@2aa2977ad6a4534f9179e22bd0ff146a1e1d3466 # v1.52 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + goos: ${{ matrix.goos }} + goarch: ${{ matrix.goarch }} + go_version: 1.22 + binary_name: "validator" + ldflags: -w -s -extldflags "-static" -X github.com/Boeing/config-file-validator.version=${{ github.event.release.tag_name }} + build_tags: -tags netgo + project_path: cmd/validator + extra_files: LICENSE README.md + + aur-publish: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Publish AUR package + uses: KSXGitHub/github-actions-deploy-aur@9dfe151cf48f26a957bbd0379c120e79cb990e13 # v2.7.2 + with: + pkgname: config-file-validator + pkgbuild: ./PKGBUILD + commit_username: ${{ secrets.AUR_USERNAME }} + commit_email: ${{ secrets.AUR_EMAIL }} + ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} + commit_message: Update AUR package + ssh_keyscan_types: rsa,ecdsa,ed25519 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 5eccf0be..ece4b39d 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -30,12 +30,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - name: "Checkout code" - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false @@ -54,7 +54,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3.pre.node20 + uses: actions/upload-artifact@c24449f33cd45d4826c6702db7e49f7cdb9b551d # v3.pre.node20 with: name: SARIF file path: results.sarif @@ -63,6 +63,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 with: sarif_file: results.sarif diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 00000000..c45e7398 --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,37 @@ +# Maintainer: Clayton Kehoe +# Contributor : wiz64 +pkgname=config-file-validator +pkgver=1.7.1 +pkgrel=1 +pkgdesc="A tool to validate the syntax of configuration files" +arch=('x86_64') +url="https://github.com/Boeing/config-file-validator" +license=('Apache 2.0') +depends=('glibc') +makedepends=('go>=1.21' 'git' 'sed') +source=("git+https://github.com/Boeing/config-file-validator.git") +sha256sums=('SKIP') +md5sums=('SKIP') + +pkgver() { + cd "$srcdir/$pkgname" + git describe --tags --abbrev=0 | sed 's/^v//' +} + +build() { + cd "$srcdir/$pkgname" + CGO_ENABLED=0 \ + GOOS=linux \ + GOARCH=amd64 \ + go build \ + -ldflags="-w -s -extldflags '-static' \ + -X github.com/Boeing/config-file-validator.version=$pkgver" \ + -tags netgo \ + -o validator \ + cmd/validator/validator.go +} + +package() { + cd "$srcdir/$pkgname" + install -Dm755 validator "$pkgdir/usr/bin/validator" +} \ No newline at end of file diff --git a/README.md b/README.md index 40ee7ce9..7293720f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

-Code Coverage +Code Coverage OpenSSF Scorecard @@ -116,6 +116,20 @@ optional flags: Version prints the release version of validator ``` +### Environment Variables + +The config-file-validator supports setting options via environment variables. If both command-line flags and environment variables are set, the command-line flags will take precedence. The supported environment variables are as follows: + +| Environment Variable | Equivalent Flag | +|----------------------|-----------------| +| `CFV_DEPTH` | `-depth` | +| `CFV_EXCLUDE_DIRS` | `-exclude-dirs` | +| `CFV_EXCLUDE_FILE_TYPES` | `-exclude-file-types` | +| `CFV_OUTPUT` | `-output` | +| `CFV_REPORTER` | `-reporter` | +| `CFV_GROUPBY` | `-groupby` | +| `CFV_QUIET` | `-quiet` | + ### Examples #### Standard Run If the search path is omitted it will search the current directory diff --git a/cmd/validator/validator.go b/cmd/validator/validator.go index c3eb89c0..a3c1c9d4 100644 --- a/cmd/validator/validator.go +++ b/cmd/validator/validator.go @@ -20,7 +20,7 @@ optional flags: -output Destination of a file to outputting results -reporter string - Format of the printed report. Options are standard and json (default "standard") + Format of the printed report. Options are standard, json, junit and sarif (default "standard") -version Version prints the release version of validator */ @@ -103,11 +103,28 @@ func getFlags() (validatorConfig, error) { excludeDirsPtr = flag.String("exclude-dirs", "", "Subdirectories to exclude when searching for configuration files") excludeFileTypesPtr = flag.String("exclude-file-types", "", "A comma separated list of file types to ignore.\nValid options: "+strings.Join(getFileTypes(), ", ")) outputPtr = flag.String("output", "", "Destination to a file to output results") - reportTypePtr = flag.String("reporter", "standard", "Format of the printed report. Options are standard and json") + reportTypePtr = flag.String("reporter", "standard", "Format of the printed report. Options are standard, json, junit and sarif") versionPtr = flag.Bool("version", false, "Version prints the release version of validator") groupOutputPtr = flag.String("groupby", "", "Group output by filetype, directory, pass-fail. Supported for Standard and JSON reports") - quietPrt = flag.Bool("quiet", false, "If quiet flag is set. It doesn't print any output to stdout.") + quietPtr = flag.Bool("quiet", false, "If quiet flag is set. It doesn't print any output to stdout.") ) + + flagsEnvMap := map[string]string{ + "depth": "CFV_DEPTH", + "exclude-dirs": "CFV_EXCLUDE_DIRS", + "exclude-file-types": "CFV_EXCLUDE_FILE_TYPES", + "output": "CFV_OUTPUT", + "reporter": "CFV_REPORTER", + "groupby": "CFV_GROUPBY", + "quiet": "CFV_QUIET", + } + + for flagName, envVar := range flagsEnvMap { + if err := setFlagFromEnvIfNotSet(flagName, envVar); err != nil { + return validatorConfig{}, err + } + } + flag.Parse() searchPaths := make([]string, 0) @@ -121,12 +138,16 @@ func getFlags() (validatorConfig, error) { searchPaths = append(searchPaths, flag.Args()...) } - if *reportTypePtr != "standard" && *reportTypePtr != "json" && *reportTypePtr != "junit" { - return validatorConfig{}, errors.New("Wrong parameter value for reporter, only supports standard, json or junit") + acceptedReportTypes := map[string]bool{"standard": true, "json": true, "junit": true, "sarif": true} + + if !acceptedReportTypes[*reportTypePtr] { + return validatorConfig{}, errors.New("Wrong parameter value for reporter, only supports standard, json, junit or sarif") } - if *reportTypePtr == "junit" && *groupOutputPtr != "" { - return validatorConfig{}, errors.New("Wrong parameter value for reporter, groupby is not supported for JUnit reports") + groupOutputReportTypes := map[string]bool{"standard": true, "json": true} + + if !groupOutputReportTypes[*reportTypePtr] && *groupOutputPtr != "" { + return validatorConfig{}, errors.New("Wrong parameter value for reporter, groupby is only supported for standard and JSON reports") } if depthPtr != nil && isFlagSet("depth") && *depthPtr < 0 { @@ -166,7 +187,7 @@ func getFlags() (validatorConfig, error) { versionPtr, outputPtr, groupOutputPtr, - quietPrt, + quietPtr, } return config, nil @@ -185,6 +206,20 @@ func isFlagSet(flagName string) bool { return isSet } +func setFlagFromEnvIfNotSet(flagName string, envVar string) error { + if isFlagSet(flagName) { + return nil + } + + if envVarValue, ok := os.LookupEnv(envVar); ok { + if err := flag.Set(flagName, envVarValue); err != nil { + return err + } + } + + return nil +} + // Return the reporter associated with the // reportType string func getReporter(reportType, outputDest *string) reporter.Reporter { @@ -193,6 +228,8 @@ func getReporter(reportType, outputDest *string) reporter.Reporter { return reporter.NewJunitReporter(*outputDest) case "json": return reporter.NewJSONReporter(*outputDest) + case "sarif": + return reporter.NewSARIFReporter(*outputDest) default: return reporter.StdoutReporter{} } diff --git a/cmd/validator/validator_test.go b/cmd/validator/validator_test.go index bcd9c03b..4f1ca4b1 100644 --- a/cmd/validator/validator_test.go +++ b/cmd/validator/validator_test.go @@ -22,7 +22,8 @@ func Test_flags(t *testing.T) { {"depth set", []string{"-depth=1", "."}, 0}, {"flags set, wrong reporter", []string{"--exclude-dirs=subdir", "--reporter=wrong", "."}, 1}, {"flags set, json reporter", []string{"--exclude-dirs=subdir", "--reporter=json", "."}, 0}, - {"flags set, junit reported", []string{"--exclude-dirs=subdir", "--reporter=junit", "."}, 0}, + {"flags set, junit reporter", []string{"--exclude-dirs=subdir", "--reporter=junit", "."}, 0}, + {"flags set, sarif reporter", []string{"--exclude-dirs=subdir", "--reporter=sarif", "."}, 0}, {"bad path", []string{"/path/does/not/exit"}, 1}, {"exclude file types set", []string{"--exclude-file-types=json", "."}, 0}, {"multiple paths", []string{"../../test/fixtures/subdir/good.json", "../../test/fixtures/good.json"}, 0}, @@ -33,6 +34,7 @@ func Test_flags(t *testing.T) { {"incorrect group", []string{"-groupby=badgroup", "."}, 1}, {"correct group", []string{"-groupby=directory", "."}, 0}, {"grouped junit", []string{"-groupby=directory", "--reporter=junit", "."}, 1}, + {"grouped sarif", []string{"-groupby=directory", "--reporter=sarif", "."}, 1}, {"groupby duplicate", []string{"--groupby=directory,directory", "."}, 1}, {"quiet flag", []string{"--quiet=true", "."}, 0}, } diff --git a/go.mod b/go.mod index 8d4165c0..87b13541 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,16 @@ module github.com/Boeing/config-file-validator -go 1.21 +go 1.21.0 require ( + github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 github.com/fatih/color v1.13.0 github.com/gurkankaymak/hocon v1.2.18 + github.com/hashicorp/go-envparse v0.1.0 github.com/hashicorp/hcl/v2 v2.18.1 github.com/magiconair/properties v1.8.7 - github.com/pelletier/go-toml/v2 v2.0.6 - github.com/stretchr/testify v1.8.1 + github.com/pelletier/go-toml/v2 v2.2.3 + github.com/stretchr/testify v1.9.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 howett.net/plist v1.0.0 @@ -19,16 +21,12 @@ require ( github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/hashicorp/go-envparse v0.1.0 // indirect github.com/mattn/go-colorable v0.1.9 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/zclconf/go-cty v1.13.0 // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/sync v0.2.0 // indirect golang.org/x/sys v0.12.0 // indirect golang.org/x/text v0.11.0 // indirect ) diff --git a/go.sum b/go.sum index d15544fc..6f0ca383 100644 --- a/go.sum +++ b/go.sum @@ -4,7 +4,6 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6 github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 h1:dKG8sc7n321deIVRcQtwlMNoBEra7j0qQ8RwxO8RN0w= @@ -13,8 +12,7 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gurkankaymak/hocon v1.2.18 h1:/COj3okWh58himiYO0R7PrPX+iE7PbuzTn2cEv7fPsw= github.com/gurkankaymak/hocon v1.2.18/go.mod h1:dQCfhnuDKlLqAZRGhFTd81HkAfMx7STHv0w2JkJ6iq4= @@ -38,23 +36,16 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0= github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -67,7 +58,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= diff --git a/index.md b/index.md index 9a7733ce..37f7656a 100644 --- a/index.md +++ b/index.md @@ -19,7 +19,7 @@

-Code Coverage +Code Coverage OpenSSF Scorecard diff --git a/pkg/reporter/reporter_test.go b/pkg/reporter/reporter_test.go index 4a0aa1f0..72473e88 100644 --- a/pkg/reporter/reporter_test.go +++ b/pkg/reporter/reporter_test.go @@ -145,6 +145,40 @@ func Test_junitReport(t *testing.T) { } } +func Test_sarifReport(t *testing.T) { + reportNoValidationError := Report{ + "good.xml", + "/fake/path/good.xml", + true, + nil, + false, + } + + reportWithBackslashPath := Report{ + "good.xml", + "\\fake\\path\\good.xml", + true, + nil, + false, + } + + reportWithValidationError := Report{ + "bad.xml", + "/fake/path/bad.xml", + false, + errors.New("Unable to parse bad.xml file"), + false, + } + + reports := []Report{reportNoValidationError, reportWithValidationError, reportWithBackslashPath} + + sarifReporter := SARIFReporter{} + err := sarifReporter.Print(reports) + if err != nil { + t.Errorf("Reporting failed") + } +} + func Test_jsonReporterWriter(t *testing.T) { report := Report{ "good.json", @@ -251,6 +285,112 @@ func Test_jsonReporterWriter(t *testing.T) { } } +func Test_sarifReporterWriter(t *testing.T) { + report := Report{ + "good.json", + "test/output/example/good.json", + true, + nil, + false, + } + deleteFiles(t) + + bytes, err := os.ReadFile("../../test/output/example/result.sarif") + require.NoError(t, err) + + type args struct { + reports []Report + outputDest string + } + type want struct { + fileName string + data []byte + err assert.ErrorAssertionFunc + } + + tests := map[string]struct { + args args + want want + }{ + "normal/existing dir/default name": { + args: args{ + reports: []Report{ + report, + }, + outputDest: "../../test/output", + }, + want: want{ + fileName: "result.sarif", + data: bytes, + err: assert.NoError, + }, + }, + "normal/file name is given": { + args: args{ + reports: []Report{ + report, + }, + outputDest: "../../test/output/validator_result.sarif", + }, + want: want{ + fileName: "validator_result.sarif", + data: bytes, + err: assert.NoError, + }, + }, + "quash normal/empty string": { + args: args{ + reports: []Report{ + report, + }, + outputDest: "", + }, + want: want{ + fileName: "", + data: nil, + err: assert.NoError, + }, + }, + "abnormal/non-existing dir": { + args: args{ + reports: []Report{ + report, + }, + outputDest: "../../test/wrong/output", + }, + want: want{ + fileName: "", + data: nil, + err: assertRegexpError("failed to create a file: "), + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + sut := NewSARIFReporter(tt.args.outputDest) + err := sut.Print(tt.args.reports) + tt.want.err(t, err) + if tt.want.data != nil { + info, err := os.Stat(tt.args.outputDest) + require.NoError(t, err) + var filePath string + if info.IsDir() { + filePath = tt.args.outputDest + "/result.sarif" + } else { // if file was named with outputDest value + assert.Equal(t, tt.want.fileName, info.Name()) + filePath = tt.args.outputDest + } + bytes, err := os.ReadFile(filePath) + require.NoError(t, err) + assert.Equal(t, tt.want.data, bytes) + err = os.Remove(filePath) + require.NoError(t, err) + } + }, + ) + } +} + func Test_JunitReporter_OutputBytesToFile(t *testing.T) { report := Report{ "good.json", diff --git a/pkg/reporter/sarif_reporter.go b/pkg/reporter/sarif_reporter.go new file mode 100644 index 00000000..b775a254 --- /dev/null +++ b/pkg/reporter/sarif_reporter.go @@ -0,0 +1,133 @@ +package reporter + +import ( + "encoding/json" + "fmt" + "strings" +) + +const SARIFVersion = "2.1.0" +const SARIFSchema = "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json" +const DriverName = "config-file-validator" +const DriverInfoURI = "https://github.com/Boeing/config-file-validator" +const DriverVersion = "1.7.1" + +type SARIFReporter struct { + outputDest string +} + +type SARIFLog struct { + Version string `json:"version"` + Schema string `json:"$schema"` + Runs []runs `json:"runs"` +} + +type runs struct { + Tool tool `json:"tool"` + Results []result `json:"results"` +} + +type tool struct { + Driver driver `json:"driver"` +} + +type driver struct { + Name string `json:"name"` + InfoURI string `json:"informationUri"` + Version string `json:"version"` +} + +type result struct { + Kind string `json:"kind"` + Level string `json:"level"` + Message message `json:"message"` + Locations []location `json:"locations"` +} + +type message struct { + Text string `json:"text"` +} + +type location struct { + PhysicalLocation physicalLocation `json:"physicalLocation"` +} + +type physicalLocation struct { + ArtifactLocation artifactLocation `json:"artifactLocation"` +} + +type artifactLocation struct { + URI string `json:"uri"` +} + +func NewSARIFReporter(outputDest string) *SARIFReporter { + return &SARIFReporter{ + outputDest: outputDest, + } +} + +func createSARIFReport(reports []Report) (*SARIFLog, error) { + var log SARIFLog + + n := len(reports) + + log.Version = SARIFVersion + log.Schema = SARIFSchema + + log.Runs = make([]runs, 1) + runs := &log.Runs[0] + + runs.Tool.Driver.Name = DriverName + runs.Tool.Driver.InfoURI = DriverInfoURI + runs.Tool.Driver.Version = DriverVersion + + runs.Results = make([]result, n) + + for i, report := range reports { + if strings.Contains(report.FilePath, "\\") { + report.FilePath = strings.ReplaceAll(report.FilePath, "\\", "/") + } + + result := &runs.Results[i] + if !report.IsValid { + result.Kind = "fail" + result.Level = "error" + result.Message.Text = report.ValidationError.Error() + } else { + result.Kind = "pass" + result.Level = "none" + result.Message.Text = "No errors detected" + } + + result.Locations = make([]location, 1) + location := &result.Locations[0] + + location.PhysicalLocation.ArtifactLocation.URI = "file:///" + report.FilePath + } + + return &log, nil +} + +func (sr SARIFReporter) Print(reports []Report) error { + report, err := createSARIFReport(reports) + if err != nil { + return err + } + + sarifBytes, err := json.MarshalIndent(report, "", " ") + if err != nil { + return err + } + + sarifBytes = append(sarifBytes, '\n') + + if len(reports) > 0 && !reports[0].IsQuiet { + fmt.Print(string(sarifBytes)) + } + + if sr.outputDest != "" { + return outputBytesToFile(sr.outputDest, "result", "sarif", sarifBytes) + } + + return nil +} diff --git a/test/output/example/result.sarif b/test/output/example/result.sarif new file mode 100644 index 00000000..e86e9f47 --- /dev/null +++ b/test/output/example/result.sarif @@ -0,0 +1,33 @@ +{ + "version": "2.1.0", + "$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json", + "runs": [ + { + "tool": { + "driver": { + "name": "config-file-validator", + "informationUri": "https://github.com/Boeing/config-file-validator", + "version": "1.7.1" + } + }, + "results": [ + { + "kind": "pass", + "level": "none", + "message": { + "text": "No errors detected" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///test/output/example/good.json" + } + } + } + ] + } + ] + } + ] +}