Files
component-detection-depende…/componentDetection.ts
T

176 lines
6.5 KiB
TypeScript
Raw Normal View History

2023-01-20 19:38:27 +00:00
import * as github from '@actions/github'
import * as core from '@actions/core'
import {
PackageCache,
BuildTarget,
Package,
Snapshot,
Manifest,
submitSnapshot
} from '@github/dependency-submission-toolkit'
import fetch from 'cross-fetch'
2023-01-20 19:38:27 +00:00
import tar from 'tar'
import fs from 'fs'
import * as exec from '@actions/exec';
import dotenv from 'dotenv'
import { Context } from '@actions/github/lib/context'
import { unmockedModulePathPatterns } from './jest.config'
dotenv.config();
2023-01-22 19:16:34 +00:00
2023-01-22 01:06:08 +00:00
export default class ComponentDetection {
2023-05-05 00:03:32 -04:00
public static componentDetectionPath = process.platform === "win32" ? './component-detection.exe' : './component-detection';
2023-01-22 20:19:41 +00:00
public static outputPath = './output.json';
2023-01-22 01:06:08 +00:00
// This is the default entry point for this class.
static async scanAndGetManifests(path: string): Promise<Manifest[] | undefined> {
await this.downloadLatestRelease();
await this.runComponentDetection(path);
return await this.getManifestsFromResults();
}
// Get the latest release from the component-detection repo, download the tarball, and extract it
2023-01-22 20:19:41 +00:00
public static async downloadLatestRelease() {
2023-01-22 01:06:08 +00:00
try {
2023-05-05 00:21:28 -04:00
core.debug(`Downloading latest release for ${process.platform}`);
2023-01-22 07:16:21 +00:00
const downloadURL = await this.getLatestReleaseURL();
2023-01-22 01:06:08 +00:00
const blob = await (await fetch(new URL(downloadURL))).blob();
const arrayBuffer = await blob.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
// Write the blob to a file
2023-05-05 00:21:28 -04:00
core.debug(`Writing binary to file ${this.componentDetectionPath}`);
2023-05-04 22:57:27 -04:00
await fs.writeFileSync(this.componentDetectionPath, buffer, { mode: 0o777, flag: 'w' });
2023-01-22 01:06:08 +00:00
} catch (error: any) {
core.error(error);
2023-05-04 22:57:27 -04:00
}
2023-01-22 01:06:08 +00:00
}
2023-01-20 19:38:27 +00:00
2023-01-22 01:06:08 +00:00
// Run the component-detection CLI on the path specified
2023-01-22 20:19:41 +00:00
public static async runComponentDetection(path: string) {
2023-01-22 07:20:25 +00:00
core.info("Running component-detection");
2023-01-22 07:22:56 +00:00
2023-01-22 01:06:08 +00:00
try {
2023-01-22 19:46:41 +00:00
await exec.exec(`${this.componentDetectionPath} scan --SourceDirectory ${path} --ManifestFile ${this.outputPath} ${this.getComponentDetectionParameters()}`);
2023-01-22 01:06:08 +00:00
} catch (error: any) {
core.error(error);
}
}
2023-01-22 19:46:41 +00:00
private static getComponentDetectionParameters(): string {
var parameters = "";
parameters += (core.getInput('directoryExclusionList')) ? ` --DirectoryExclusionList ${core.getInput('directoryExclusionList')}` : "";
parameters += (core.getInput('detectorArgs')) ? ` --DetectorArgs ${core.getInput('detectorArgs')}` : "";
parameters += (core.getInput('detectorsFilter')) ? ` --DetectorsFilter ${core.getInput('detectorsFilter')}` : "";
parameters += (core.getInput('dockerImagesToScan')) ? ` --DockerImagesToScan ${core.getInput('dockerImagesToScan')}` : "";
return parameters;
}
2023-05-04 22:57:27 -04:00
public static async getManifestsFromResults(): Promise<Manifest[] | undefined> {
2023-01-22 07:20:25 +00:00
core.info("Getting manifests from results");
2023-01-22 08:17:06 +00:00
// Parse the result file and add the packages to the package cache
const packageCache = new PackageCache();
2023-05-04 22:57:27 -04:00
const packages: Array<ComponentDetectionPackage> = [];
2023-01-22 08:17:06 +00:00
const results = await fs.readFileSync(this.outputPath, 'utf8');
2023-05-04 22:57:27 -04:00
2023-01-22 08:17:06 +00:00
var json: any = JSON.parse(results);
json.componentsFound.forEach(async (component: any) => {
const packageUrl = ComponentDetection.makePackageUrl(component.component.packageUrl);
2023-05-04 22:57:27 -04:00
2023-01-22 08:17:06 +00:00
if (!packageCache.hasPackage(packageUrl)) {
2023-05-04 22:57:27 -04:00
const pkg = new ComponentDetectionPackage(packageUrl, component.component.id,
component.isDevelopmentDependency, component.topLevelReferrers, component.locationsFoundAt, component.containerDetailIds, component.containerLayerIds);
2023-01-22 08:17:06 +00:00
packageCache.addPackage(pkg);
packages.push(pkg);
}
});
2023-01-22 01:06:08 +00:00
2023-01-22 08:17:06 +00:00
// Set the transitive dependencies
core.debug("Sorting out transitive dependencies");
packages.forEach(async (pkg: ComponentDetectionPackage) => {
pkg.topLevelReferrers.forEach(async (referrer: any) => {
const referrerPackage = packageCache.lookupPackage(ComponentDetection.makePackageUrl(referrer.packageUrl));
if (referrerPackage) {
referrerPackage.dependsOn(pkg);
}
2023-01-22 01:06:08 +00:00
});
2023-01-22 08:17:06 +00:00
});
2023-01-22 01:06:08 +00:00
2023-01-22 08:17:06 +00:00
// Create manifests
const manifests: Array<Manifest> = [];
2023-01-22 01:06:08 +00:00
2023-01-22 08:17:06 +00:00
// Check the locationsFoundAt for every package and add each as a manifest
packages.forEach(async (pkg: ComponentDetectionPackage) => {
pkg.locationsFoundAt.forEach(async (location: any) => {
2023-01-22 08:33:45 +00:00
if (!manifests.find((manifest: Manifest) => manifest.name == location)) {
2023-01-22 08:24:44 +00:00
const manifest = new Manifest(location, location);
2023-01-22 08:17:06 +00:00
manifests.push(manifest);
}
if (pkg.topLevelReferrers.length == 0) {
2023-01-22 08:28:57 +00:00
manifests.find((manifest: Manifest) => manifest.name == location)?.addDirectDependency(pkg, ComponentDetection.getDependencyScope(pkg));
2023-01-22 08:17:06 +00:00
} else {
2023-05-04 22:57:27 -04:00
manifests.find((manifest: Manifest) => manifest.name == location)?.addIndirectDependency(pkg, ComponentDetection.getDependencyScope(pkg));
}
2023-01-22 01:06:08 +00:00
});
2023-01-22 08:17:06 +00:00
});
return manifests;
2023-01-22 01:06:08 +00:00
}
private static getDependencyScope(pkg: ComponentDetectionPackage) {
return pkg.isDevelopmentDependency ? 'development' : 'runtime'
}
private static makePackageUrl(packageUrlJson: any): string {
var packageUrl = `${packageUrlJson.Scheme}:${packageUrlJson.Type}/`;
if (packageUrlJson.Namespace) {
2023-01-22 19:16:34 +00:00
packageUrl += `${packageUrlJson.Namespace.replaceAll("@", "%40")}/`;
2023-01-22 01:06:08 +00:00
}
2023-01-22 19:16:34 +00:00
packageUrl += `${packageUrlJson.Name.replaceAll("@", "%40")}`;
2023-01-22 01:06:08 +00:00
if (packageUrlJson.Version) {
packageUrl += `@${packageUrlJson.Version}`;
}
if (packageUrlJson.Qualifiers) {
packageUrl += `?${packageUrlJson.Qualifiers}`;
}
return packageUrl;
}
2023-01-22 07:16:21 +00:00
private static async getLatestReleaseURL(): Promise<string> {
2023-05-04 22:57:27 -04:00
const githubToken = core.getInput('token') || process.env.GITHUB_TOKEN || "";
2023-01-22 01:06:08 +00:00
const octokit = github.getOctokit(githubToken);
const owner = "microsoft";
const repo = "component-detection";
const latestRelease = await octokit.rest.repos.getLatestRelease({
owner, repo
});
var downloadURL: string = "";
2023-05-04 22:57:27 -04:00
const assetName = process.platform === "win32" ? "component-detection-win-x64.exe" : "component-detection-linux-x64";
2023-01-22 01:06:08 +00:00
latestRelease.data.assets.forEach((asset: any) => {
2023-05-04 22:57:27 -04:00
if (asset.name === assetName) {
2023-01-22 01:06:08 +00:00
downloadURL = asset.browser_download_url;
}
2023-01-22 01:06:08 +00:00
});
return downloadURL;
}
}
class ComponentDetectionPackage extends Package {
2023-05-04 22:57:27 -04:00
constructor(packageUrl: string, public id: string, public isDevelopmentDependency: boolean, public topLevelReferrers: [],
2023-01-22 07:55:00 +00:00
public locationsFoundAt: [], public containerDetailIds: [], public containerLayerIds: []) {
super(packageUrl);
}
}
2023-01-20 19:38:27 +00:00