Previously, we uploaded SARIF artifacts in the `analyze-post` step and database and log artifacts in the `init-post` step. As we migrate to the updated `artifact` dependencies, we want to switch to uploading all artifacts in one step. In order to upload all artifacts in one go and maintain the artifacts at the root of the debug directory, we first move SARIF artifacts to the database directory. This should not affect any other consumers of the SARIF file as this occurs in the `init-post` step.
170 lines
5.1 KiB
TypeScript
170 lines
5.1 KiB
TypeScript
import * as fs from "fs";
|
|
import * as path from "path";
|
|
|
|
import * as artifact from "@actions/artifact";
|
|
import * as core from "@actions/core";
|
|
import AdmZip from "adm-zip";
|
|
import del from "del";
|
|
|
|
import { getRequiredInput } from "./actions-util";
|
|
import { dbIsFinalized } from "./analyze";
|
|
import { getCodeQL } from "./codeql";
|
|
import { Config } from "./config-utils";
|
|
import { EnvVar } from "./environment";
|
|
import { Language } from "./languages";
|
|
import { Logger } from "./logging";
|
|
import {
|
|
bundleDb,
|
|
doesDirectoryExist,
|
|
getCodeQLDatabasePath,
|
|
listFolder,
|
|
} from "./util";
|
|
|
|
export function sanitizeArifactName(name: string): string {
|
|
return name.replace(/[^a-zA-Z0-9_\\-]+/g, "");
|
|
}
|
|
|
|
export async function uploadAllAvailableDebugArtifacts(
|
|
config: Config,
|
|
logger: Logger,
|
|
) {
|
|
let filesToUpload: string[] = [];
|
|
|
|
const analyzeActionOutputDir = process.env[EnvVar.SARIF_RESULTS_OUTPUT_DIR];
|
|
for (const lang of config.languages) {
|
|
// Add any SARIF files, if they exist
|
|
if (
|
|
analyzeActionOutputDir !== undefined &&
|
|
fs.existsSync(analyzeActionOutputDir) &&
|
|
fs.lstatSync(analyzeActionOutputDir).isDirectory()
|
|
) {
|
|
const sarifFile = path.resolve(analyzeActionOutputDir, `${lang}.sarif`);
|
|
// Move SARIF to DB location so that they can be uploaded with the same root directory as the other artifacts.
|
|
if (fs.existsSync(sarifFile)) {
|
|
const sarifInDbLocation = path.resolve(
|
|
config.dbLocation,
|
|
`${lang}.sarif`,
|
|
);
|
|
fs.renameSync(sarifFile, sarifInDbLocation);
|
|
filesToUpload = filesToUpload.concat(sarifInDbLocation);
|
|
}
|
|
}
|
|
|
|
// Add any log files
|
|
const databaseDirectory = getCodeQLDatabasePath(config, lang);
|
|
const logsDirectory = path.resolve(databaseDirectory, "log");
|
|
if (doesDirectoryExist(logsDirectory)) {
|
|
filesToUpload = filesToUpload.concat(listFolder(logsDirectory));
|
|
}
|
|
|
|
// Multilanguage tracing: there are additional logs in the root of the cluster
|
|
const multiLanguageTracingLogsDirectory = path.resolve(
|
|
config.dbLocation,
|
|
"log",
|
|
);
|
|
if (doesDirectoryExist(multiLanguageTracingLogsDirectory)) {
|
|
filesToUpload = filesToUpload.concat(
|
|
listFolder(multiLanguageTracingLogsDirectory),
|
|
);
|
|
}
|
|
|
|
// Add database bundle
|
|
let databaseBundlePath: string;
|
|
if (!dbIsFinalized(config, lang, logger)) {
|
|
databaseBundlePath = await createPartialDatabaseBundle(config, lang);
|
|
} else {
|
|
databaseBundlePath = await createDatabaseBundleCli(config, lang);
|
|
}
|
|
filesToUpload = filesToUpload.concat(databaseBundlePath);
|
|
}
|
|
|
|
await uploadDebugArtifacts(
|
|
filesToUpload,
|
|
config.dbLocation,
|
|
config.debugArtifactName,
|
|
);
|
|
}
|
|
|
|
export async function uploadDebugArtifacts(
|
|
toUpload: string[],
|
|
rootDir: string,
|
|
artifactName: string,
|
|
) {
|
|
if (toUpload.length === 0) {
|
|
return;
|
|
}
|
|
let suffix = "";
|
|
const matrix = getRequiredInput("matrix");
|
|
if (matrix) {
|
|
try {
|
|
for (const [, matrixVal] of Object.entries(
|
|
JSON.parse(matrix) as any[][],
|
|
).sort())
|
|
suffix += `-${matrixVal}`;
|
|
} catch {
|
|
core.info(
|
|
"Could not parse user-specified `matrix` input into JSON. The debug artifact will not be named with the user's `matrix` input.",
|
|
);
|
|
}
|
|
}
|
|
|
|
try {
|
|
await artifact.create().uploadArtifact(
|
|
sanitizeArifactName(`${artifactName}${suffix}`),
|
|
toUpload.map((file) => path.normalize(file)),
|
|
path.normalize(rootDir),
|
|
{
|
|
continueOnError: true,
|
|
// ensure we don't keep the debug artifacts around for too long since they can be large.
|
|
retentionDays: 7,
|
|
},
|
|
);
|
|
} catch (e) {
|
|
// A failure to upload debug artifacts should not fail the entire action.
|
|
core.warning(`Failed to upload debug artifacts: ${e}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If a database has not been finalized, we cannot run the `codeql database bundle`
|
|
* command in the CLI because it will return an error. Instead we directly zip
|
|
* all files in the database folder and return the path.
|
|
*/
|
|
async function createPartialDatabaseBundle(
|
|
config: Config,
|
|
language: Language,
|
|
): Promise<string> {
|
|
const databasePath = getCodeQLDatabasePath(config, language);
|
|
const databaseBundlePath = path.resolve(
|
|
config.dbLocation,
|
|
`${config.debugDatabaseName}-${language}-partial.zip`,
|
|
);
|
|
core.info(
|
|
`${config.debugDatabaseName}-${language} is not finalized. Uploading partial database bundle at ${databaseBundlePath}...`,
|
|
);
|
|
// See `bundleDb` for explanation behind deleting existing db bundle.
|
|
if (fs.existsSync(databaseBundlePath)) {
|
|
await del(databaseBundlePath, { force: true });
|
|
}
|
|
const zip = new AdmZip();
|
|
zip.addLocalFolder(databasePath);
|
|
zip.writeZip(databaseBundlePath);
|
|
return databaseBundlePath;
|
|
}
|
|
|
|
/**
|
|
* Runs `codeql database bundle` command and returns the path.
|
|
*/
|
|
async function createDatabaseBundleCli(
|
|
config: Config,
|
|
language: Language,
|
|
): Promise<string> {
|
|
// Otherwise run `codeql database bundle` command.
|
|
const databaseBundlePath = await bundleDb(
|
|
config,
|
|
language,
|
|
await getCodeQL(config.codeQLCmd),
|
|
`${config.debugDatabaseName}-${language}`,
|
|
);
|
|
return databaseBundlePath;
|
|
}
|