Skip to content

Commit 36cd083

Browse files
fix it:tests and codacy review warning fix
1 parent a7fc362 commit 36cd083

File tree

6 files changed

+285
-84
lines changed

6 files changed

+285
-84
lines changed

cmd/upload_sbom.go

Lines changed: 123 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"path/filepath"
1111
"strings"
12+
"time"
1213

1314
"codacy/cli-v2/utils/logger"
1415

@@ -25,9 +26,17 @@ var (
2526
sbomTag string
2627
sbomRepoName string
2728
sbomEnv string
28-
sbomFormat string
29+
sbomFormat string
30+
sbomBaseURL string
31+
32+
sbomHTTPClient httpDoer = &http.Client{Timeout: 5 * time.Minute}
2933
)
3034

35+
// httpDoer abstracts the Do method of http.Client for testing.
36+
type httpDoer interface {
37+
Do(req *http.Request) (*http.Response, error)
38+
}
39+
3140
func init() {
3241
uploadSBOMCmd.Flags().StringVarP(&sbomAPIToken, "api-token", "a", "", "API token for Codacy API (required)")
3342
uploadSBOMCmd.Flags().StringVarP(&sbomProvider, "provider", "p", "", "Git provider (gh, gl, bb) (required)")
@@ -80,33 +89,71 @@ func executeUploadSBOM(imageRef string) int {
8089
}
8190

8291
imageName, tag := parseImageRef(imageRef)
92+
isDigest := strings.Contains(imageRef, "@")
93+
8394
if sbomTag != "" {
95+
if isDigest {
96+
color.Red("Error: --tag cannot be used with digest references (image@sha256:...)")
97+
return 2
98+
}
8499
tag = sbomTag
85100
}
86101
sbomImageName = imageName
87102

103+
var effectiveImageRef string
104+
if isDigest {
105+
effectiveImageRef = fmt.Sprintf("%s@%s", imageName, tag)
106+
} else {
107+
effectiveImageRef = fmt.Sprintf("%s:%s", imageName, tag)
108+
}
109+
88110
logger.Info("Starting SBOM upload", logrus.Fields{
89-
"image": imageRef,
111+
"image": effectiveImageRef,
90112
"provider": sbomProvider,
91113
"org": sbomOrg,
92114
})
93115

94-
// Generate SBOM with Trivy
116+
sbomPath, err := generateSBOM(effectiveImageRef)
117+
if err != nil {
118+
return 2
119+
}
120+
defer os.Remove(sbomPath)
121+
122+
fmt.Printf("Uploading SBOM to Codacy (org: %s/%s)...\n", sbomProvider, sbomOrg)
123+
params := sbomUploadParams{
124+
provider: sbomProvider,
125+
org: sbomOrg,
126+
apiToken: sbomAPIToken,
127+
repoName: sbomRepoName,
128+
env: sbomEnv,
129+
baseURL: sbomBaseURL,
130+
}
131+
if err := uploadSBOMToCodacy(sbomPath, sbomImageName, tag, params); err != nil {
132+
logger.Error("Failed to upload SBOM", logrus.Fields{"error": err.Error()})
133+
color.Red("Error: Failed to upload SBOM: %v", err)
134+
return 1
135+
}
136+
137+
color.Green("Successfully uploaded SBOM for %s", effectiveImageRef)
138+
return 0
139+
}
140+
141+
// generateSBOM runs Trivy to generate an SBOM file and returns the path to it.
142+
func generateSBOM(imageRef string) (string, error) {
95143
trivyPath, err := getTrivyPath()
96144
if err != nil {
97145
handleTrivyNotFound(err)
98-
return 2
146+
return "", err
99147
}
100148

101149
tmpFile, err := os.CreateTemp("", "codacy-sbom-*")
102150
if err != nil {
103151
logger.Error("Failed to create temp file", logrus.Fields{"error": err.Error()})
104152
color.Red("Error: Failed to create temporary file: %v", err)
105-
return 2
153+
return "", err
106154
}
107155
tmpFile.Close()
108156
sbomPath := tmpFile.Name()
109-
defer os.Remove(sbomPath)
110157

111158
fmt.Printf("Generating SBOM for image: %s\n", imageRef)
112159
args := []string{"image", "--format", sbomFormat, "-o", sbomPath, imageRef}
@@ -120,20 +167,11 @@ func executeUploadSBOM(imageRef string) int {
120167
color.Red("Error: Failed to generate SBOM: %v", err)
121168
}
122169
logger.Error("Trivy SBOM generation failed", logrus.Fields{"error": err.Error()})
123-
return 2
170+
os.Remove(sbomPath)
171+
return "", err
124172
}
125173
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
174+
return sbomPath, nil
137175
}
138176

