mirror of
https://github.com/github/codeql-action.git
synced 2026-10-03 09:14:58 +00:00
Merge pull request #4138 from github/henrymercer/bundle-download-errors
Preserve HTTP errors from streaming bundle downloads
This commit is contained in:
16
lib/entry-points.js
generated
16
lib/entry-points.js
generated
@@ -151690,11 +151690,14 @@ async function downloadAndExtract(codeqlURL, compressionMethod, dest, authorizat
|
|||||||
return { totalDurationMs };
|
return { totalDurationMs };
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
await cleanUpPath(dest, "CodeQL bundle", logger);
|
||||||
|
if (asHTTPError(e)?.status === 404) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
core11.warning(
|
core11.warning(
|
||||||
`Failed to download and extract CodeQL bundle using streaming with error: ${getErrorMessage(e)}`
|
`Failed to download and extract CodeQL bundle using streaming with error: ${getErrorMessage(e)}`
|
||||||
);
|
);
|
||||||
core11.warning(`Falling back to downloading the bundle before extracting.`);
|
core11.warning(`Falling back to downloading the bundle before extracting.`);
|
||||||
await cleanUpPath(dest, "CodeQL bundle", logger);
|
|
||||||
}
|
}
|
||||||
const toolsDownloadStart = import_perf_hooks2.performance.now();
|
const toolsDownloadStart = import_perf_hooks2.performance.now();
|
||||||
const archivedBundlePath = await toolcache2.downloadTool(
|
const archivedBundlePath = await toolcache2.downloadTool(
|
||||||
@@ -151766,9 +151769,14 @@ async function downloadAndExtractZstdWithStreaming(codeqlURL, dest, authorizatio
|
|||||||
});
|
});
|
||||||
if (response.statusCode !== 200) {
|
if (response.statusCode !== 200) {
|
||||||
response.resume();
|
response.resume();
|
||||||
throw new Error(
|
const baseMessage = `Failed to download CodeQL bundle from ${codeqlURL}.`;
|
||||||
`Failed to download CodeQL bundle from ${codeqlURL}. HTTP status code: ${response.statusCode}.`
|
if (response.statusCode !== void 0) {
|
||||||
);
|
throw new HTTPError(
|
||||||
|
`${baseMessage} HTTP status code: ${response.statusCode}.`,
|
||||||
|
response.statusCode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw new Error(baseMessage);
|
||||||
}
|
}
|
||||||
await extractTarZst(response, dest, tarVersion, logger);
|
await extractTarZst(response, dest, tarVersion, logger);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { once } from "events";
|
import { once } from "events";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { ClientRequest, IncomingMessage } from "http";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
|
import * as core from "@actions/core";
|
||||||
import * as toolcache from "@actions/tool-cache";
|
import * as toolcache from "@actions/tool-cache";
|
||||||
import test from "ava";
|
import test from "ava";
|
||||||
|
import { https } from "follow-redirects";
|
||||||
import nock from "nock";
|
import nock from "nock";
|
||||||
import * as sinon from "sinon";
|
import * as sinon from "sinon";
|
||||||
|
|
||||||
@@ -10,14 +14,14 @@ import { getRunnerLogger } from "./logging";
|
|||||||
import * as tar from "./tar";
|
import * as tar from "./tar";
|
||||||
import { setupTests } from "./testing-utils";
|
import { setupTests } from "./testing-utils";
|
||||||
import { downloadAndExtract } from "./tools-download";
|
import { downloadAndExtract } from "./tools-download";
|
||||||
import { withTmpDir } from "./util";
|
import * as util from "./util";
|
||||||
|
|
||||||
setupTests(test);
|
setupTests(test);
|
||||||
|
|
||||||
test.serial(
|
test.serial(
|
||||||
"downloadAndExtract reports the durations when downloading before extracting",
|
"downloadAndExtract reports the durations when downloading before extracting",
|
||||||
async (t) => {
|
async (t) => {
|
||||||
await withTmpDir(async (tmpDir) => {
|
await util.withTmpDir(async (tmpDir) => {
|
||||||
const archivePath = path.join(tmpDir, "codeql-bundle.tar.gz");
|
const archivePath = path.join(tmpDir, "codeql-bundle.tar.gz");
|
||||||
const destination = path.join(tmpDir, "codeql");
|
const destination = path.join(tmpDir, "codeql");
|
||||||
sinon.stub(toolcache, "downloadTool").resolves(archivePath);
|
sinon.stub(toolcache, "downloadTool").resolves(archivePath);
|
||||||
@@ -43,13 +47,16 @@ test.serial(
|
|||||||
test.serial(
|
test.serial(
|
||||||
"downloadAndExtract falls back to downloading before extracting if streaming fails",
|
"downloadAndExtract falls back to downloading before extracting if streaming fails",
|
||||||
async (t) => {
|
async (t) => {
|
||||||
await withTmpDir(async (tmpDir) => {
|
await util.withTmpDir(async (tmpDir) => {
|
||||||
sinon.stub(process, "platform").value("linux");
|
sinon.stub(process, "platform").value("linux");
|
||||||
const archivePath = path.join(tmpDir, "codeql-bundle.tar.zst");
|
const archivePath = path.join(tmpDir, "codeql-bundle.tar.zst");
|
||||||
const destination = path.join(tmpDir, "codeql");
|
const destination = path.join(tmpDir, "codeql");
|
||||||
const downloadTool = sinon
|
const downloadTool = sinon
|
||||||
.stub(toolcache, "downloadTool")
|
.stub(toolcache, "downloadTool")
|
||||||
.resolves(archivePath);
|
.callsFake(async () => {
|
||||||
|
t.false(fs.existsSync(destination));
|
||||||
|
return archivePath;
|
||||||
|
});
|
||||||
const extract = sinon.stub(tar, "extract").resolves(destination);
|
const extract = sinon.stub(tar, "extract").resolves(destination);
|
||||||
const extractTarZst = sinon.stub(tar, "extractTarZst").resolves();
|
const extractTarZst = sinon.stub(tar, "extractTarZst").resolves();
|
||||||
const request = nock("https://example.com")
|
const request = nock("https://example.com")
|
||||||
@@ -78,10 +85,134 @@ test.serial(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test.serial(
|
||||||
|
"downloadAndExtract rethrows a 404 rather than retrying the download",
|
||||||
|
async (t) => {
|
||||||
|
await util.withTmpDir(async (tmpDir) => {
|
||||||
|
sinon.stub(process, "platform").value("linux");
|
||||||
|
const destination = path.join(tmpDir, "codeql");
|
||||||
|
const downloadTool = sinon.stub(toolcache, "downloadTool");
|
||||||
|
const extractTarZst = sinon.stub(tar, "extractTarZst").resolves();
|
||||||
|
const request = nock("https://example.com")
|
||||||
|
.get("/codeql-bundle.tar.zst")
|
||||||
|
.reply(404, "Not found");
|
||||||
|
|
||||||
|
const error = await t.throwsAsync(
|
||||||
|
downloadAndExtract(
|
||||||
|
"https://example.com/codeql-bundle.tar.zst",
|
||||||
|
"zstd",
|
||||||
|
destination,
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
{ type: "gnu", version: "1.34" },
|
||||||
|
getRunnerLogger(true),
|
||||||
|
),
|
||||||
|
{
|
||||||
|
instanceOf: util.HTTPError,
|
||||||
|
message:
|
||||||
|
"Failed to download CodeQL bundle from https://example.com/codeql-bundle.tar.zst. HTTP status code: 404.",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
t.is(error?.status, 404);
|
||||||
|
t.true(request.isDone());
|
||||||
|
t.false(extractTarZst.called);
|
||||||
|
t.false(downloadTool.called);
|
||||||
|
t.false(fs.existsSync(destination));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test.serial(
|
||||||
|
"downloadAndExtract falls back to downloading before extracting on a server error",
|
||||||
|
async (t) => {
|
||||||
|
await util.withTmpDir(async (tmpDir) => {
|
||||||
|
sinon.stub(process, "platform").value("linux");
|
||||||
|
const archivePath = path.join(tmpDir, "codeql-bundle.tar.zst");
|
||||||
|
const destination = path.join(tmpDir, "codeql");
|
||||||
|
const downloadTool = sinon
|
||||||
|
.stub(toolcache, "downloadTool")
|
||||||
|
.callsFake(async () => {
|
||||||
|
t.false(fs.existsSync(destination));
|
||||||
|
return archivePath;
|
||||||
|
});
|
||||||
|
const extract = sinon.stub(tar, "extract").resolves(destination);
|
||||||
|
const extractTarZst = sinon.stub(tar, "extractTarZst").resolves();
|
||||||
|
const request = nock("https://example.com")
|
||||||
|
.get("/codeql-bundle.tar.zst")
|
||||||
|
.reply(500);
|
||||||
|
|
||||||
|
const statusReport = await downloadAndExtract(
|
||||||
|
"https://example.com/codeql-bundle.tar.zst",
|
||||||
|
"zstd",
|
||||||
|
destination,
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
{ type: "gnu", version: "1.34" },
|
||||||
|
getRunnerLogger(true),
|
||||||
|
);
|
||||||
|
|
||||||
|
t.assert(Number.isInteger(statusReport.downloadDurationMs));
|
||||||
|
t.true(request.isDone());
|
||||||
|
t.false(extractTarZst.called);
|
||||||
|
t.true(downloadTool.calledOnce);
|
||||||
|
t.true(extract.calledOnce);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test.serial(
|
||||||
|
"downloadAndExtract handles an unknown status as a non-HTTP error",
|
||||||
|
async (t) => {
|
||||||
|
const asHTTPError = sinon.spy(util, "asHTTPError");
|
||||||
|
await util.withTmpDir(async (tmpDir) => {
|
||||||
|
sinon.stub(process, "platform").value("linux");
|
||||||
|
const archivePath = path.join(tmpDir, "codeql-bundle.tar.zst");
|
||||||
|
const destination = path.join(tmpDir, "codeql");
|
||||||
|
const response = sinon.createStubInstance(IncomingMessage);
|
||||||
|
response.statusCode = undefined;
|
||||||
|
sinon
|
||||||
|
.stub(https, "get")
|
||||||
|
.callsArgWith(2, response)
|
||||||
|
.returns(sinon.createStubInstance(ClientRequest));
|
||||||
|
const warning = sinon.stub(core, "warning");
|
||||||
|
const downloadTool = sinon
|
||||||
|
.stub(toolcache, "downloadTool")
|
||||||
|
.resolves(archivePath);
|
||||||
|
const extract = sinon.stub(tar, "extract").resolves(destination);
|
||||||
|
const extractTarZst = sinon.stub(tar, "extractTarZst").resolves();
|
||||||
|
|
||||||
|
await downloadAndExtract(
|
||||||
|
"https://example.com/codeql-bundle.tar.zst",
|
||||||
|
"zstd",
|
||||||
|
destination,
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
{ type: "gnu", version: "1.34" },
|
||||||
|
getRunnerLogger(true),
|
||||||
|
);
|
||||||
|
|
||||||
|
t.is(
|
||||||
|
warning.firstCall.args[0],
|
||||||
|
"Failed to download and extract CodeQL bundle using streaming with error: Failed to download CodeQL bundle from https://example.com/codeql-bundle.tar.zst.",
|
||||||
|
);
|
||||||
|
t.true(response.resume.calledOnce);
|
||||||
|
t.false(extractTarZst.called);
|
||||||
|
t.true(downloadTool.calledOnce);
|
||||||
|
t.true(extract.calledOnce);
|
||||||
|
});
|
||||||
|
|
||||||
|
t.true(asHTTPError.calledOnce);
|
||||||
|
t.true(asHTTPError.firstCall.args[0] instanceof Error);
|
||||||
|
t.false(asHTTPError.firstCall.args[0] instanceof util.HTTPError);
|
||||||
|
t.is(asHTTPError.firstCall.returnValue, undefined);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test.serial(
|
test.serial(
|
||||||
"downloadAndExtract reports only the total duration when streaming extraction",
|
"downloadAndExtract reports only the total duration when streaming extraction",
|
||||||
async (t) => {
|
async (t) => {
|
||||||
await withTmpDir(async (tmpDir) => {
|
await util.withTmpDir(async (tmpDir) => {
|
||||||
sinon.stub(process, "platform").value("linux");
|
sinon.stub(process, "platform").value("linux");
|
||||||
const downloadTool = sinon.stub(toolcache, "downloadTool");
|
const downloadTool = sinon.stub(toolcache, "downloadTool");
|
||||||
const extractTarZst = sinon
|
const extractTarZst = sinon
|
||||||
|
|||||||
@@ -14,7 +14,13 @@ import { ActionState } from "./action-common";
|
|||||||
import { ActionsEnvVars, getEnv, ReadOnlyEnv } from "./environment";
|
import { ActionsEnvVars, getEnv, ReadOnlyEnv } from "./environment";
|
||||||
import { formatDuration, Logger } from "./logging";
|
import { formatDuration, Logger } from "./logging";
|
||||||
import * as tar from "./tar";
|
import * as tar from "./tar";
|
||||||
import { cleanUpPath, getErrorMessage, getRequiredEnvParam } from "./util";
|
import {
|
||||||
|
asHTTPError,
|
||||||
|
cleanUpPath,
|
||||||
|
getErrorMessage,
|
||||||
|
getRequiredEnvParam,
|
||||||
|
HTTPError,
|
||||||
|
} from "./util";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* High watermark to use when streaming the download and extraction of the CodeQL tools.
|
* High watermark to use when streaming the download and extraction of the CodeQL tools.
|
||||||
@@ -88,14 +94,20 @@ export async function downloadAndExtract(
|
|||||||
return { totalDurationMs };
|
return { totalDurationMs };
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// If we failed during processing, we want to clean up the destination directory
|
||||||
|
// before we either try again or give up.
|
||||||
|
await cleanUpPath(dest, "CodeQL bundle", logger);
|
||||||
|
|
||||||
|
// Retrying a 404 is pointless: the asset does not exist, so downloading it a different way
|
||||||
|
// will fail in the same way.
|
||||||
|
if (asHTTPError(e)?.status === 404) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
core.warning(
|
core.warning(
|
||||||
`Failed to download and extract CodeQL bundle using streaming with error: ${getErrorMessage(e)}`,
|
`Failed to download and extract CodeQL bundle using streaming with error: ${getErrorMessage(e)}`,
|
||||||
);
|
);
|
||||||
core.warning(`Falling back to downloading the bundle before extracting.`);
|
core.warning(`Falling back to downloading the bundle before extracting.`);
|
||||||
|
|
||||||
// If we failed during processing, we want to clean up the destination directory
|
|
||||||
// before we try again.
|
|
||||||
await cleanUpPath(dest, "CodeQL bundle", logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const toolsDownloadStart = performance.now();
|
const toolsDownloadStart = performance.now();
|
||||||
@@ -191,9 +203,14 @@ async function downloadAndExtractZstdWithStreaming(
|
|||||||
if (response.statusCode !== 200) {
|
if (response.statusCode !== 200) {
|
||||||
// Discard the response body so that the connection can be released.
|
// Discard the response body so that the connection can be released.
|
||||||
response.resume();
|
response.resume();
|
||||||
throw new Error(
|
const baseMessage = `Failed to download CodeQL bundle from ${codeqlURL}.`;
|
||||||
`Failed to download CodeQL bundle from ${codeqlURL}. HTTP status code: ${response.statusCode}.`,
|
if (response.statusCode !== undefined) {
|
||||||
);
|
throw new HTTPError(
|
||||||
|
`${baseMessage} HTTP status code: ${response.statusCode}.`,
|
||||||
|
response.statusCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw new Error(baseMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
await tar.extractTarZst(response, dest, tarVersion, logger);
|
await tar.extractTarZst(response, dest, tarVersion, logger);
|
||||||
|
|||||||
Reference in New Issue
Block a user