packages/repocop/src/query.ts (165 lines of code) (raw):

import type { github_languages, github_repository_branches, github_repository_custom_properties, guardian_github_actions_usage, PrismaClient, view_repo_ownership, } from '@prisma/client'; import type { NonEmptyArray, RepocopVulnerability, Repository, } from 'common/src/types'; import type { Octokit } from 'octokit'; import { toNonEmptyArray } from '../../common/src/functions'; import { dependabotAlertToRepocopVulnerability } from './evaluation/repository'; import type { Alert, AwsCloudFormationStack, DependabotVulnResponse, Team, } from './types'; export async function getRepositories( client: PrismaClient, ignoredRepositoryPrefixes: string[], ): Promise<Repository[]> { console.debug('Discovering repositories'); const repositories = await client.github_repositories.findMany({ where: { NOT: [ { OR: ignoredRepositoryPrefixes.map((prefix) => { return { full_name: { startsWith: prefix } }; }), }, ], }, }); console.debug(`Found ${repositories.length} repositories`); return toNonEmptyArray(repositories.map((r) => r as Repository)); } // We only care about branches from repos we've selected, so lets only pull those to save us some time/memory export async function getRepositoryBranches( client: PrismaClient, repos: Repository[], ): Promise<NonEmptyArray<github_repository_branches>> { const branches = await client.github_repository_branches.findMany({ where: { repository_id: { in: repos.map((repo) => repo.id) }, }, }); return toNonEmptyArray(branches); } export const getTeams = async (client: PrismaClient): Promise<Team[]> => { const teams = ( await client.github_teams.findMany({ select: { slug: true, id: true, name: true, }, }) ).map((t) => t as Team); console.debug(`Found ${teams.length} teams.`); return toNonEmptyArray(teams); }; export async function getRepoOwnership( client: PrismaClient, ): Promise<NonEmptyArray<view_repo_ownership>> { const data = await client.view_repo_ownership.findMany(); console.log(`Found ${data.length} repo ownership records.`); return toNonEmptyArray(data); } export async function getStacks( client: PrismaClient, ): Promise<NonEmptyArray<AwsCloudFormationStack>> { const stacks = ( await client.aws_cloudformation_stacks.findMany({ select: { stack_name: true, tags: true, creation_time: true, }, }) ).map((stack) => stack as AwsCloudFormationStack); console.debug(`Found ${stacks.length} stacks.`); return toNonEmptyArray(stacks); } export async function getRepositoryLanguages( client: PrismaClient, ): Promise<NonEmptyArray<github_languages>> { return toNonEmptyArray(await client.github_languages.findMany({})); } //Octokit Queries async function getAlertsForRepo( octokit: Octokit, orgName: string, repoName: string, ): Promise<Alert[] | undefined> { const prefix = `${orgName}/`; if (repoName.startsWith(prefix)) { repoName = repoName.replace(prefix, ''); } try { const alert: DependabotVulnResponse = await octokit.rest.dependabot.listAlertsForRepo({ owner: orgName, repo: repoName, per_page: 100, severity: 'critical,high', state: 'open', sort: 'created', direction: 'asc', //retrieve oldest vulnerabilities first }); const openRuntimeDependencies = alert.data.filter( (a) => a.dependency.scope !== 'development', ); return openRuntimeDependencies; } catch (error) { console.debug( `Dependabot - ${repoName}: Could not get alerts. Dependabot may not be enabled.`, ); console.debug(error); // Return undefined if dependabot is not enabled, to distinguish from // the scenario where it is enabled, but there are no alerts return undefined; } } export async function getDependabotVulnerabilities( repos: Repository[], orgName: string, octokit: Octokit, ) { const dependabotVulnerabilities: RepocopVulnerability[] = ( await Promise.all( repos.map(async (repo) => { const alerts = await getAlertsForRepo(octokit, orgName, repo.name); if (alerts) { return alerts.map((a) => dependabotAlertToRepocopVulnerability(repo.full_name, a), ); } return []; }), ) ).flat(); console.log( `Found ${dependabotVulnerabilities.length} dependabot vulnerabilities across ${repos.length} repos`, ); return dependabotVulnerabilities; } export async function getProductionWorkflowUsages( client: PrismaClient, productionRepos: Repository[], ): Promise<NonEmptyArray<guardian_github_actions_usage>> { const actions_usage = await client.guardian_github_actions_usage.findMany({ where: { full_name: { in: productionRepos.map((repo) => repo.full_name) }, }, }); return toNonEmptyArray(actions_usage); } export async function getRepositoryCustomProperties( client: PrismaClient, ): Promise<NonEmptyArray<github_repository_custom_properties>> { const custom_properties = await client.github_repository_custom_properties.findMany({}); return toNonEmptyArray(custom_properties); }