Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion go/extractor/diagnostics/BUILD.bazel

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

104 changes: 67 additions & 37 deletions go/extractor/diagnostics/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package diagnostics
import (
"encoding/json"
"fmt"
"log"
"log/slog"
"os"
"strings"
"time"
Expand Down Expand Up @@ -56,18 +56,65 @@ type diagnostic struct {
var diagnosticsEmitted, diagnosticsLimit uint = 0, 100
var noDiagnosticDirPrinted bool = false

func emitDiagnostic(sourceid, sourcename, markdownMessage string, severity diagnosticSeverity, visibility *visibilityStruct, location *locationStruct) {
if diagnosticsEmitted < diagnosticsLimit {
diagnosticsEmitted += 1
type DiagnosticsWriter interface {
WriteDiagnostic(d diagnostic)
}

diagnosticDir := os.Getenv("CODEQL_EXTRACTOR_GO_DIAGNOSTIC_DIR")
if diagnosticDir == "" {
if !noDiagnosticDirPrinted {
log.Println("No diagnostic directory set, so not emitting diagnostic")
noDiagnosticDirPrinted = true
}
return
type FileDiagnosticsWriter struct {
diagnosticDir string
}

func (writer *FileDiagnosticsWriter) WriteDiagnostic(d diagnostic) {
if writer == nil {
return
}

content, err := json.Marshal(d)
if err != nil {
slog.Error("Failed to encode diagnostic as JSON", slog.Any("err", err))
return
}

targetFile, err := os.CreateTemp(writer.diagnosticDir, "go-extractor.*.json")
if err != nil {
slog.Error("Failed to create diagnostic file", slog.Any("err", err))
return
}
defer func() {
if err := targetFile.Close(); err != nil {
slog.Error("Failed to close diagnostic file", slog.Any("err", err))
}
}()

_, err = targetFile.Write(content)
if err != nil {
slog.Error("Failed to write to diagnostic file", slog.Any("err", err))
}
}

var DefaultWriter *FileDiagnosticsWriter = nil

func NewFileDiagnosticsWriter() *FileDiagnosticsWriter {
diagnosticDir := os.Getenv("CODEQL_EXTRACTOR_GO_DIAGNOSTIC_DIR")
if diagnosticDir == "" {
if !noDiagnosticDirPrinted {
slog.Warn("No diagnostic directory set, so not emitting diagnostics")
noDiagnosticDirPrinted = true
}
return nil
}

return &FileDiagnosticsWriter{diagnosticDir}
}

func init() {
DefaultWriter = NewFileDiagnosticsWriter()
}

// Emits a diagnostic using the specified `DiagnosticsWriter`.
func emitDiagnosticTo(writer DiagnosticsWriter, sourceid, sourcename, markdownMessage string, severity diagnosticSeverity, visibility *visibilityStruct, location *locationStruct) {
if diagnosticsEmitted < diagnosticsLimit {
diagnosticsEmitted += 1

timestamp := time.Now().UTC().Format("2006-01-02T15:04:05.000") + "Z"

Expand All @@ -93,33 +140,15 @@ func emitDiagnostic(sourceid, sourcename, markdownMessage string, severity diagn
}
}

content, err := json.Marshal(d)
if err != nil {
log.Println(err)
return
}

targetFile, err := os.CreateTemp(diagnosticDir, "go-extractor.*.json")
if err != nil {
log.Println("Failed to create diagnostic file: ")
log.Println(err)
return
}
defer func() {
if err := targetFile.Close(); err != nil {
log.Println("Failed to close diagnostic file:")
log.Println(err)
}
}()

_, err = targetFile.Write(content)
if err != nil {
log.Println("Failed to write to diagnostic file: ")
log.Println(err)
}
writer.WriteDiagnostic(d)
}
}

// Emits a diagnostic using the default `DiagnosticsWriter`.
func emitDiagnostic(sourceid, sourcename, markdownMessage string, severity diagnosticSeverity, visibility *visibilityStruct, location *locationStruct) {
emitDiagnosticTo(DefaultWriter, sourceid, sourcename, markdownMessage, severity, visibility, location)
}

func EmitPackageDifferentOSArchitecture(pkgPath string) {
emitDiagnostic(
"go/autobuilder/package-different-os-architecture",
Expand All @@ -141,7 +170,7 @@ func plural(n int, singular, plural string) string {

const maxNumPkgPaths = 5

func EmitCannotFindPackages(pkgPaths []string) {
func EmitCannotFindPackages(writer DiagnosticsWriter, pkgPaths []string) {
numPkgPaths := len(pkgPaths)

numPrinted := numPkgPaths
Expand Down Expand Up @@ -188,7 +217,8 @@ func EmitCannotFindPackages(pkgPaths []string) {
"If any of the packages are already present in the repository, but were not found, then you may need a [custom build command](https://docs.github.com/en/code-security/how-tos/scan-code-for-vulnerabilities/manage-your-configuration/codeql-code-scanning-for-compiled-languages)."
}

emitDiagnostic(
emitDiagnosticTo(
writer,
"go/autobuilder/package-not-found",
"Some packages could not be found",
message,
Expand Down
85 changes: 85 additions & 0 deletions go/extractor/diagnostics/diagnostics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package diagnostics

import (
"testing"

"github.com/stretchr/testify/assert"
)

type memoryDiagnosticsWriter struct {
diagnostics []diagnostic
}

func newMemoryDiagnosticsWriter() *memoryDiagnosticsWriter {
return &memoryDiagnosticsWriter{[]diagnostic{}}
}

func (writer *memoryDiagnosticsWriter) WriteDiagnostic(d diagnostic) {
writer.diagnostics = append(writer.diagnostics, d)
}

func Test_EmitCannotFindPackages_Default(t *testing.T) {
writer := newMemoryDiagnosticsWriter()

// Clear environment variables that affect the diagnostic message.
t.Setenv("GITHUB_EVENT_NAME", "")
t.Setenv("GITHUB_ACTIONS", "")

EmitCannotFindPackages(writer, []string{"github.com/github/foo"})

assert.Len(t, writer.diagnostics, 1, "Expected one diagnostic to be emitted")

d := writer.diagnostics[0]
assert.Equal(t, d.Source.Id, "go/autobuilder/package-not-found")
assert.Equal(t, d.Severity, string(severityWarning))
assert.True(t, d.Visibility.CliSummaryTable)
assert.True(t, d.Visibility.StatusPage)
assert.True(t, d.Visibility.Telemetry)
// Non-Actions suggestion for private registries
assert.Contains(t, d.MarkdownMessage, "ensure that the necessary credentials and environment variables are set up")
// Custom build command suggestion
assert.Contains(t, d.MarkdownMessage, "If any of the packages are already present in the repository")
}

func Test_EmitCannotFindPackages_Dynamic(t *testing.T) {
writer := newMemoryDiagnosticsWriter()

// Set environment variables that affect the diagnostic message.
t.Setenv("GITHUB_EVENT_NAME", "dynamic")
t.Setenv("GITHUB_ACTIONS", "true")

EmitCannotFindPackages(writer, []string{"github.com/github/foo"})

assert.Len(t, writer.diagnostics, 1, "Expected one diagnostic to be emitted")

d := writer.diagnostics[0]
assert.Equal(t, d.Source.Id, "go/autobuilder/package-not-found")
assert.Equal(t, d.Severity, string(severityWarning))
// Dynamic workflow suggestion for private registries
assert.Contains(t, d.MarkdownMessage, "can grant access to private registries for GitHub security products")
// No default suggestions for private registries and custom build command
assert.NotContains(t, d.MarkdownMessage, "ensure that the necessary credentials and environment variables are set up")
assert.NotContains(t, d.MarkdownMessage, "If any of the packages are already present in the repository")
}

func Test_EmitCannotFindPackages_Actions(t *testing.T) {
writer := newMemoryDiagnosticsWriter()

// Set environment variables that affect the diagnostic message.
t.Setenv("GITHUB_EVENT_NAME", "push")
t.Setenv("GITHUB_ACTIONS", "true")

EmitCannotFindPackages(writer, []string{"github.com/github/foo"})

assert.Len(t, writer.diagnostics, 1, "Expected one diagnostic to be emitted")

d := writer.diagnostics[0]
assert.Equal(t, d.Source.Id, "go/autobuilder/package-not-found")
assert.Equal(t, d.Severity, string(severityWarning))
// Advanced workflow suggestion for private registries
assert.Contains(t, d.MarkdownMessage, "add a step to your workflow which sets up")
// No default suggestion for private registries
assert.NotContains(t, d.MarkdownMessage, "ensure that the necessary credentials and environment variables are set up")
// Custom build command suggestion
assert.Contains(t, d.MarkdownMessage, "If any of the packages are already present in the repository")
}
2 changes: 1 addition & 1 deletion go/extractor/extractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func ExtractWithFlags(buildFlags []string, patterns []string, extractTests bool,
})

if len(pkgsNotFound) > 0 {
diagnostics.EmitCannotFindPackages(pkgsNotFound)
diagnostics.EmitCannotFindPackages(diagnostics.DefaultWriter, pkgsNotFound)
}

for _, pkg := range pkgs {
Expand Down
8 changes: 7 additions & 1 deletion go/extractor/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,10 @@ require (
golang.org/x/tools v0.41.0
)

require golang.org/x/sync v0.19.0 // indirect
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.11.1 // indirect
golang.org/x/sync v0.19.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
9 changes: 9 additions & 0 deletions go/extractor/go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=