Skip to content

Commit 817662b

Browse files
fix it:tests and codacy review warning fix
1 parent a7fc362 commit 817662b

File tree

4 files changed

+211
-79
lines changed

4 files changed

+211
-79
lines changed

cmd/upload_sbom.go

Lines changed: 103 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,17 @@ var (
2525
sbomTag string
2626
sbomRepoName string
2727
sbomEnv string
28-
sbomFormat string
28+
sbomFormat string
29+
sbomBaseURL string
30+
31+
sbomHTTPClient httpDoer = http.DefaultClient
2932
)
3033

34+
// httpDoer abstracts the Do method of http.Client for testing.
35+
type httpDoer interface {
36+
Do(req *http.Request) (*http.Response, error)
37+
}
38+
3139
func init() {
3240
uploadSBOMCmd.Flags().StringVarP(&sbomAPIToken, "api-token", "a", "", "API token for Codacy API (required)")
3341
uploadSBOMCmd.Flags().StringVarP(&sbomProvider, "provider", "p", "", "Git provider (gh, gl, bb) (required)")
@@ -85,28 +93,55 @@ func executeUploadSBOM(imageRef string) int {
8593
}
8694
sbomImageName = imageName
8795

96+
effectiveImageRef := fmt.Sprintf("%s:%s", imageName, tag)
97+
8898
logger.Info("Starting SBOM upload", logrus.Fields{
89-
"image": imageRef,
99+
"image": effectiveImageRef,
90100
"provider": sbomProvider,
91101
"org": sbomOrg,
92102
})
93103

94-
// Generate SBOM with Trivy
104+
sbomPath, err := generateSBOM(effectiveImageRef)
105+
if err != nil {
106+
return 2
107+
}
108+
defer os.Remove(sbomPath)
109+
110+
fmt.Printf("Uploading SBOM to Codacy (org: %s/%s)...\n", sbomProvider, sbomOrg)
111+
params := sbomUploadParams{
112+
provider: sbomProvider,
113+
org: sbomOrg,
114+
apiToken: sbomAPIToken,
115+
repoName: sbomRepoName,
116+
env: sbomEnv,
117+
baseURL: sbomBaseURL,
118+
}
119+
if err := uploadSBOMToCodacy(sbomPath, sbomImageName, tag, params); err != nil {
120+
logger.Error("Failed to upload SBOM", logrus.Fields{"error": err.Error()})
121+
color.Red("Error: Failed to upload SBOM: %v", err)
122+
return 1
123+
}
124+
125+
color.Green("Successfully uploaded SBOM for %s:%s", sbomImageName, tag)
126+
return 0
127+
}
128+
129+
// generateSBOM runs Trivy to generate an SBOM file and returns the path to it.
130+
func generateSBOM(imageRef string) (string, error) {
95131
trivyPath, err := getTrivyPath()
96132
if err != nil {
97133
handleTrivyNotFound(err)
98-
return 2
134+
return "", err
99135
}
100136

101137
tmpFile, err := os.CreateTemp("", "codacy-sbom-*")
102138
if err != nil {
103139
logger.Error("Failed to create temp file", logrus.Fields{"error": err.Error()})
104140
color.Red("Error: Failed to create temporary file: %v", err)
105-
return 2
141+
return "", err
106142
}
107143
tmpFile.Close()
108144
sbomPath := tmpFile.Name()
109-
defer os.Remove(sbomPath)
110145

111146
fmt.Printf("Generating SBOM for image: %s\n", imageRef)
112147
args := []string{"image", "--format", sbomFormat, "-o", sbomPath, imageRef}
@@ -120,20 +155,11 @@ func executeUploadSBOM(imageRef string) int {
120155
color.Red("Error: Failed to generate SBOM: %v", err)
121156
}
122157
logger.Error("Trivy SBOM generation failed", logrus.Fields{"error": err.Error()})
123-
return 2
158+
os.Remove(sbomPath)
159+
return "", err
124160
}
125161
fmt.Println("SBOM generated successfully")
126-
127-
// Upload SBOM to Codacy
128-
fmt.Printf("Uploading SBOM to Codacy (org: %s/%s)...\n", sbomProvider, sbomOrg)
129-
if err := uploadSBOMToCodacy(sbomPath, sbomImageName, tag); err != nil {
130-
logger.Error("Failed to upload SBOM", logrus.Fields{"error": err.Error()})
131-
color.Red("Error: Failed to upload SBOM: %v", err)
132-
return 1
133-
}
134-
135-
color.Green("Successfully uploaded SBOM for %s:%s", sbomImageName, tag)
136-
return 0
162+
return sbomPath, nil
137163
}
138164

