Refactor scorecard API implementation
This commit is contained in:
+1
-1
@@ -279,7 +279,7 @@ function printScorecardBlock(
|
||||
if (scorecard) {
|
||||
for (const dependency of scorecard.dependencies) {
|
||||
core.info(
|
||||
`${dependency.ecosystem}/${dependency.packageName}: OpenSSF Scorecard Score: ${dependency?.depsDevData?.scorecard?.overallScore}`
|
||||
`${dependency.ecosystem}/${dependency.packageName}: OpenSSF Scorecard Score: ${dependency?.scorecard?.score}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+33
-1
@@ -102,6 +102,37 @@ export const ComparisonResponseSchema = z.object({
|
||||
snapshot_warnings: z.string()
|
||||
})
|
||||
|
||||
export const ScorecardApiSchema = z.object({
|
||||
date: z.string(),
|
||||
repo: z
|
||||
.object({
|
||||
name: z.string(),
|
||||
commit: z.string()
|
||||
})
|
||||
.nullish(),
|
||||
scorecard: z
|
||||
.object({
|
||||
version: z.string(),
|
||||
commit: z.string()
|
||||
})
|
||||
.nullish(),
|
||||
checks: z
|
||||
.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
documentation: z.object({
|
||||
shortDescription: z.string(),
|
||||
url: z.string()
|
||||
}),
|
||||
score: z.string(),
|
||||
reason: z.string(),
|
||||
details: z.array(z.string())
|
||||
})
|
||||
)
|
||||
.nullish(),
|
||||
score: z.number().nullish()
|
||||
})
|
||||
|
||||
export const DepsDevProjectSchema = z
|
||||
.object({
|
||||
projectKey: z.object({
|
||||
@@ -160,7 +191,7 @@ export const ScorecardSchema = z.object({
|
||||
ecosystem: z.string(),
|
||||
packageName: z.string(),
|
||||
version: z.string().nullish(),
|
||||
depsDevData: DepsDevProjectSchema
|
||||
scorecard: ScorecardApiSchema.nullish()
|
||||
})
|
||||
)
|
||||
})
|
||||
@@ -173,3 +204,4 @@ export type Severity = z.infer<typeof SeveritySchema>
|
||||
export type Scope = (typeof SCOPES)[number]
|
||||
export type DepsDevProject = z.infer<typeof DepsDevProjectSchema>
|
||||
export type Scorecard = z.infer<typeof ScorecardSchema>
|
||||
export type ScorecardApi = z.infer<typeof ScorecardApiSchema>
|
||||
|
||||
+55
-63
@@ -2,7 +2,8 @@ import {
|
||||
Change,
|
||||
DepsDevProject,
|
||||
DepsDevProjectSchema,
|
||||
Scorecard
|
||||
Scorecard,
|
||||
ScorecardApi
|
||||
} from './schemas'
|
||||
import {isSPDXValid, octokitClient} from './utils'
|
||||
import {PackageURL} from 'packageurl-js'
|
||||
@@ -16,77 +17,68 @@ export async function getScorecardLevels(
|
||||
const ecosystem = change.ecosystem
|
||||
const packageName = change.name
|
||||
const version = change.version
|
||||
try {
|
||||
const depsDevResponse: DepsDevProject = await getDepsDevData(
|
||||
ecosystem,
|
||||
packageName,
|
||||
version
|
||||
)
|
||||
data.dependencies.push({
|
||||
ecosystem,
|
||||
packageName,
|
||||
version,
|
||||
depsDevData: depsDevResponse
|
||||
})
|
||||
} catch (error: any) {
|
||||
core.debug(`Error querying for depsDevData: ${error.message}`)
|
||||
data.dependencies.push({
|
||||
ecosystem,
|
||||
packageName,
|
||||
version,
|
||||
depsDevData: null
|
||||
})
|
||||
|
||||
//Get the project repository
|
||||
let repositoryUrl = change.source_repository_url
|
||||
// If GitHub API doesn't have the repository URL, query deps.dev for it.
|
||||
if (
|
||||
repositoryUrl === null ||
|
||||
repositoryUrl === undefined ||
|
||||
repositoryUrl === ''
|
||||
) {
|
||||
// Call the deps.dev API to get the repository URL from there
|
||||
repositoryUrl = await getProjectUrl(ecosystem, packageName, version)
|
||||
}
|
||||
|
||||
// Get the scorecard API response from the scorecards API
|
||||
let scorecardApi: ScorecardApi | null = null
|
||||
if (
|
||||
repositoryUrl !== null &&
|
||||
repositoryUrl !== undefined &&
|
||||
repositoryUrl !== ''
|
||||
) {
|
||||
try {
|
||||
scorecardApi = await getScorecard(repositoryUrl)
|
||||
} catch (error: any) {
|
||||
core.debug(`Error querying for scorecard: ${error.message}`)
|
||||
}
|
||||
}
|
||||
data.dependencies.push({
|
||||
ecosystem,
|
||||
packageName,
|
||||
version,
|
||||
scorecard: scorecardApi
|
||||
})
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
const depsDevAPIRoot = 'https://api.deps.dev'
|
||||
async function getScorecard(repositoryUrl: string): Promise<ScorecardApi> {
|
||||
const apiRoot = 'https://api.securityscorecards.dev/'
|
||||
let scorecardResponse: ScorecardApi = {} as ScorecardApi
|
||||
|
||||
async function getDepsDevData(
|
||||
ecosystem: String,
|
||||
packageName: String,
|
||||
version: any
|
||||
): Promise<DepsDevProject> {
|
||||
try {
|
||||
core.debug(`Getting deps.dev data for ${packageName} ${version}`)
|
||||
const url = `${depsDevAPIRoot}/v3alpha/systems/${ecosystem}/packages/${packageName}/versions/${version}`
|
||||
const response = await fetch(url)
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
const projects = data.relatedProjects
|
||||
for (const project of projects) {
|
||||
return await getDepsDevProjectData(project.projectKey.id)
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`Failed to fetch data with status code: ${response.status}`
|
||||
)
|
||||
}
|
||||
} catch (error: any) {
|
||||
core.debug(`Error fetching data: ${error.message}`)
|
||||
const url = `${apiRoot}/${repositoryUrl}`
|
||||
const response = await fetch(url)
|
||||
if (response.ok) {
|
||||
scorecardResponse = await response.json()
|
||||
}
|
||||
return DepsDevProjectSchema.parse({})
|
||||
return scorecardResponse
|
||||
}
|
||||
|
||||
async function getDepsDevProjectData(
|
||||
projectKeyId: string
|
||||
): Promise<DepsDevProject> {
|
||||
try {
|
||||
core.debug(`Getting deps.dev project data for ${projectKeyId}`)
|
||||
const url = `${depsDevAPIRoot}/v3alpha/projects/${encodeURIComponent(projectKeyId)}`
|
||||
const response = await fetch(url)
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
//core.debug(`Got deps.dev project data: ${JSON.stringify(data)}`)
|
||||
return DepsDevProjectSchema.parse(data)
|
||||
} else {
|
||||
throw new Error(
|
||||
`Failed to fetch project data with status code: ${response.status}`
|
||||
)
|
||||
async function getProjectUrl(
|
||||
ecosystem: string,
|
||||
packageName: string,
|
||||
version: string
|
||||
): Promise<string> {
|
||||
core.debug(`Getting deps.dev data for ${packageName} ${version}`)
|
||||
const depsDevAPIRoot = 'https://api.deps.dev'
|
||||
const url = `${depsDevAPIRoot}/v3alpha/systems/${ecosystem}/packages/${packageName}/versions/${version}`
|
||||
const response = await fetch(url)
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
if (data.relatedProjects.length > 0) {
|
||||
return data.relatedProjects[0].projectKey.id
|
||||
}
|
||||
} catch (error: any) {
|
||||
core.debug(`Error fetching project data: ${error.message}`)
|
||||
}
|
||||
return DepsDevProjectSchema.parse({})
|
||||
return ''
|
||||
}
|
||||
|
||||
+9
-10
@@ -260,19 +260,16 @@ export function addScorecardToSummary(
|
||||
)
|
||||
for (const dependency of scorecard.dependencies) {
|
||||
core.debug('Adding scorecard to summary')
|
||||
core.debug(
|
||||
`Overall score ${dependency.depsDevData?.scorecard.overallScore}`
|
||||
)
|
||||
core.debug(`Overall score ${dependency.scorecard?.score}`)
|
||||
|
||||
// Set the icon based on the overall score value
|
||||
let overallIcon = ''
|
||||
if (
|
||||
dependency.depsDevData?.scorecard.overallScore !== undefined &&
|
||||
dependency.depsDevData?.scorecard.overallScore !== null
|
||||
dependency.scorecard?.score !== undefined &&
|
||||
dependency.scorecard?.score !== null
|
||||
) {
|
||||
overallIcon =
|
||||
dependency.depsDevData?.scorecard.overallScore <
|
||||
config.warn_on_openssf_scorecard_level
|
||||
dependency.scorecard?.score < config.warn_on_openssf_scorecard_level
|
||||
? ':warning:'
|
||||
: ':green_circle:'
|
||||
}
|
||||
@@ -280,13 +277,15 @@ export function addScorecardToSummary(
|
||||
//Add a row for the dependency
|
||||
core.summary.addRaw(
|
||||
`<tr><td>${dependency.ecosystem}/${dependency.packageName}</td><td>${dependency.version}</td>
|
||||
<td>${overallIcon} ${dependency.depsDevData?.scorecard.overallScore === undefined || dependency.depsDevData?.scorecard.overallScore === null ? 'Unknown' : dependency.depsDevData?.scorecard.overallScore}</td>`,
|
||||
<td>${overallIcon} ${dependency.scorecard?.score === undefined || dependency.scorecard?.score === null ? 'Unknown' : dependency.scorecard?.score}</td>`,
|
||||
false
|
||||
)
|
||||
if (dependency.depsDevData?.scorecard.checks !== undefined) {
|
||||
|
||||
//Add details table in the last column
|
||||
if (dependency.scorecard?.checks !== undefined) {
|
||||
let detailsTable =
|
||||
'<table><tr><th>Check</th><th>Score</th><th>Reason</th></tr>'
|
||||
for (const check of dependency.depsDevData?.scorecard.checks || []) {
|
||||
for (const check of dependency.scorecard?.checks || []) {
|
||||
const icon =
|
||||
parseFloat(check.score) < config.warn_on_openssf_scorecard_level
|
||||
? ':warning:'
|
||||
|
||||
Reference in New Issue
Block a user