mirror of
https://github.com/ivuorinen/go-test-sarif.git
synced 2026-01-26 03:04:09 +00:00
feat: the app (#2)
This commit is contained in:
122
.github/README.md
vendored
Normal file
122
.github/README.md
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
# go-test-sarif
|
||||
|
||||
[](https://github.com/ivuorinen/go-test-sarif/actions/workflows/test.yml)
|
||||
|
||||
`go-test-sarif` is a CLI tool and GitHub Action for converting `go test -json` output into SARIF format, making it compatible with GitHub Security Tab and other SARIF consumers.
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- Converts `go test -json` output to **SARIF format**.
|
||||
- **GitHub Action integration** for CI/CD pipelines.
|
||||
- Generates structured test failure reports for **security and compliance tools**.
|
||||
- Works as a **standalone CLI tool**.
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
### Using `go install`
|
||||
|
||||
```sh
|
||||
go install github.com/ivuorinen/go-test-sarif@latest
|
||||
```
|
||||
|
||||
### Using Docker
|
||||
|
||||
```sh
|
||||
docker pull ghcr.io/ivuorinen/go-test-sarif:latest
|
||||
```
|
||||
|
||||
## 🛠️ Usage
|
||||
|
||||
### CLI Usage
|
||||
|
||||
```sh
|
||||
go test -json ./... > go-test-results.json
|
||||
go-test-sarif go-test-results.json go-test-results.sarif
|
||||
```
|
||||
|
||||
### Docker Usage
|
||||
|
||||
```sh
|
||||
docker run --rm -v $(pwd):/workspace ghcr.io/ivuorinen/go-test-sarif go-test-results.json go-test-results.sarif
|
||||
```
|
||||
|
||||
### GitHub Action Usage
|
||||
|
||||
Add the following step to your GitHub Actions workflow:
|
||||
|
||||
```yaml
|
||||
- name: Convert JSON to SARIF
|
||||
uses: ivuorinen/go-test-sarif@v1
|
||||
with:
|
||||
test_results: go-test-results.json
|
||||
```
|
||||
|
||||
To upload the SARIF file to GitHub Security Tab, add:
|
||||
|
||||
```yaml
|
||||
- name: Upload SARIF report
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: go-test-results.sarif
|
||||
```
|
||||
|
||||
## 📜 Output Example
|
||||
|
||||
SARIF report example:
|
||||
```json
|
||||
{
|
||||
"version": "2.1.0",
|
||||
"runs": [
|
||||
{
|
||||
"tool": {
|
||||
"driver": {
|
||||
"name": "Go Test",
|
||||
"informationUri": "https://golang.org/cmd/go/#hdr-Test_packages",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"ruleId": "go-test-failure",
|
||||
"level": "error",
|
||||
"message": {
|
||||
"text": "Test failed"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "github.com/example/package"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 🏗 Development
|
||||
|
||||
Clone the repository and build the project:
|
||||
```sh
|
||||
git clone https://github.com/ivuorinen/go-test-sarif.git
|
||||
cd go-test-sarif
|
||||
go build -o go-test-sarif ./cmd/main.go
|
||||
```
|
||||
|
||||
Run tests:
|
||||
|
||||
```sh
|
||||
go test ./...
|
||||
```
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the **MIT License**.
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Pull requests are welcome! For major changes, please open an issue first to discuss the changes.
|
||||
14
.github/workflows/release-drafter.yml
vendored
14
.github/workflows/release-drafter.yml
vendored
@@ -1,14 +0,0 @@
|
||||
---
|
||||
name: Release Drafter
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
Draft:
|
||||
uses: ivuorinen/.github/.github/workflows/sync-labels.yml@main
|
||||
32
.github/workflows/test.yml
vendored
Normal file
32
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Run Go Tests and Generate SARIF
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- name: Run Go Tests
|
||||
run: go test -json ./... > go-test-results.json
|
||||
|
||||
- name: Convert JSON to SARIF
|
||||
uses: ivuorinen/go-test-sarif@v1
|
||||
with:
|
||||
test_results: go-test-results.json
|
||||
|
||||
- name: Upload SARIF to GitHub Security Tab
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: go-test-results.sarif
|
||||
111
.gitignore
vendored
111
.gitignore
vendored
@@ -1,83 +1,7 @@
|
||||
.php-cs-fixer.cache
|
||||
.php-cs-fixer.php
|
||||
composer.phar
|
||||
/vendor/
|
||||
.phpunit.result.cache
|
||||
.phpunit.cache
|
||||
/app/phpunit.xml
|
||||
/phpunit.xml
|
||||
/build/
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
lib-cov
|
||||
coverage
|
||||
*.lcov
|
||||
.nyc_output
|
||||
.grunt
|
||||
bower_components
|
||||
.lock-wscript
|
||||
build/Release
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
web_modules/
|
||||
*.tsbuildinfo
|
||||
.npm
|
||||
.eslintcache
|
||||
.stylelintcache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
.node_repl_history
|
||||
*.tgz
|
||||
.yarn-integrity
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
.cache
|
||||
.parcel-cache
|
||||
.next
|
||||
out
|
||||
.nuxt
|
||||
dist
|
||||
.cache/
|
||||
.vuepress/dist
|
||||
.temp
|
||||
.docusaurus
|
||||
.serverless/
|
||||
.fusebox/
|
||||
.dynamodb/
|
||||
.tern-port
|
||||
.vscode-test
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
[._]*.s[a-v][a-z]
|
||||
!*.svg # comment out if you don't need vector files
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-rt-v][a-z]
|
||||
[._]ss[a-gi-z]
|
||||
[._]sw[a-p]
|
||||
Session.vim
|
||||
Sessionx.vim
|
||||
.netrwhist
|
||||
*~
|
||||
tags
|
||||
[._]*.un~
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
@@ -94,41 +18,10 @@ tags
|
||||
.idea/**/dbnavigator.xml
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
cmake-build-*/
|
||||
.idea/**/mongoSettings.xml
|
||||
*.iws
|
||||
out/
|
||||
.idea_modules/
|
||||
atlassian-ide-plugin.xml
|
||||
.idea/replstate.xml
|
||||
.idea/sonarlint/
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
.idea/httpRequests
|
||||
.idea/caches/build_file_checksums.ser
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
bootstrap/compiled.php
|
||||
app/storage/
|
||||
public/storage
|
||||
public/hot
|
||||
public_html/storage
|
||||
public_html/hot
|
||||
storage/*.key
|
||||
Homestead.yaml
|
||||
Homestead.json
|
||||
/.vagrant
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
/coverage
|
||||
/.next/
|
||||
/out/
|
||||
/build
|
||||
.DS_Store
|
||||
*.pem
|
||||
.env*.local
|
||||
.vercel
|
||||
next-env.d.ts
|
||||
bin/*
|
||||
!bin/.gitkeep
|
||||
|
||||
10
Dockerfile
Normal file
10
Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
||||
FROM golang:1.21-alpine AS build
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN go build -o /go-test-sarif ./cmd/main.go
|
||||
|
||||
FROM alpine:latest
|
||||
COPY --from=build /go-test-sarif /go-test-sarif
|
||||
COPY action/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
40
Justfile
Normal file
40
Justfile
Normal file
@@ -0,0 +1,40 @@
|
||||
# Set the application name
|
||||
app_name := "go-test-sarif"
|
||||
binary_path := "./bin/" + app_name
|
||||
src := "./cmd/main.go"
|
||||
|
||||
# Default task
|
||||
default:
|
||||
just build
|
||||
|
||||
# Build the Go binary
|
||||
build:
|
||||
echo "Building {{app_name}}..."
|
||||
mkdir -p bin
|
||||
GOOS=linux GOARCH=amd64 go build -o {{binary_path}} {{src}}
|
||||
echo "Binary built at {{binary_path}}"
|
||||
|
||||
# Run tests
|
||||
test:
|
||||
echo "Running tests..."
|
||||
go test ./... -v
|
||||
|
||||
# Run the application
|
||||
run:
|
||||
echo "Running {{app_name}}..."
|
||||
{{binary_path}} go-test-results.json go-test-results.sarif
|
||||
|
||||
# Clean build artifacts
|
||||
clean:
|
||||
echo "Cleaning up..."
|
||||
rm -rf bin go-test-results.sarif
|
||||
|
||||
# Build the Docker image
|
||||
docker-build:
|
||||
echo "Building Docker image..."
|
||||
docker build -t ghcr.io/ivuorinen/{{app_name}}:latest .
|
||||
|
||||
# Run the application inside Docker
|
||||
docker-run:
|
||||
echo "Running {{app_name}} in Docker..."
|
||||
docker run --rm -v $(pwd):/workspace ghcr.io/ivuorinen/{{app_name}} go-test-results.json go-test-results.sarif
|
||||
13
action/entrypoint.sh
Normal file
13
action/entrypoint.sh
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ -z "$INPUT_TEST_RESULTS" ]; then
|
||||
echo "Missing test results input file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OUTPUT_FILE="go-test-results.sarif"
|
||||
|
||||
/go-test-sarif "$INPUT_TEST_RESULTS" "$OUTPUT_FILE"
|
||||
|
||||
echo "Generated SARIF report: $OUTPUT_FILE"
|
||||
0
bin/.gitkeep
Normal file
0
bin/.gitkeep
Normal file
25
cmd/main.go
Normal file
25
cmd/main.go
Normal file
@@ -0,0 +1,25 @@
|
||||
// main package contains the cli functionality
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ivuorinen/go-test-sarif/internal"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 3 {
|
||||
fmt.Println("Usage: go-test-sarif <input.json> <output.sarif>")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
inputFile := os.Args[1]
|
||||
outputFile := os.Args[2]
|
||||
|
||||
err := internal.ConvertToSARIF(inputFile, outputFile)
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
75
internal/converter.go
Normal file
75
internal/converter.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// TestResult represents a single test result from 'go test -json' output.
|
||||
type TestResult struct {
|
||||
Action string `json:"Action"`
|
||||
Package string `json:"Package"`
|
||||
Output string `json:"Output"`
|
||||
}
|
||||
|
||||
// ConvertToSARIF converts Go test JSON results to SARIF format.
|
||||
func ConvertToSARIF(inputFile, outputFile string) error {
|
||||
// Read the input file
|
||||
data, err := ioutil.ReadFile(inputFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read input file: %w", err)
|
||||
}
|
||||
|
||||
// Parse the JSON data
|
||||
var testResults []TestResult
|
||||
if err := json.Unmarshal(data, &testResults); err != nil {
|
||||
return fmt.Errorf("invalid JSON format: %w", err)
|
||||
}
|
||||
|
||||
// Convert test results to SARIF format
|
||||
sarifData := map[string]interface{}{
|
||||
"version": "2.1.0",
|
||||
"runs": []map[string]interface{}{
|
||||
{
|
||||
"tool": map[string]interface{}{
|
||||
"driver": map[string]interface{}{
|
||||
"name": "go-test-sarif",
|
||||
"version": "1.0.0",
|
||||
},
|
||||
},
|
||||
"results": convertResults(testResults),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Marshal SARIF data to JSON
|
||||
sarifJSON, err := json.MarshalIndent(sarifData, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal SARIF data: %w", err)
|
||||
}
|
||||
|
||||
// Write the SARIF JSON to the output file
|
||||
if err := ioutil.WriteFile(outputFile, sarifJSON, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write SARIF output file: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("SARIF report generated: %s\n", outputFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertResults transforms test results into SARIF result objects.
|
||||
func convertResults(testResults []TestResult) []map[string]interface{} {
|
||||
var results []map[string]interface{}
|
||||
for _, tr := range testResults {
|
||||
if tr.Action == "fail" {
|
||||
results = append(results, map[string]interface{}{
|
||||
"ruleId": "go-test-failure",
|
||||
"message": map[string]string{"text": tr.Output},
|
||||
"level": "error",
|
||||
"locations": []map[string]interface{}{},
|
||||
})
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
94
internal/converter_test.go
Normal file
94
internal/converter_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestConvertToSARIF_Success tests the successful conversion of a valid Go test JSON output to SARIF format.
|
||||
func TestConvertToSARIF_Success(t *testing.T) {
|
||||
// Create a temporary JSON input file with valid test data
|
||||
inputFile, err := os.CreateTemp("", "test_input_*.json")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp input file: %v", err)
|
||||
}
|
||||
defer os.Remove(inputFile.Name())
|
||||
|
||||
inputContent := `[{"Action":"fail","Package":"github.com/ivuorinen/go-test-sarif/internal","Output":"Test failed"}]`
|
||||
if _, err := inputFile.WriteString(inputContent); err != nil {
|
||||
t.Fatalf("Failed to write to temp input file: %v", err)
|
||||
}
|
||||
|
||||
// Create a temporary SARIF output file
|
||||
outputFile, err := os.CreateTemp("", "test_output_*.sarif")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp output file: %v", err)
|
||||
}
|
||||
defer os.Remove(outputFile.Name())
|
||||
|
||||
// Run the ConvertToSARIF function
|
||||
err = ConvertToSARIF(inputFile.Name(), outputFile.Name())
|
||||
if err != nil {
|
||||
t.Errorf("ConvertToSARIF returned an error: %v", err)
|
||||
}
|
||||
|
||||
// Read and validate the SARIF output
|
||||
outputContent, err := os.ReadFile(outputFile.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read SARIF output file: %v", err)
|
||||
}
|
||||
|
||||
// Perform basic validation on the SARIF output
|
||||
if len(outputContent) == 0 {
|
||||
t.Errorf("SARIF output is empty")
|
||||
}
|
||||
|
||||
// Additional validations can be added here to verify the correctness of the SARIF content
|
||||
}
|
||||
|
||||
// TestConvertToSARIF_InvalidInput tests the function's behavior when provided with invalid JSON input.
|
||||
func TestConvertToSARIF_InvalidInput(t *testing.T) {
|
||||
// Create a temporary JSON input file with invalid test data
|
||||
inputFile, err := os.CreateTemp("", "test_input_invalid_*.json")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp input file: %v", err)
|
||||
}
|
||||
defer os.Remove(inputFile.Name())
|
||||
|
||||
inputContent := `{"Action":"fail","Package":"github.com/ivuorinen/go-test-sarif/internal","Output":Test failed}` // Missing quotes around 'Test failed'
|
||||
if _, err := inputFile.WriteString(inputContent); err != nil {
|
||||
t.Fatalf("Failed to write to temp input file: %v", err)
|
||||
}
|
||||
|
||||
// Create a temporary SARIF output file
|
||||
outputFile, err := os.CreateTemp("", "test_output_invalid_*.sarif")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp output file: %v", err)
|
||||
}
|
||||
defer os.Remove(outputFile.Name())
|
||||
|
||||
// Run the ConvertToSARIF function
|
||||
err = ConvertToSARIF(inputFile.Name(), outputFile.Name())
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error for invalid JSON input, but got none")
|
||||
}
|
||||
}
|
||||
|
||||
// TestConvertToSARIF_FileNotFound tests the function's behavior when the input file does not exist.
|
||||
func TestConvertToSARIF_FileNotFound(t *testing.T) {
|
||||
// Define a non-existent input file path
|
||||
inputFile := "non_existent_file.json"
|
||||
|
||||
// Create a temporary SARIF output file
|
||||
outputFile, err := os.CreateTemp("", "test_output_notfound_*.sarif")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp output file: %v", err)
|
||||
}
|
||||
defer os.Remove(outputFile.Name())
|
||||
|
||||
// Run the ConvertToSARIF function
|
||||
err = ConvertToSARIF(inputFile, outputFile.Name())
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error for non-existent input file, but got none")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user