139177
// parseImageRef splits an image reference into name and tag.
@@ -162,38 +200,31 @@ func parseImageRef(imageRef string) (string, string) {
162200
return imageRef, "latest"
163201
}
164202

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)
203+
type sbomUploadParams struct {
204+
provider string
205+
org string
206+
apiToken string
207+
repoName string
208+
env string
209+
baseURL string
210+
}
171211

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)
212+
func (p sbomUploadParams) uploadURL() string {
213+
base := p.baseURL
214+
if base == "" {
215+
base = "https://app.codacy.com"
176216
}
177-
defer sbomFile.Close()
217+
return fmt.Sprintf("%s/api/v3/organizations/%s/%s/image-sboms", base, p.provider, p.org)
218+
}
178219

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-
}
220+
func uploadSBOMToCodacy(sbomPath, imageName, tag string, params sbomUploadParams) error {
221+
url := params.uploadURL()
186222

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

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

199230
if err := writer.Close(); err != nil {
@@ -206,9 +237,9 @@ func uploadSBOMToCodacy(sbomPath, imageName, tag string) error {
206237
}
207238
req.Header.Set("Content-Type", writer.FormDataContentType())
208239
req.Header.Set("Accept", "application/json")
209-
req.Header.Set("api-token", sbomAPIToken)
240+
req.Header.Set("api-token", params.apiToken)
210241

211-
resp, err := http.DefaultClient.Do(req)
242+
resp, err := sbomHTTPClient.Do(req)
212243
if err != nil {
213244
return fmt.Errorf("request failed: %w", err)
214245
}
@@ -221,3 +252,48 @@ func uploadSBOMToCodacy(sbomPath, imageName, tag string) error {
221252

222253
return nil
223254
}
255+
256+
// buildSBOMMultipartForm populates the multipart form with the SBOM file and metadata fields.
257+
func buildSBOMMultipartForm(writer *multipart.Writer, sbomPath, imageName, tag string, params sbomUploadParams) error {
258+
if err := addSBOMFile(writer, sbomPath); err != nil {
259+
return err
260+
}
261+
262+
fields := map[string]string{
263+
"imageName": imageName,
264+
"tag": tag,
265+
}
266+
if params.repoName != "" {
267+
fields["repositoryName"] = params.repoName
268+
}
269+
if params.env != "" {
270+
fields["environment"] = params.env
271+
}
272+
273+
for name, value := range fields {
274+
if err := writer.WriteField(name, value); err != nil {
275+
return fmt.Errorf("failed to write %s field: %w", name, err)
276+
}
277+
}
278+
279+
return nil
280+
}
281+
282+
// addSBOMFile adds the SBOM file to the multipart form.
283+
func addSBOMFile(writer *multipart.Writer, sbomPath string) error {
284+
sbomFile, err := os.Open(sbomPath)
285+
if err != nil {
286+
return fmt.Errorf("failed to open SBOM file: %w", err)
287+
}
288+
defer sbomFile.Close()
289+
290+
part, err := writer.CreateFormFile("sbom", filepath.Base(sbomPath))
291+
if err != nil {
292+
return fmt.Errorf("failed to create form file: %w", err)
293+
}
294+
if _, err := io.Copy(part, sbomFile); err != nil {
295+
return fmt.Errorf("failed to write SBOM to form: %w", err)
296+
}
297+
298+
return nil
299+
}

0 commit comments

Comments
 (0)