mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 11:04:27 +08:00
fix(ci): fail closed on knowledge deployment
This commit is contained in:
parent
4fc0d8bf2a
commit
0ca46ae273
22
.github/workflows/deploy-knowledge.yml
vendored
22
.github/workflows/deploy-knowledge.yml
vendored
@ -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"
|
||||
|
||||
1
.github/workflows/knowledge-fs-ci.yml
vendored
1
.github/workflows/knowledge-fs-ci.yml
vendored
@ -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:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"schemaVersion": 5,
|
||||
"subtreeTree": "43bd8308bed1f6254fda98c6be9773fd804aa3a7",
|
||||
"subtreeTree": "7e0b021106e0dd93e5a6145c062029baf4f05053",
|
||||
"openapiSha256": "37c8bdd6a6e7696aae3b336b0de215577ecda45535b1c2eb02d7a337b7399a95",
|
||||
"capabilityV2AuthManifestSha256": "fc0a47e23cce12544882f0298522b4933002e892b84ce1815df7e81d36a7a0c7",
|
||||
"capabilityV2AuthTestVectorSha256": "ae0de37b1ff05c40f905cf17a7b410d8971acacf64db07d5ee3d6fecfa559ce3",
|
||||
|
||||
@ -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/);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user