Skip to content

Commit d94d2be

Browse files
committed
chore: linting a mix of problems new to this branch and preexisting in the last refactor
- rename package output_workflow > outputworkflow - relax some linters to match GAF - wrap errors - add many missing comments - refactor RunTest() and others to reduce complexity - import ordering - check bounds before casting ints (on uniqueCount) - remove duplicate severity color function - change CAPS naming on consts to match Go styling - follow guidelines to tighten security on file writes -> 0x644 to 0x600 - Go naming for Id -> ID and Json -> JSON - ignore ireturn warning since we *want* to return an interface in our imported template write function - reorder parameters - ... more I'm forgetting
1 parent 69beb77 commit d94d2be

34 files changed

+664
-420
lines changed

.golangci.yaml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@ linters-settings:
3636
enable-all: true
3737
disable:
3838
- fieldalignment
39-
ireturn:
40-
allow:
41-
- error
42-
- github.com/snyk/go-application-framework/pkg/workflow.Data
4339
lll:
4440
line-length: 160
4541
misspell:
@@ -122,7 +118,8 @@ linters:
122118
- goprintffuncname
123119
- gosec
124120
- interfacebloat
125-
- ireturn
121+
# TODO(ireturn): revisit in a followup; required for the output workflow
122+
#- ireturn
126123
- lll
127124
- misspell
128125
- nakedret
@@ -152,6 +149,11 @@ linters:
152149

153150
issues:
154151
exclude-rules:
152+
- path: internal/legacy/definitions/oapi\.gen\.go
153+
linters:
154+
- godot
155+
- tagliatelle
156+
- wrapcheck
155157
- path: _test\.go
156158
linters:
157159
- bodyclose
@@ -163,4 +165,4 @@ issues:
163165
- testpackage
164166
include:
165167
- EXC0012
166-
- EXC0014
168+
- EXC0014

internal/bundlestore/client.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,15 @@ func NewClient(httpClient *http.Client, csConfig CodeClientConfig, cScanner code
6262
}
6363
}
6464

65+
// host returns the appropriate host URL based on configuration.
6566
func (c *HTTPClient) host() string {
6667
if c.CodeClientConfig.IsFedramp() {
6768
return c.SnykApi() + "/hidden/orgs/" + c.CodeClientConfig.Organization() + "/code"
6869
}
6970
return c.SnykCodeApi()
7071
}
7172

73+
// request sends an HTTP request to the bundle store.
7274
func (c *HTTPClient) request(
7375
ctx context.Context,
7476
method string,
@@ -117,6 +119,8 @@ func (c *HTTPClient) request(
117119
return responseBody, nil
118120
}
119121

122+
// createBundle creates a new bundle with the given file hashes.
123+
//
120124
//nolint:gocritic // Code copied verbatim from code-client-go
121125
func (c *HTTPClient) createBundle(ctx context.Context, fileHashes map[string]string) (string, []string, error) {
122126
requestBody, err := json.Marshal(fileHashes)
@@ -137,6 +141,8 @@ func (c *HTTPClient) createBundle(ctx context.Context, fileHashes map[string]str
137141
return bundle.BundleHash, bundle.MissingFiles, nil
138142
}
139143

144+
// extendBundle extends an existing bundle with new or removed files.
145+
//
140146
//nolint:gocritic // Code copied verbatim from code-client-go
141147
func (c *HTTPClient) extendBundle(ctx context.Context, bundleHash string, files map[string]BundleFile, removedFiles []string) (string, []string, error) {
142148
requestBody, err := json.Marshal(ExtendBundleRequest{

internal/commands/ostest/depgraph_flow.go

Lines changed: 139 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
"github.com/snyk/cli-extension-os-flows/internal/errors"
1818
"github.com/snyk/cli-extension-os-flows/internal/flags"
1919
"github.com/snyk/cli-extension-os-flows/internal/legacy/definitions"
20-
"github.com/snyk/cli-extension-os-flows/internal/output_workflow"
20+
"github.com/snyk/cli-extension-os-flows/internal/outputworkflow"
2121
)
2222

2323
// RunUnifiedTestFlow handles the unified test API flow.
@@ -38,50 +38,63 @@ func RunUnifiedTestFlow(
3838
if err != nil {
3939
return nil, err
4040
}
41-
var allLegacyFindings []definitions.LegacyVulnerabilityResponse
42-
var allOutputData []workflow.Data
4341

4442
localPolicy := createLocalPolicy(riskScoreThreshold, severityThreshold)
4543

44+
allLegacyFindings, allOutputData, err := testAllDepGraphs(
45+
ctx,
46+
ictx,
47+
testClient,
48+
orgID,
49+
errFactory,
50+
logger,
51+
localPolicy,
52+
depGraphs,
53+
displayTargetFiles,
54+
)
55+
if err != nil {
56+
return nil, err
57+
}
58+
59+
//nolint:contextcheck // The handleOutput call chain is not context-aware
60+
return handleOutput(ictx, allLegacyFindings, allOutputData, errFactory)
61+
}
62+
63+
func testAllDepGraphs(
64+
ctx context.Context,
65+
ictx workflow.InvocationContext,
66+
testClient testapi.TestClient,
67+
orgID string,
68+
errFactory *errors.ErrorFactory,
69+
logger *zerolog.Logger,
70+
localPolicy *testapi.LocalPolicy,
71+
depGraphs []*testapi.IoSnykApiV1testdepgraphRequestDepGraph,
72+
displayTargetFiles []string,
73+
) ([]definitions.LegacyVulnerabilityResponse, []workflow.Data, error) {
74+
var allLegacyFindings []definitions.LegacyVulnerabilityResponse
75+
var allOutputData []workflow.Data
76+
4677
for i, depGraph := range depGraphs {
4778
displayTargetFile := ""
4879
if i < len(displayTargetFiles) {
4980
displayTargetFile = displayTargetFiles[i]
5081
}
5182

52-
// Create depgraph subject
53-
depGraphSubject := testapi.DepGraphSubjectCreate{
54-
Type: testapi.DepGraphSubjectCreateTypeDepGraph,
55-
DepGraph: depGraph,
56-
Locator: testapi.LocalPathLocator{
57-
Paths: []string{displayTargetFile},
58-
Type: testapi.LocalPath,
59-
},
60-
}
61-
62-
// Create test subject with depgraph
63-
var subject testapi.TestSubjectCreate
64-
err = subject.FromDepGraphSubjectCreate(depGraphSubject)
83+
subject, err := createTestSubject(depGraph, displayTargetFile)
6584
if err != nil {
66-
return nil, fmt.Errorf("failed to create test subject: %w", err)
67-
}
68-
69-
// Project name assigned as follows: --project-name || config project name || scannedProject?.depTree?.name
70-
// TODO: use project name from Config file
71-
config := ictx.GetConfiguration()
72-
projectName := config.GetString(flags.FlagProjectName)
73-
if projectName == "" && len(depGraph.Pkgs) > 0 {
74-
projectName = depGraph.Pkgs[0].Info.Name
85+
return nil, nil, err
7586
}
7687

88+
projectName := getProjectName(ictx, depGraph)
7789
packageManager := depGraph.PkgManager.Name
7890
depCount := max(0, len(depGraph.Pkgs)-1)
7991

8092
// Run the test with the depgraph subject
81-
legacyFinding, outputData, err := RunTest(ctx, ictx, testClient, subject, projectName, packageManager, depCount, displayTargetFile, orgID, errFactory, logger, localPolicy)
82-
93+
legacyFinding, outputData, err := RunTest(
94+
ctx, ictx, testClient, subject, projectName, packageManager, depCount,
95+
displayTargetFile, orgID, errFactory, logger, localPolicy)
8396
if err != nil {
84-
return nil, err
97+
return nil, nil, err
8598
}
8699

87100
if legacyFinding != nil {
@@ -90,59 +103,92 @@ func RunUnifiedTestFlow(
90103
allOutputData = append(allOutputData, outputData...)
91104
}
92105

106+
return allLegacyFindings, allOutputData, nil
107+
}
108+
109+
func createTestSubject(
110+
depGraph *testapi.IoSnykApiV1testdepgraphRequestDepGraph,
111+
displayTargetFile string,
112+
) (testapi.TestSubjectCreate, error) {
113+
// Create depgraph subject
114+
depGraphSubject := testapi.DepGraphSubjectCreate{
115+
Type: testapi.DepGraphSubjectCreateTypeDepGraph,
116+
DepGraph: *depGraph,
117+
Locator: testapi.LocalPathLocator{
118+
Paths: []string{displayTargetFile},
119+
Type: testapi.LocalPath,
120+
},
121+
}
122+
123+
// Create test subject with depgraph
124+
var subject testapi.TestSubjectCreate
125+
err := subject.FromDepGraphSubjectCreate(depGraphSubject)
126+
if err != nil {
127+
return subject, fmt.Errorf("failed to create test subject: %w", err)
128+
}
129+
return subject, nil
130+
}
131+
132+
func getProjectName(
133+
ictx workflow.InvocationContext,
134+
depGraph *testapi.IoSnykApiV1testdepgraphRequestDepGraph,
135+
) string {
136+
// Project name assigned as follows: --project-name || config project name || scannedProject?.depTree?.name
137+
// TODO: use project name from Config file
138+
config := ictx.GetConfiguration()
139+
projectName := config.GetString(flags.FlagProjectName)
140+
if projectName == "" && len(depGraph.Pkgs) > 0 {
141+
projectName = depGraph.Pkgs[0].Info.Name
142+
}
143+
return projectName
144+
}
145+
146+
func handleOutput(
147+
ictx workflow.InvocationContext,
148+
allLegacyFindings []definitions.LegacyVulnerabilityResponse,
149+
allOutputData []workflow.Data,
150+
errFactory *errors.ErrorFactory,
151+
) ([]workflow.Data, error) {
93152
config := ictx.GetConfiguration()
94153
jsonOutput := config.GetBool("json")
95-
jsonFileOutput := config.GetString(output_workflow.OUTPUT_CONFIG_KEY_JSON_FILE)
154+
jsonFileOutput := config.GetString(outputworkflow.OutputConfigKeyJSONFile)
96155

97156
// Human-readable output is suppressed only when --json is specified.
98157
wantsHumanReadable := !jsonOutput
158+
wantsJSONFile := jsonFileOutput != ""
159+
wantsJSONStdOut := jsonOutput
99160

100161
var finalOutput []workflow.Data
101162
if wantsHumanReadable {
102-
outputDestination := output_workflow.NewOutputDestination()
163+
outputDestination := outputworkflow.NewOutputDestination()
103164
// The output workflow returns data it did not handle, like test summaries for exit code calculation.
104-
remainingData, err := output_workflow.OutputWorkflowEntryPoint(ictx, allOutputData, outputDestination)
165+
remainingData, err := outputworkflow.EntryPoint(ictx, allOutputData, outputDestination)
105166
if err != nil {
106-
return nil, err
167+
return nil, fmt.Errorf("failed to process output workflow: %w", err)
107168
}
108169
finalOutput = append(finalOutput, remainingData...)
109170
}
110171

111172
// Handle JSON output to a file or stdout.
112-
wantsJSONFile := jsonFileOutput != ""
113-
wantsJSONStdOut := jsonOutput
114-
115-
if wantsJSONFile || wantsJSONStdOut {
116-
if len(allLegacyFindings) > 0 {
117-
var findingsData any
118-
if len(allLegacyFindings) == 1 {
119-
findingsData = allLegacyFindings[0]
120-
} else {
121-
findingsData = allLegacyFindings
122-
}
123-
124-
var buffer bytes.Buffer
125-
encoder := json.NewEncoder(&buffer)
126-
encoder.SetEscapeHTML(false)
127-
encoder.SetIndent("", " ")
128-
if err = encoder.Encode(findingsData); err != nil {
129-
return nil, errFactory.NewLegacyJSONTransformerError(fmt.Errorf("marshaling to json: %w", err))
130-
}
131-
// encoder.Encode adds a newline, which we trim to match Marshal's behavior.
132-
findingsBytes := bytes.TrimRight(buffer.Bytes(), "\n")
173+
if !wantsJSONFile && !wantsJSONStdOut || len(allLegacyFindings) == 0 {
174+
return finalOutput, nil
175+
}
133176

134-
if wantsJSONFile {
135-
if err := os.WriteFile(jsonFileOutput, findingsBytes, 0644); err != nil {
136-
return nil, fmt.Errorf("failed to write JSON output to file: %w", err)
137-
}
138-
}
177+
jsonBytes, err := prepareJSONOutput(allLegacyFindings, errFactory)
178+
if err != nil {
179+
return nil, err
180+
}
139181

140-
if wantsJSONStdOut {
141-
finalOutput = append(finalOutput, NewWorkflowData(ApplicationJSONContentType, findingsBytes))
142-
}
182+
if wantsJSONFile {
183+
if err := os.WriteFile(jsonFileOutput, jsonBytes, 0o600); err != nil {
184+
return nil, fmt.Errorf("failed to write JSON output to file: %w", err)
143185
}
144186
}
145187

188+
if wantsJSONStdOut {
189+
finalOutput = append(finalOutput, NewWorkflowData(ApplicationJSONContentType, jsonBytes))
190+
}
191+
146192
// If only JSON output to stdout was requested, we still need the summary for the exit code.
147193
if wantsJSONStdOut && !wantsHumanReadable {
148194
for _, d := range allOutputData {
@@ -155,24 +201,46 @@ func RunUnifiedTestFlow(
155201
return finalOutput, nil
156202
}
157203

204+
func prepareJSONOutput(
205+
allLegacyFindings []definitions.LegacyVulnerabilityResponse,
206+
errFactory *errors.ErrorFactory,
207+
) ([]byte, error) {
208+
if len(allLegacyFindings) == 0 {
209+
return nil, nil
210+
}
211+
212+
var findingsData any
213+
if len(allLegacyFindings) == 1 {
214+
findingsData = allLegacyFindings[0]
215+
} else {
216+
findingsData = allLegacyFindings
217+
}
218+
219+
var buffer bytes.Buffer
220+
encoder := json.NewEncoder(&buffer)
221+
encoder.SetEscapeHTML(false)
222+
encoder.SetIndent("", " ")
223+
if err := encoder.Encode(findingsData); err != nil {
224+
return nil, errFactory.NewLegacyJSONTransformerError(fmt.Errorf("marshaling to json: %w", err))
225+
}
226+
// encoder.Encode adds a newline, which we trim to match Marshal's behavior.
227+
return bytes.TrimRight(buffer.Bytes(), "\n"), nil
228+
}
229+
158230
// Create local policy only if risk score or severity threshold are specified.
159231
func createLocalPolicy(riskScoreThreshold *uint16, severityThreshold *testapi.Severity) *testapi.LocalPolicy {
160232
if riskScoreThreshold == nil && severityThreshold == nil {
161233
return nil
162234
}
163235

164-
localPolicy := &testapi.LocalPolicy{}
165-
if riskScoreThreshold != nil {
166-
localPolicy.RiskScoreThreshold = riskScoreThreshold
167-
}
168-
if severityThreshold != nil {
169-
localPolicy.SeverityThreshold = severityThreshold
236+
return &testapi.LocalPolicy{
237+
RiskScoreThreshold: riskScoreThreshold,
238+
SeverityThreshold: severityThreshold,
170239
}
171-
return localPolicy
172240
}
173241

174242
// createDepGraphs creates depgraphs from the file parameter in the context.
175-
func createDepGraphs(ictx workflow.InvocationContext) ([]testapi.IoSnykApiV1testdepgraphRequestDepGraph, []string, error) {
243+
func createDepGraphs(ictx workflow.InvocationContext) ([]*testapi.IoSnykApiV1testdepgraphRequestDepGraph, []string, error) {
176244
depGraphResult, err := service.GetDepGraph(ictx)
177245
if err != nil {
178246
return nil, nil, fmt.Errorf("failed to get dependency graph: %w", err)
@@ -182,15 +250,15 @@ func createDepGraphs(ictx workflow.InvocationContext) ([]testapi.IoSnykApiV1test
182250
return nil, nil, fmt.Errorf("no dependency graphs found")
183251
}
184252

185-
depGraphs := make([]testapi.IoSnykApiV1testdepgraphRequestDepGraph, len(depGraphResult.DepGraphBytes))
253+
depGraphs := make([]*testapi.IoSnykApiV1testdepgraphRequestDepGraph, len(depGraphResult.DepGraphBytes))
186254
for i, depGraphBytes := range depGraphResult.DepGraphBytes {
187255
var depGraphStruct testapi.IoSnykApiV1testdepgraphRequestDepGraph
188256
err = json.Unmarshal(depGraphBytes, &depGraphStruct)
189257
if err != nil {
190258
return nil, nil,
191259
fmt.Errorf("unmarshaling depGraph from args failed: %w", err)
192260
}
193-
depGraphs[i] = depGraphStruct
261+
depGraphs[i] = &depGraphStruct
194262
}
195263

196264
return depGraphs, depGraphResult.DisplayTargetFiles, nil

internal/commands/ostest/sbom_reachability_flow.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ func RunSbomReachabilityFlow(
4747
return nil, nil // TODO: return something meaningful once this function is complete
4848
}
4949

50+
// validateDirectory checks if the given path exists and contains files.
5051
func validateDirectory(sourceCodePath string, logger *zerolog.Logger, errFactory *errors.ErrorFactory) error {
5152
exists, err := dirExists(sourceCodePath)
5253
if err != nil {
@@ -68,6 +69,7 @@ func validateDirectory(sourceCodePath string, logger *zerolog.Logger, errFactory
6869
return nil
6970
}
7071

72+
// dirExists checks if the given path exists as a directory.
7173
func dirExists(path string) (bool, error) {
7274
_, err := os.Stat(path)
7375
if err != nil {
@@ -80,6 +82,7 @@ func dirExists(path string) (bool, error) {
8082
return true, nil
8183
}
8284

85+
// dirContainsFiles checks if the given directory contains any files.
8386
func dirContainsFiles(path string) (bool, error) {
8487
entries, err := os.ReadDir(path)
8588
if err != nil {

0 commit comments

Comments
 (0)