@@ -17,7 +17,7 @@ import (
17
17
"github.com/snyk/cli-extension-os-flows/internal/errors"
18
18
"github.com/snyk/cli-extension-os-flows/internal/flags"
19
19
"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 "
21
21
)
22
22
23
23
// RunUnifiedTestFlow handles the unified test API flow.
@@ -38,50 +38,63 @@ func RunUnifiedTestFlow(
38
38
if err != nil {
39
39
return nil , err
40
40
}
41
- var allLegacyFindings []definitions.LegacyVulnerabilityResponse
42
- var allOutputData []workflow.Data
43
41
44
42
localPolicy := createLocalPolicy (riskScoreThreshold , severityThreshold )
45
43
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
+
46
77
for i , depGraph := range depGraphs {
47
78
displayTargetFile := ""
48
79
if i < len (displayTargetFiles ) {
49
80
displayTargetFile = displayTargetFiles [i ]
50
81
}
51
82
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 )
65
84
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
75
86
}
76
87
88
+ projectName := getProjectName (ictx , depGraph )
77
89
packageManager := depGraph .PkgManager .Name
78
90
depCount := max (0 , len (depGraph .Pkgs )- 1 )
79
91
80
92
// 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 )
83
96
if err != nil {
84
- return nil , err
97
+ return nil , nil , err
85
98
}
86
99
87
100
if legacyFinding != nil {
@@ -90,59 +103,92 @@ func RunUnifiedTestFlow(
90
103
allOutputData = append (allOutputData , outputData ... )
91
104
}
92
105
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 ) {
93
152
config := ictx .GetConfiguration ()
94
153
jsonOutput := config .GetBool ("json" )
95
- jsonFileOutput := config .GetString (output_workflow . OUTPUT_CONFIG_KEY_JSON_FILE )
154
+ jsonFileOutput := config .GetString (outputworkflow . OutputConfigKeyJSONFile )
96
155
97
156
// Human-readable output is suppressed only when --json is specified.
98
157
wantsHumanReadable := ! jsonOutput
158
+ wantsJSONFile := jsonFileOutput != ""
159
+ wantsJSONStdOut := jsonOutput
99
160
100
161
var finalOutput []workflow.Data
101
162
if wantsHumanReadable {
102
- outputDestination := output_workflow .NewOutputDestination ()
163
+ outputDestination := outputworkflow .NewOutputDestination ()
103
164
// 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 )
105
166
if err != nil {
106
- return nil , err
167
+ return nil , fmt . Errorf ( "failed to process output workflow: %w" , err )
107
168
}
108
169
finalOutput = append (finalOutput , remainingData ... )
109
170
}
110
171
111
172
// 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
+ }
133
176
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
+ }
139
181
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 )
143
185
}
144
186
}
145
187
188
+ if wantsJSONStdOut {
189
+ finalOutput = append (finalOutput , NewWorkflowData (ApplicationJSONContentType , jsonBytes ))
190
+ }
191
+
146
192
// If only JSON output to stdout was requested, we still need the summary for the exit code.
147
193
if wantsJSONStdOut && ! wantsHumanReadable {
148
194
for _ , d := range allOutputData {
@@ -155,24 +201,46 @@ func RunUnifiedTestFlow(
155
201
return finalOutput , nil
156
202
}
157
203
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
+
158
230
// Create local policy only if risk score or severity threshold are specified.
159
231
func createLocalPolicy (riskScoreThreshold * uint16 , severityThreshold * testapi.Severity ) * testapi.LocalPolicy {
160
232
if riskScoreThreshold == nil && severityThreshold == nil {
161
233
return nil
162
234
}
163
235
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 ,
170
239
}
171
- return localPolicy
172
240
}
173
241
174
242
// 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 ) {
176
244
depGraphResult , err := service .GetDepGraph (ictx )
177
245
if err != nil {
178
246
return nil , nil , fmt .Errorf ("failed to get dependency graph: %w" , err )
@@ -182,15 +250,15 @@ func createDepGraphs(ictx workflow.InvocationContext) ([]testapi.IoSnykApiV1test
182
250
return nil , nil , fmt .Errorf ("no dependency graphs found" )
183
251
}
184
252
185
- depGraphs := make ([]testapi.IoSnykApiV1testdepgraphRequestDepGraph , len (depGraphResult .DepGraphBytes ))
253
+ depGraphs := make ([]* testapi.IoSnykApiV1testdepgraphRequestDepGraph , len (depGraphResult .DepGraphBytes ))
186
254
for i , depGraphBytes := range depGraphResult .DepGraphBytes {
187
255
var depGraphStruct testapi.IoSnykApiV1testdepgraphRequestDepGraph
188
256
err = json .Unmarshal (depGraphBytes , & depGraphStruct )
189
257
if err != nil {
190
258
return nil , nil ,
191
259
fmt .Errorf ("unmarshaling depGraph from args failed: %w" , err )
192
260
}
193
- depGraphs [i ] = depGraphStruct
261
+ depGraphs [i ] = & depGraphStruct
194
262
}
195
263
196
264
return depGraphs , depGraphResult .DisplayTargetFiles , nil
0 commit comments