Public release release-6d1a121b0a8a
Source commit: 6d1a121b0a8a636aac95998fe5d15c24c78eb7d0 Public tree identity: sha256:2bc22807f5b838286678b65d3f550b8ae4714ae673622041d4c2516a7c5b7926
This commit is contained in:
commit
21f097d9a8
210 changed files with 78649 additions and 0 deletions
365
scripts/cli-error-exit-smoke.js
Executable file
365
scripts/cli-error-exit-smoke.js
Executable file
|
|
@ -0,0 +1,365 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const assert = require("assert");
|
||||
const cp = require("child_process");
|
||||
const fs = require("fs");
|
||||
const http = require("http");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
|
||||
const repo = path.resolve(__dirname, "..");
|
||||
const project = path.join(repo, "examples/launch-build-demo");
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "clusterflux-cli-error-"));
|
||||
|
||||
function runClusterflux(args) {
|
||||
return new Promise((resolve) => {
|
||||
const child = cp.spawn(
|
||||
"cargo",
|
||||
["run", "-q", "-p", "clusterflux-cli", "--bin", "clusterflux", "--", ...args],
|
||||
{
|
||||
cwd: repo,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
}
|
||||
);
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
child.stdout.setEncoding("utf8");
|
||||
child.stderr.setEncoding("utf8");
|
||||
child.stdout.on("data", (chunk) => {
|
||||
stdout += chunk;
|
||||
});
|
||||
child.stderr.on("data", (chunk) => {
|
||||
stderr += chunk;
|
||||
});
|
||||
child.on("close", (code, signal) => {
|
||||
resolve({ code, signal, stdout, stderr });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function runWithOneCoordinatorResponse(buildArgs, response) {
|
||||
let request = "";
|
||||
const server = http.createServer((incoming, outgoing) => {
|
||||
incoming.setEncoding("utf8");
|
||||
incoming.on("data", (chunk) => {
|
||||
request += chunk;
|
||||
});
|
||||
incoming.on("end", () => {
|
||||
outgoing.writeHead(200, { "content-type": "application/json" });
|
||||
outgoing.end(JSON.stringify(response));
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
|
||||
const address = await new Promise((resolve) => {
|
||||
server.listen(0, "127.0.0.1", () => resolve(server.address()));
|
||||
});
|
||||
const coordinator = `http://${address.address}:${address.port}`;
|
||||
const result = await runClusterflux(buildArgs(coordinator));
|
||||
return { request, result };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const environmentProject = path.join(tempRoot, "missing-env-project");
|
||||
fs.mkdirSync(path.join(environmentProject, "src"), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(environmentProject, "Cargo.toml"),
|
||||
"[package]\nname = \"missing-env-project\"\nversion = \"0.1.0\"\nedition = \"2021\"\n"
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(environmentProject, "src", "main.rs"),
|
||||
"fn main() { let _target = env!(\"linux\"); }\n"
|
||||
);
|
||||
const environmentFailure = await runClusterflux([
|
||||
"build",
|
||||
"--project",
|
||||
environmentProject,
|
||||
"--json",
|
||||
]);
|
||||
assert.strictEqual(environmentFailure.signal, null, environmentFailure.stderr);
|
||||
assert.strictEqual(environmentFailure.code, 26, environmentFailure.stderr);
|
||||
const environmentReport = JSON.parse(environmentFailure.stdout);
|
||||
assert.strictEqual(environmentReport.status, "blocked_before_schedule");
|
||||
assert.strictEqual(environmentReport.scheduled_work, false);
|
||||
assert.strictEqual(environmentReport.machine_error.category, "environment");
|
||||
assert.strictEqual(
|
||||
environmentReport.machine_error.process_exit_code_applied,
|
||||
true
|
||||
);
|
||||
assert(
|
||||
environmentReport.machine_error.next_actions.includes("clusterflux inspect")
|
||||
);
|
||||
assert.strictEqual(environmentReport.diagnostics[0].code, "missing_environment");
|
||||
|
||||
const nonInteractive = await runClusterflux([
|
||||
"run",
|
||||
"build",
|
||||
"--project",
|
||||
project,
|
||||
"--non-interactive",
|
||||
"--json",
|
||||
]);
|
||||
assert.strictEqual(nonInteractive.signal, null, nonInteractive.stderr);
|
||||
assert.strictEqual(nonInteractive.code, 20, nonInteractive.stderr);
|
||||
assert.doesNotMatch(nonInteractive.stderr, /Opening Clusterflux browser login/);
|
||||
const nonInteractiveReport = JSON.parse(nonInteractive.stdout);
|
||||
assert.strictEqual(nonInteractiveReport.status, "authentication_required");
|
||||
assert.strictEqual(nonInteractiveReport.non_interactive, true);
|
||||
assert.strictEqual(nonInteractiveReport.browser_opened, false);
|
||||
assert.strictEqual(nonInteractiveReport.machine_error.category, "authentication");
|
||||
assert.strictEqual(nonInteractiveReport.machine_error.stable_exit_code, 20);
|
||||
assert.strictEqual(
|
||||
nonInteractiveReport.machine_error.process_exit_code_applied,
|
||||
true
|
||||
);
|
||||
assert(
|
||||
nonInteractiveReport.machine_error.next_actions.includes(
|
||||
"pass --local to run against local services"
|
||||
)
|
||||
);
|
||||
|
||||
let request = "";
|
||||
const server = http.createServer((incoming, outgoing) => {
|
||||
incoming.setEncoding("utf8");
|
||||
incoming.on("data", (chunk) => {
|
||||
request += chunk;
|
||||
});
|
||||
incoming.on("end", () => {
|
||||
outgoing.writeHead(200, { "content-type": "application/json" });
|
||||
outgoing.end(
|
||||
JSON.stringify({
|
||||
type: "error",
|
||||
message: "quota unavailable: resource limit exceeded for api_calls",
|
||||
})
|
||||
);
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
|
||||
const address = await new Promise((resolve) => {
|
||||
server.listen(0, "127.0.0.1", () => resolve(server.address()));
|
||||
});
|
||||
const coordinator = `http://${address.address}:${address.port}`;
|
||||
|
||||
const result = await runClusterflux([
|
||||
"run",
|
||||
"build",
|
||||
"--project",
|
||||
project,
|
||||
"--coordinator",
|
||||
coordinator,
|
||||
"--json",
|
||||
]);
|
||||
|
||||
assert.strictEqual(result.signal, null, result.stderr);
|
||||
assert.strictEqual(result.code, 22, result.stderr);
|
||||
assert.match(request, /"type":"start_process"/);
|
||||
const report = JSON.parse(result.stdout);
|
||||
assert.strictEqual(report.status, "coordinator_rejected");
|
||||
assert.strictEqual(report.run_start.machine_error.category, "quota");
|
||||
assert.strictEqual(report.run_start.machine_error.resource_category, "api_calls");
|
||||
assert.strictEqual(report.run_start.machine_error.community_tier_language, true);
|
||||
assert.strictEqual(
|
||||
report.run_start.machine_error.community_tier_label,
|
||||
"community tier"
|
||||
);
|
||||
assert.doesNotMatch(result.stdout, new RegExp(["free", "tier"].join(" "), "i"));
|
||||
assert.strictEqual(
|
||||
report.run_start.machine_error.private_abuse_heuristics_exposed,
|
||||
false
|
||||
);
|
||||
assert.strictEqual(report.run_start.machine_error.stable_exit_code, 22);
|
||||
assert.strictEqual(
|
||||
report.run_start.machine_error.process_exit_code_applied,
|
||||
true
|
||||
);
|
||||
assert(
|
||||
report.run_start.machine_error.next_actions.includes("clusterflux quota status")
|
||||
);
|
||||
|
||||
const capabilityFailure = await runWithOneCoordinatorResponse(
|
||||
(coordinator) => [
|
||||
"run",
|
||||
"build",
|
||||
"--project",
|
||||
project,
|
||||
"--coordinator",
|
||||
coordinator,
|
||||
"--json",
|
||||
],
|
||||
{
|
||||
type: "error",
|
||||
message:
|
||||
"scheduler placement failed: no capable node for placement: missing capability Command",
|
||||
}
|
||||
);
|
||||
assert.strictEqual(capabilityFailure.result.signal, null, capabilityFailure.result.stderr);
|
||||
assert.strictEqual(capabilityFailure.result.code, 24, capabilityFailure.result.stderr);
|
||||
assert.match(capabilityFailure.request, /"type":"start_process"/);
|
||||
const capabilityReport = JSON.parse(capabilityFailure.result.stdout);
|
||||
assert.strictEqual(
|
||||
capabilityReport.run_start.machine_error.category,
|
||||
"capability"
|
||||
);
|
||||
assert.strictEqual(
|
||||
capabilityReport.run_start.machine_error.process_exit_code_applied,
|
||||
true
|
||||
);
|
||||
assert(
|
||||
capabilityReport.run_start.machine_error.next_actions.includes(
|
||||
"attach a node with the required capabilities"
|
||||
)
|
||||
);
|
||||
|
||||
const nodePolicyFailure = await runWithOneCoordinatorResponse(
|
||||
(coordinator) => [
|
||||
"run",
|
||||
"build",
|
||||
"--project",
|
||||
project,
|
||||
"--coordinator",
|
||||
coordinator,
|
||||
"--json",
|
||||
],
|
||||
{
|
||||
type: "error",
|
||||
message: "node policy denied native command execution",
|
||||
}
|
||||
);
|
||||
assert.strictEqual(nodePolicyFailure.result.signal, null, nodePolicyFailure.result.stderr);
|
||||
assert.strictEqual(nodePolicyFailure.result.code, 23, nodePolicyFailure.result.stderr);
|
||||
assert.match(nodePolicyFailure.request, /"type":"start_process"/);
|
||||
const nodePolicyReport = JSON.parse(nodePolicyFailure.result.stdout);
|
||||
assert.strictEqual(
|
||||
nodePolicyReport.run_start.machine_error.category,
|
||||
"policy"
|
||||
);
|
||||
assert.strictEqual(
|
||||
nodePolicyReport.run_start.machine_error.process_exit_code_applied,
|
||||
true
|
||||
);
|
||||
assert(
|
||||
nodePolicyReport.run_start.machine_error.next_actions.includes(
|
||||
"check coordinator policy for this action"
|
||||
)
|
||||
);
|
||||
|
||||
const programFailure = await runWithOneCoordinatorResponse(
|
||||
(coordinator) => [
|
||||
"task",
|
||||
"list",
|
||||
"--coordinator",
|
||||
coordinator,
|
||||
"--json",
|
||||
],
|
||||
{
|
||||
type: "task_events",
|
||||
events: [
|
||||
{
|
||||
process: "vp-current",
|
||||
task: "compile",
|
||||
terminal_state: "failed",
|
||||
environment: "linux",
|
||||
node: "node-linux",
|
||||
status_code: 1,
|
||||
stderr_tail: "task exited with status 1",
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
assert.strictEqual(programFailure.result.signal, null, programFailure.result.stderr);
|
||||
assert.strictEqual(programFailure.result.code, 0, programFailure.result.stderr);
|
||||
assert.match(programFailure.request, /"type":"list_task_events"/);
|
||||
const programReport = JSON.parse(programFailure.result.stdout);
|
||||
assert.strictEqual(programReport.tasks[0].machine_error.category, "program");
|
||||
assert.strictEqual(programReport.tasks[0].machine_error.stable_exit_code, 27);
|
||||
assert(
|
||||
programReport.tasks[0].machine_error.next_actions.includes("clusterflux logs")
|
||||
);
|
||||
|
||||
const artifactDownload = await runWithOneCoordinatorResponse(
|
||||
(coordinator) => [
|
||||
"artifact",
|
||||
"download",
|
||||
"app.txt",
|
||||
"--coordinator",
|
||||
coordinator,
|
||||
"--json",
|
||||
],
|
||||
{
|
||||
type: "artifact_download_denied",
|
||||
message: "artifact download unauthorized for project",
|
||||
}
|
||||
);
|
||||
assert.strictEqual(artifactDownload.result.signal, null, artifactDownload.result.stderr);
|
||||
assert.strictEqual(artifactDownload.result.code, 21, artifactDownload.result.stderr);
|
||||
assert.match(artifactDownload.request, /"type":"create_artifact_download_link"/);
|
||||
const artifactDownloadReport = JSON.parse(artifactDownload.result.stdout);
|
||||
assert.strictEqual(
|
||||
artifactDownloadReport.download_session.machine_error.category,
|
||||
"authorization"
|
||||
);
|
||||
assert.strictEqual(
|
||||
artifactDownloadReport.download_session.machine_error.process_exit_code_applied,
|
||||
true
|
||||
);
|
||||
|
||||
const artifactExport = await runWithOneCoordinatorResponse(
|
||||
(coordinator) => [
|
||||
"artifact",
|
||||
"export",
|
||||
"app.txt",
|
||||
"--to",
|
||||
path.join(project, "target", "blocked-artifact.txt"),
|
||||
"--coordinator",
|
||||
coordinator,
|
||||
"--json",
|
||||
],
|
||||
{
|
||||
type: "artifact_export_unavailable",
|
||||
message: "direct connectivity unavailable for artifact export",
|
||||
}
|
||||
);
|
||||
assert.strictEqual(artifactExport.result.signal, null, artifactExport.result.stderr);
|
||||
assert.strictEqual(artifactExport.result.code, 25, artifactExport.result.stderr);
|
||||
assert.match(artifactExport.request, /"type":"export_artifact_to_node"/);
|
||||
const artifactExportReport = JSON.parse(artifactExport.result.stdout);
|
||||
assert.strictEqual(
|
||||
artifactExportReport.export_plan.machine_error.category,
|
||||
"connectivity"
|
||||
);
|
||||
assert.strictEqual(
|
||||
artifactExportReport.export_plan.machine_error.process_exit_code_applied,
|
||||
true
|
||||
);
|
||||
|
||||
const confirmation = await runClusterflux([
|
||||
"process",
|
||||
"cancel",
|
||||
"--coordinator",
|
||||
"127.0.0.1:9",
|
||||
"--json",
|
||||
]);
|
||||
assert.strictEqual(confirmation.signal, null, confirmation.stderr);
|
||||
assert.strictEqual(confirmation.code, 23, confirmation.stderr);
|
||||
const confirmationReport = JSON.parse(confirmation.stdout);
|
||||
assert.strictEqual(confirmationReport.status, "confirmation_required");
|
||||
assert.strictEqual(confirmationReport.coordinator_request_sent, false);
|
||||
assert.strictEqual(confirmationReport.machine_error.category, "policy");
|
||||
assert.strictEqual(
|
||||
confirmationReport.machine_error.process_exit_code_applied,
|
||||
true
|
||||
);
|
||||
assert(
|
||||
confirmationReport.next_actions.some((action) => action.includes("--yes"))
|
||||
);
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => {
|
||||
console.log("CLI error exit smoke passed");
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue