Skip to content

feat: generate and upload trivy SBOM into codacy#200

Open
franciscoovazevedo wants to merge 1 commit intomainfrom
upload-trivy-sbom
Open

feat: generate and upload trivy SBOM into codacy#200
franciscoovazevedo wants to merge 1 commit intomainfrom
upload-trivy-sbom

Conversation

@franciscoovazevedo
Copy link
Copy Markdown
Contributor

No description provided.

Copilot AI review requested due to automatic review settings April 7, 2026 16:11
@codacy-production
Copy link
Copy Markdown

codacy-production bot commented Apr 7, 2026

Not up to standards ⛔

🔴 Issues 3 medium · 1 minor

Alerts:
⚠ 3 issues (≤ 1 issue of at least medium severity)

Results:
4 new issues

Category Results
Documentation 1 minor
Complexity 3 medium

View in Codacy

🟢 Metrics 53 complexity · 2 duplication

Metric Results
Complexity 53
Duplication 2

View in Codacy

🟢 Coverage 80.92% diff coverage · +1.46% coverage variation

Metric Results
Coverage variation +1.46% coverage variation (-0.50%)
Diff coverage 80.92% diff coverage (50.00%)

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (9b6cc83) 5997 1310 21.84%
Head commit (a7fc362) 6149 (+152) 1433 (+123) 23.30% (+1.46%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#200) 152 123 80.92%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.

Run reviewer

TIP This summary will be updated as you push new changes. Give us feedback

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new CLI command to generate (via Trivy) and upload an SPDX JSON SBOM to Codacy, reusing .codacy/cli-config.yaml metadata when available.

Changes:

  • Extend .codacy/cli-config.yaml parsing to include provider/org/repository and expose a GetCliConfig() accessor.
  • Add upload-sbom command to generate/read an SBOM and upload it to Codacy’s image SBOM endpoint.
  • Add unit tests for image ref parsing and SBOM upload behaviors; skip codacy.yaml validation for upload-sbom.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
config/config.go Extends CLI config model and adds GetCliConfig() for full YAML parsing.
cmd/validation.go Skips codacy.yaml validation for the new upload-sbom command.
cmd/upload_sbom.go Implements SBOM generation (Trivy) and multipart upload to Codacy.
cmd/upload_sbom_test.go Adds tests for parsing and SBOM upload flow (but currently includes an external network call).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +86 to +93
// Fill provider/organization/repository from cli-config.yaml if not set via flags
if sbomProvider == "" || sbomOrg == "" {
if cliConfig, err := config.Config.GetCliConfig(); err == nil {
if sbomProvider == "" && cliConfig.Provider != "" {
sbomProvider = cliConfig.Provider
}
if sbomOrg == "" && cliConfig.Organization != "" {
sbomOrg = cliConfig.Organization
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetCliConfig() errors (e.g., permission denied or YAML parse errors) are silently ignored here, which can hide the real reason provider/org weren’t loaded and results in a misleading “--provider/--organization required” message. Consider surfacing non-IsNotExist errors (warn and continue, or fail fast with the underlying error) to help users fix a broken .codacy/cli-config.yaml.

Copilot uses AI. Check for mistakes.
Comment on lines +206 to +209
func uploadSBOMToCodacy(sbomPath, imageName, tag string) error {
url := fmt.Sprintf("https://app.codacy.com/api/v3/organizations/%s/%s/image-sboms",
sbomProvider, sbomOrg)

Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uploadSBOMToCodacy relies on package-level globals (sbomProvider, sbomOrg, sbomAPIToken, etc.) instead of its parameters, which makes the function harder to test, non-reentrant, and more error-prone (state leaks between invocations). Consider passing provider/org/token/repo/env (and optionally an *http.Client/base URL) as explicit parameters or encapsulating them in a struct.

Copilot uses AI. Check for mistakes.
Comment on lines +229 to +237
writer.WriteField("imageName", imageName)
writer.WriteField("tag", tag)

// Add optional fields
if sbomRepoName != "" {
writer.WriteField("repositoryName", sbomRepoName)
}
if sbomEnv != "" {
writer.WriteField("environment", sbomEnv)
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Errors returned by writer.WriteField(...) are ignored. If these writes fail (e.g., due to an earlier multipart error), the request will be sent missing required/optional fields with no indication why. Handle and return these errors (especially for required fields like imageName and tag).

Suggested change
writer.WriteField("imageName", imageName)
writer.WriteField("tag", tag)
// Add optional fields
if sbomRepoName != "" {
writer.WriteField("repositoryName", sbomRepoName)
}
if sbomEnv != "" {
writer.WriteField("environment", sbomEnv)
if err := writer.WriteField("imageName", imageName); err != nil {
return fmt.Errorf("failed to write imageName field: %w", err)
}
if err := writer.WriteField("tag", tag); err != nil {
return fmt.Errorf("failed to write tag field: %w", err)
}
// Add optional fields
if sbomRepoName != "" {
if err := writer.WriteField("repositoryName", sbomRepoName); err != nil {
return fmt.Errorf("failed to write repositoryName field: %w", err)
}
}
if sbomEnv != "" {
if err := writer.WriteField("environment", sbomEnv); err != nil {
return fmt.Errorf("failed to write environment field: %w", err)
}

Copilot uses AI. Check for mistakes.
Comment on lines +252 to +255
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using http.DefaultClient without a timeout can cause the CLI to hang indefinitely on network issues. Prefer a dedicated http.Client{Timeout: ...} (there’s already a 10s timeout used in codacy-client/client.go) and/or allow injecting the client for tests.

Copilot uses AI. Check for mistakes.
Comment on lines +168 to +204
ss := saveSBOMState()
defer ss.restore()

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "POST", r.Method)
assert.Equal(t, "my-token", r.Header.Get("api-token"))
assert.Equal(t, "application/json", r.Header.Get("Accept"))

err := r.ParseMultipartForm(10 << 20)
assert.NoError(t, err)
assert.Equal(t, "myapp", r.FormValue("imageName"))
assert.Equal(t, "v1.0", r.FormValue("tag"))
assert.Equal(t, "my-repo", r.FormValue("repositoryName"))
assert.Equal(t, "prod", r.FormValue("environment"))

_, _, err = r.FormFile("sbom")
assert.NoError(t, err)

w.WriteHeader(http.StatusNoContent)
}))
defer server.Close()

// Create a temp SBOM file
tmpFile, err := os.CreateTemp("", "test-sbom-*.json")
assert.NoError(t, err)
tmpFile.WriteString(`{"spdxVersion": "SPDX-2.3"}`)
tmpFile.Close()
defer os.Remove(tmpFile.Name())

sbomAPIToken = "my-token"
sbomRepoName = "my-repo"
sbomEnv = "prod"

// This hits the real Codacy URL (can't easily override), so it will fail with a connection error.
// Verifies the function doesn't panic and correctly builds the request.
err = uploadSBOMToCodacy(tmpFile.Name(), "myapp", "v1.0")
assert.Error(t, err)
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test starts an httptest server but the production code always posts to https://app.codacy.com/..., so the test still performs a real network call and can be flaky/slow. Refactor uploadSBOMToCodacy to accept a base URL and/or http.Client so the test can hit server.URL and assert success (nil error) instead of asserting an error.

Suggested change
ss := saveSBOMState()
defer ss.restore()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "POST", r.Method)
assert.Equal(t, "my-token", r.Header.Get("api-token"))
assert.Equal(t, "application/json", r.Header.Get("Accept"))
err := r.ParseMultipartForm(10 << 20)
assert.NoError(t, err)
assert.Equal(t, "myapp", r.FormValue("imageName"))
assert.Equal(t, "v1.0", r.FormValue("tag"))
assert.Equal(t, "my-repo", r.FormValue("repositoryName"))
assert.Equal(t, "prod", r.FormValue("environment"))
_, _, err = r.FormFile("sbom")
assert.NoError(t, err)
w.WriteHeader(http.StatusNoContent)
}))
defer server.Close()
// Create a temp SBOM file
tmpFile, err := os.CreateTemp("", "test-sbom-*.json")
assert.NoError(t, err)
tmpFile.WriteString(`{"spdxVersion": "SPDX-2.3"}`)
tmpFile.Close()
defer os.Remove(tmpFile.Name())
sbomAPIToken = "my-token"
sbomRepoName = "my-repo"
sbomEnv = "prod"
// This hits the real Codacy URL (can't easily override), so it will fail with a connection error.
// Verifies the function doesn't panic and correctly builds the request.
err = uploadSBOMToCodacy(tmpFile.Name(), "myapp", "v1.0")
assert.Error(t, err)
t.Skip("uploadSBOMToCodacy currently posts to the fixed Codacy URL; refactor it to accept an injectable base URL and/or http.Client so this test can use httptest and assert success")

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants