feat: the app (#2)

This commit is contained in:
2025-03-24 00:38:41 +02:00
committed by GitHub
parent 3470e255a7
commit e904b1beb3
12 changed files with 416 additions and 123 deletions

122
.github/README.md vendored Normal file
View File

@@ -0,0 +1,122 @@
# go-test-sarif
[![CI](https://github.com/ivuorinen/go-test-sarif/actions/workflows/test.yml/badge.svg)](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.

View File

@@ -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
View 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
View File

@@ -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
View 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
View 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
View 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
View File

25
cmd/main.go Normal file
View 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)
}
}

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/ivuorinen/go-test-sarif
go 1.24.1

75
internal/converter.go Normal file
View 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
}

View 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")
}
}