fix(ci): fail closed on knowledge deployment

This commit is contained in:
Jyong 2026-08-18 09:26:37 -04:00
parent 4fc0d8bf2a
commit 0ca46ae273
4 changed files with 62 additions and 1 deletions

View File

@ -68,4 +68,26 @@ jobs:
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
set -euo pipefail
deploy_script="$(mktemp)"
normalized_script="${deploy_script}.normalized"
cleanup() {
rm -f "$deploy_script" "$normalized_script"
}
trap cleanup EXIT
cat > "$deploy_script" <<'DIFY_KNOWLEDGE_DEPLOY_SCRIPT'
${{ vars.SSH_KNOWLEDGE_SCRIPT || secrets.SSH_KNOWLEDGE_SCRIPT }}
DIFY_KNOWLEDGE_DEPLOY_SCRIPT
if [[ ! -s "$deploy_script" ]]; then
echo "KnowledgeFS deploy script is empty." >&2
exit 1
fi
# GitHub variables and secrets can retain Windows CRLF line endings.
# Normalize them before Bash parses options such as `set -eu`.
tr -d '\r' < "$deploy_script" > "$normalized_script"
mv "$normalized_script" "$deploy_script"
bash -e -u -o pipefail "$deploy_script"

View File

@ -107,6 +107,7 @@ jobs:
- 'docker/generate_docker_compose'
- 'docs/design/knowledge-fs*'
- '.github/dependabot.yml'
- '.github/workflows/deploy-knowledge.yml'
- '.github/workflows/knowledge-fs-ci.yml'
build:

View File

@ -1,6 +1,6 @@
{
"schemaVersion": 5,
"subtreeTree": "43bd8308bed1f6254fda98c6be9773fd804aa3a7",
"subtreeTree": "7e0b021106e0dd93e5a6145c062029baf4f05053",
"openapiSha256": "37c8bdd6a6e7696aae3b336b0de215577ecda45535b1c2eb02d7a337b7399a95",
"capabilityV2AuthManifestSha256": "fc0a47e23cce12544882f0298522b4933002e892b84ce1815df7e81d36a7a0c7",
"capabilityV2AuthTestVectorSha256": "ae0de37b1ff05c40f905cf17a7b410d8971acacf64db07d5ee3d6fecfa559ce3",

View File

@ -1,4 +1,5 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { readFileSync } from "node:fs";
import test from "node:test";
import { parse } from "yaml";
@ -12,6 +13,10 @@ const workflow = readFileSync(
new URL("../../.github/workflows/knowledge-fs-ci.yml", import.meta.url),
"utf8",
);
const deployWorkflow = readFileSync(
new URL("../../.github/workflows/deploy-knowledge.yml", import.meta.url),
"utf8",
);
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
const apiPackageJson = JSON.parse(
readFileSync(new URL("../packages/api/package.json", import.meta.url), "utf8"),
@ -19,6 +24,7 @@ const apiPackageJson = JSON.parse(
const lockfile = readFileSync(new URL("../pnpm-lock.yaml", import.meta.url), "utf8");
const dependabot = readFileSync(new URL("../../.github/dependabot.yml", import.meta.url), "utf8");
const workflowDocument = parse(workflow);
const deployWorkflowDocument = parse(deployWorkflow);
const pathsFilterStep = workflowDocument.jobs["check-changes"].steps.find((step) =>
step.uses?.startsWith("dorny/paths-filter@"),
);
@ -76,6 +82,7 @@ test("root workflow scopes expensive checks internally", () => {
assert.match(workflow, /- 'api\/pyproject\.toml'/);
assert.match(workflow, /- 'api\/uv\.lock'/);
assert.match(workflow, /- '\.github\/dependabot\.yml'/);
assert.match(workflow, /- '\.github\/workflows\/deploy-knowledge\.yml'/);
assert.match(workflow, /needs\.check-changes\.outputs\.knowledge-fs == 'true'/);
assert.match(workflow, /^ {2}skip:$/m);
});
@ -94,9 +101,40 @@ test("workflow paths stay in parity with every auditable integration touchpoint"
test("root workflow and Dependabot configuration are valid YAML", () => {
assert.doesNotThrow(() => parse(workflow));
assert.doesNotThrow(() => parse(deployWorkflow));
assert.doesNotThrow(() => parse(dependabot));
});
test("deploy workflow normalizes CRLF scripts and fails closed", () => {
const deployStep = deployWorkflowDocument.jobs.deploy.steps.find(
(step) => step.name === "Deploy to server",
);
assert.ok(deployStep, "workflow is missing the deploy step");
assert.match(deployStep.uses, /^appleboy\/ssh-action@[0-9a-f]{40}$/);
const scriptExpression = "${{ vars.SSH_KNOWLEDGE_SCRIPT || secrets.SSH_KNOWLEDGE_SCRIPT }}";
const wrapper = deployStep.with.script;
assert.equal(wrapper.split(scriptExpression).length - 1, 1);
assert.match(wrapper, /^set -euo pipefail$/m);
assert.match(wrapper, /tr -d '\\r'/);
assert.match(wrapper, /bash -e -u -o pipefail "\$deploy_script"/);
const execute = (injectedScript) => {
assert.ok(wrapper.includes(scriptExpression));
return spawnSync("bash", ["-c", wrapper.replace(scriptExpression, injectedScript)], {
encoding: "utf8",
});
};
const success = execute("set -eu\r\nprintf 'normalized\\n'\r\n");
assert.equal(success.status, 0, success.stderr);
assert.equal(success.stdout, "normalized\n");
const failure = execute("set -eu\r\nfalse\r\nprintf 'must-not-run\\n'\r\n");
assert.notEqual(failure.status, 0);
assert.doesNotMatch(failure.stdout, /must-not-run/);
});
test("root workflow preserves the independent KnowledgeFS pnpm workspace", () => {
assert.equal(packageJson.packageManager, "pnpm@10.33.0");
assert.match(workflow, /working-directory: \.\/knowledge-fs/);