139165
// parseImageRef splits an image reference into name and tag.
@@ -162,38 +188,31 @@ func parseImageRef(imageRef string) (string, string) {
162188
return imageRef, "latest"
163189
}
164190

165-
func uploadSBOMToCodacy(sbomPath, imageName, tag string) error {
166-
url := fmt.Sprintf("https://app.codacy.com/api/v3/organizations/%s/%s/image-sboms",
167-
sbomProvider, sbomOrg)
168-
169-
body := &bytes.Buffer{}
170-
writer := multipart.NewWriter(body)
191+
type sbomUploadParams struct {
192+
provider string
193+
org string
194+
apiToken string
195+
repoName string
196+
env string
197+
baseURL string
198+
}
171199

172-
// Add the SBOM file
173-
sbomFile, err := os.Open(sbomPath)
174-
if err != nil {
175-
return fmt.Errorf("failed to open SBOM file: %w", err)
200+
func (p sbomUploadParams) uploadURL() string {
201+
base := p.baseURL
202+
if base == "" {
203+
base = "https://app.codacy.com"
176204
}
177-
defer sbomFile.Close()
205+
return fmt.Sprintf("%s/api/v3/organizations/%s/%s/image-sboms", base, p.provider, p.org)
206+
}
178207

179-
part, err := writer.CreateFormFile("sbom", filepath.Base(sbomPath))
180-
if err != nil {
181-
return fmt.Errorf("failed to create form file: %w", err)
182-
}
183-
if _, err := io.Copy(part, sbomFile); err != nil {
184-
return fmt.Errorf("failed to write SBOM to form: %w", err)
185-
}
208+
func uploadSBOMToCodacy(sbomPath, imageName, tag string, params sbomUploadParams) error {
209+
url := params.uploadURL()
186210

187-
// Add required fields
188-
writer.WriteField("imageName", imageName)
189-
writer.WriteField("tag", tag)
211+
body := &bytes.Buffer{}
212+
writer := multipart.NewWriter(body)
190213

191-
// Add optional fields
192-
if sbomRepoName != "" {
193-
writer.WriteField("repositoryName", sbomRepoName)
194-
}
195-
if sbomEnv != "" {
196-
writer.WriteField("environment", sbomEnv)
214+
if err := buildSBOMMultipartForm(writer, sbomPath, imageName, tag, params); err != nil {
215+
return err
197216
}
198217

199218
if err := writer.Close(); err != nil {
@@ -206,9 +225,9 @@ func uploadSBOMToCodacy(sbomPath, imageName, tag string) error {
206225
}
207226
req.Header.Set("Content-Type", writer.FormDataContentType())
208227
req.Header.Set("Accept", "application/json")
209-
req.Header.Set("api-token", sbomAPIToken)
228+
req.Header.Set("api-token", params.apiToken)
210229

211-
resp, err := http.DefaultClient.Do(req)
230+
resp, err := sbomHTTPClient.Do(req)
212231
if err != nil {
213232
return fmt.Errorf("request failed: %w", err)
214233
}
@@ -221,3 +240,40 @@ func uploadSBOMToCodacy(sbomPath, imageName, tag string) error {
221240

222241
return nil
223242
}
243+
244+
// buildSBOMMultipartForm populates the multipart form with the SBOM file and metadata fields.
245+
func buildSBOMMultipartForm(writer *multipart.Writer, sbomPath, imageName, tag string, params sbomUploadParams) error {
246+
sbomFile, err := os.Open(sbomPath)
247+
if err != nil {
248+
return fmt.Errorf("failed to open SBOM file: %w", err)
249+
}
250+
defer sbomFile.Close()
251+
252+
part, err := writer.CreateFormFile("sbom", filepath.Base(sbomPath))
253+
if err != nil {
254+
return fmt.Errorf("failed to create form file: %w", err)
255+
}
256+
if _, err := io.Copy(part, sbomFile); err != nil {
257+
return fmt.Errorf("failed to write SBOM to form: %w", err)
258+
}
259+
260+
if err := writer.WriteField("imageName", imageName); err != nil {
261+
return fmt.Errorf("failed to write imageName field: %w", err)
262+
}
263+
if err := writer.WriteField("tag", tag); err != nil {
264+
return fmt.Errorf("failed to write tag field: %w", err)
265+
}
266+
267+
if params.repoName != "" {
268+
if err := writer.WriteField("repositoryName", params.repoName); err != nil {
269+
return fmt.Errorf("failed to write repositoryName field: %w", err)
270+
}
271+
}
272+
if params.env != "" {
273+
if err := writer.WriteField("environment", params.env); err != nil {
274+
return fmt.Errorf("failed to write environment field: %w", err)
275+
}
276+
}
277+
278+
return nil
279+
}

cmd/upload_sbom_test.go

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,37 @@ package cmd
33
import (
44
"errors"
55
"io"
6+
"net/http"
7+
"net/http/httptest"
68
"os"
79
"testing"
810

911
"github.com/stretchr/testify/assert"
1012
)
1113

1214
type sbomTestState struct {
13-
apiToken string
14-
provider string
15-
org string
16-
repoName string
17-
env string
18-
tag string
19-
format string
15+
apiToken string
16+
provider string
17+
org string
18+
repoName string
19+
env string
20+
tag string
21+
format string
22+
baseURL string
23+
httpClient httpDoer
2024
}
2125

2226
func saveSBOMState() sbomTestState {
2327
return sbomTestState{
24-
apiToken: sbomAPIToken,
25-
provider: sbomProvider,
26-
org: sbomOrg,
27-
repoName: sbomRepoName,
28-
env: sbomEnv,
29-
tag: sbomTag,
30-
format: sbomFormat,
28+
apiToken: sbomAPIToken,
29+
provider: sbomProvider,
30+
org: sbomOrg,
31+
repoName: sbomRepoName,
32+
env: sbomEnv,
33+
tag: sbomTag,
34+
format: sbomFormat,
35+
baseURL: sbomBaseURL,
36+
httpClient: sbomHTTPClient,
3137
}
3238
}
3339

@@ -39,6 +45,8 @@ func (s sbomTestState) restore() {
3945
sbomEnv = s.env
4046
sbomTag = s.tag
4147
sbomFormat = s.format
48+
sbomBaseURL = s.baseURL
49+
sbomHTTPClient = s.httpClient
4250
}
4351

4452
// setSBOMDefaults sets the minimum required SBOM globals for tests
@@ -50,6 +58,7 @@ func setSBOMDefaults() {
5058
sbomEnv = ""
5159
sbomTag = ""
5260
sbomFormat = "cyclonedx"
61+
sbomBaseURL = ""
5362
}
5463

5564
func TestParseImageRef(t *testing.T) {
@@ -169,11 +178,19 @@ func TestExecuteUploadSBOM_TrivyCalledWithCorrectFormat(t *testing.T) {
169178
},
170179
}
171180
commandRunner = mockRunner
181+
182+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
183+
w.WriteHeader(http.StatusNoContent)
184+
}))
185+
defer server.Close()
186+
172187
setSBOMDefaults()
173188
sbomFormat = format
189+
sbomBaseURL = server.URL
190+
sbomHTTPClient = server.Client()
174191

175-
// Will fail at upload (no real API), but we can verify Trivy args
176-
_ = executeUploadSBOM("alpine:latest")
192+
exitCode := executeUploadSBOM("alpine:latest")
193+
assert.Equal(t, 0, exitCode)
177194

178195
assert.Len(t, mockRunner.Calls, 1)
179196
assert.Contains(t, mockRunner.Calls[0].Args, "--format")
@@ -183,7 +200,12 @@ func TestExecuteUploadSBOM_TrivyCalledWithCorrectFormat(t *testing.T) {
183200
}
184201

185202
func TestUploadSBOMToCodacy_FileNotFound(t *testing.T) {
186-
err := uploadSBOMToCodacy("/nonexistent/file.json", "myapp", "latest")
203+
params := sbomUploadParams{
204+
provider: "gh",
205+
org: "test-org",
206+
apiToken: "test-token",
207+
}
208+
err := uploadSBOMToCodacy("/nonexistent/file.json", "myapp", "latest", params)
187209
assert.Error(t, err)
188210
assert.Contains(t, err.Error(), "failed to open SBOM file")
189211
}

integration-tests/init-with-token/expected/codacy.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ runtimes:
55
tools:
66
- eslint@8.57.0
77
- lizard@1.17.31
8-
- opengrep@1.16.4
8+
- opengrep@1.17.0
99
- pmd@6.55.0
1010
- pylint@3.3.9
1111
- trivy@0.69.3

0 commit comments

Comments
 (0)