Skip to content

Commit 9848a8f

Browse files
authored
call the tier1 reachability scanfinalize api from the socket (#706)
* call tier1-reachability-scan/finalize from the Socket CLI * revert some debug stuff * remove some debug console.logs * fix lint error
1 parent 5095ed6 commit 9848a8f

4 files changed

Lines changed: 165 additions & 3 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { sendApiRequest } from '../../utils/api.mts'
2+
3+
import type { CResult } from '../../types.mts'
4+
5+
export type FinalizeTier1ScanOptions = {
6+
tier1_reachability_scan_id: string
7+
report_run_id: string
8+
}
9+
10+
/**
11+
* Finalize a tier1 reachability scan.
12+
* - Associates the tier1 reachability scan metadata with the full scan.
13+
* - Sets the tier1 reachability scan to "finalized" state.
14+
*/
15+
export async function finalizeTier1Scan(
16+
tier1_reachability_scan_id: string,
17+
report_run_id: string,
18+
): Promise<CResult<unknown>> {
19+
// we do not use the SDK here because the tier1-reachability-scan/finalize is a hidden
20+
// endpoint that is not part of the OpenAPI specification.
21+
return await sendApiRequest('tier1-reachability-scan/finalize', {
22+
method: 'POST',
23+
body: {
24+
tier1_reachability_scan_id,
25+
report_run_id,
26+
},
27+
})
28+
}

src/commands/scan/handle-create-new-scan.mts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ import { pluralize } from '@socketsecurity/registry/lib/words'
44

55
import { fetchCreateOrgFullScan } from './fetch-create-org-full-scan.mts'
66
import { fetchSupportedScanFileNames } from './fetch-supported-scan-file-names.mts'
7+
import { finalizeTier1Scan } from './finalize-tier1-scan.mts'
78
import { handleScanReport } from './handle-scan-report.mts'
89
import { outputCreateNewScan } from './output-create-new-scan.mts'
910
import constants from '../../constants.mts'
1011
import { handleApiCall } from '../../utils/api.mts'
1112
import { checkCommandInput } from '../../utils/check-input.mts'
12-
import { spawnCoana } from '../../utils/coana.mts'
13+
import {
14+
extractTier1ReachabilityScanId,
15+
spawnCoana,
16+
} from '../../utils/coana.mts'
1317
import { getPackageFilesForScan } from '../../utils/path-resolve.mts'
1418
import { setupSdk } from '../../utils/sdk.mts'
1519
import { readOrDefaultSocketJson } from '../../utils/socketjson.mts'
@@ -112,6 +116,7 @@ export async function handleCreateNewScan({
112116
}
113117

114118
let scanPaths: string[] = packagePaths
119+
let tier1ReachabilityScanId: string | undefined
115120

116121
// If reachability is enabled, perform reachability analysis
117122
if (reach) {
@@ -131,6 +136,7 @@ export async function handleCreateNewScan({
131136
}
132137

133138
scanPaths = reachResult.data?.scanPaths || []
139+
tier1ReachabilityScanId = reachResult.data?.tier1ReachabilityScanId
134140
}
135141

136142
const fullScanCResult = await fetchCreateOrgFullScan(
@@ -152,6 +158,15 @@ export async function handleCreateNewScan({
152158
},
153159
)
154160

161+
if (
162+
fullScanCResult.ok &&
163+
reach &&
164+
tier1ReachabilityScanId &&
165+
fullScanCResult.data?.id
166+
) {
167+
await finalizeTier1Scan(tier1ReachabilityScanId, fullScanCResult.data?.id)
168+
}
169+
155170
if (fullScanCResult.ok && report) {
156171
if (fullScanCResult.data?.id) {
157172
await handleScanReport({
@@ -195,7 +210,9 @@ async function performReachabilityAnalysis({
195210
branchName: string
196211
outputKind: OutputKind
197212
interactive: boolean
198-
}): Promise<CResult<{ scanPaths?: string[] }>> {
213+
}): Promise<
214+
CResult<{ scanPaths?: string[]; tier1ReachabilityScanId: string | undefined }>
215+
> {
199216
logger.info('Starting reachability analysis...')
200217

201218
packagePaths = packagePaths.filter(
@@ -275,6 +292,9 @@ async function performReachabilityAnalysis({
275292
ok: true,
276293
data: {
277294
scanPaths: [constants.DOT_SOCKET_DOT_FACTS_JSON],
295+
tier1ReachabilityScanId: extractTier1ReachabilityScanId(
296+
constants.DOT_SOCKET_DOT_FACTS_JSON,
297+
),
278298
},
279299
}
280300
}

src/utils/api.mts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,3 +285,103 @@ export async function queryApiSafeJson<T>(
285285
}
286286
}
287287
}
288+
289+
export async function sendApiRequest<T>(
290+
path: string,
291+
options: {
292+
method: 'POST' | 'PUT'
293+
body?: unknown
294+
fetchSpinnerDesc?: string
295+
},
296+
): Promise<CResult<T>> {
297+
const apiToken = getDefaultToken()
298+
if (!apiToken) {
299+
return {
300+
ok: false,
301+
message: 'Authentication Error',
302+
cause:
303+
'User must be authenticated to run this command. To log in, run the command `socket login` and enter your Socket API token.',
304+
}
305+
}
306+
307+
const baseUrl = getDefaultApiBaseUrl() || ''
308+
if (!baseUrl) {
309+
logger.warn(
310+
'API endpoint is not set and default was empty. Request is likely to fail.',
311+
)
312+
}
313+
314+
// Lazily access constants.spinner.
315+
const { spinner } = constants
316+
317+
if (options.fetchSpinnerDesc) {
318+
spinner.start(`Requesting ${options.fetchSpinnerDesc} from API...`)
319+
}
320+
321+
let result
322+
try {
323+
const fetchOptions = {
324+
method: options.method,
325+
headers: {
326+
Authorization: `Basic ${btoa(`${apiToken}:`)}`,
327+
'Content-Type': 'application/json',
328+
},
329+
...(options.body ? { body: JSON.stringify(options.body) } : {}),
330+
}
331+
332+
result = await fetch(
333+
`${baseUrl}${baseUrl.endsWith('/') ? '' : '/'}${path}`,
334+
fetchOptions,
335+
)
336+
if (options.fetchSpinnerDesc) {
337+
spinner.successAndStop(
338+
`Received Socket API response (after requesting ${options.fetchSpinnerDesc}).`,
339+
)
340+
}
341+
} catch (e) {
342+
if (options.fetchSpinnerDesc) {
343+
spinner.failAndStop(
344+
`An error was thrown while requesting ${options.fetchSpinnerDesc}.`,
345+
)
346+
}
347+
348+
const cause = (e as undefined | { message: string })?.message
349+
350+
debugFn('error', `caught: await fetch() ${options.method} error`)
351+
debugDir('inspect', { error: e })
352+
353+
return {
354+
ok: false,
355+
message: 'API Request failed to complete',
356+
...(cause ? { cause } : {}),
357+
}
358+
}
359+
360+
if (!result.ok) {
361+
const cause = await getErrorMessageForHttpStatusCode(result.status)
362+
return {
363+
ok: false,
364+
message: 'Socket API returned an error',
365+
cause: `${result.statusText}${cause ? ` (cause: ${cause})` : ''}`,
366+
data: {
367+
code: result.status,
368+
},
369+
}
370+
}
371+
372+
try {
373+
const data = await result.json()
374+
return {
375+
ok: true,
376+
data: data as T,
377+
}
378+
} catch (e) {
379+
debugFn('error', 'caught: await result.json() error')
380+
debugDir('inspect', { error: e })
381+
return {
382+
ok: false,
383+
message: 'API Request failed to complete',
384+
cause: 'There was an unexpected error trying to parse the response JSON',
385+
}
386+
}
387+
}

src/utils/coana.mts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { readFileSync } from 'node:fs'
2+
13
import { spawn } from '@socketsecurity/registry/lib/spawn'
24

35
import { getDefaultOrgSlug } from '../commands/ci/fetch-default-org-slug.mts'
@@ -14,7 +16,7 @@ export async function spawnCoana(
1416
args: string[] | readonly string[],
1517
options?: SpawnOptions | undefined,
1618
extra?: SpawnExtra | undefined,
17-
): Promise<CResult<unknown>> {
19+
): Promise<CResult<string>> {
1820
const { env: spawnEnv } = { __proto__: null, ...options } as SpawnOptions
1921
const mixinsEnv: Record<string, string> = {
2022
// Lazily access constants.ENV.INLINED_SOCKET_CLI_VERSION.
@@ -59,3 +61,15 @@ export async function spawnCoana(
5961
return { ok: false, data: e, message }
6062
}
6163
}
64+
65+
export function extractTier1ReachabilityScanId(
66+
socketFactsFile: string,
67+
): string | undefined {
68+
try {
69+
const content = readFileSync(socketFactsFile, 'utf8')
70+
const json = JSON.parse(content)
71+
return json.tier1ReachabilityScanId
72+
} catch {
73+
return undefined
74+
}
75+
}

0 commit comments

Comments
 (0)