修正(08-08-production-deployment-execution): 先验生产公网身份再冻结目录
这个提交包含在:
@@ -138,10 +138,19 @@ describe("deployment safety contract", () => {
|
||||
expect(remote).toContain("rollbackWindow=OPEN");
|
||||
expect(remote).not.toMatch(/\b(prisma migrate reset|prisma db push|prisma migrate resolve)\b/);
|
||||
expect(remote.indexOf("PRISMA_STATUS_BEGIN")).toBeLessThan(remote.indexOf("_prisma_migrations"));
|
||||
expect(bootstrap.indexOf("assertExactHistoricalContract")).toBeLessThan(
|
||||
bootstrap.indexOf("bootstrapDatabaseIdentityHash"),
|
||||
expect(bootstrap.indexOf("assertContractForOriginVerification")).toBeLessThan(
|
||||
bootstrap.indexOf("verifyOriginIdentity(identity, contract, expectedOrigin);"),
|
||||
);
|
||||
expect(bootstrap.indexOf("bootstrapDatabaseIdentityHash(identity")).toBeLessThan(
|
||||
expect(bootstrap.indexOf("verifyOriginIdentity(identity, contract, expectedOrigin);")).toBeLessThan(
|
||||
bootstrap.indexOf("const expectedRemoteDir = contract.REMOTE_DIR"),
|
||||
);
|
||||
expect(bootstrap.indexOf("const expectedRemoteDir = contract.REMOTE_DIR")).toBeLessThan(
|
||||
bootstrap.indexOf("const databaseIdentityHash = bootstrapDatabaseIdentityHash"),
|
||||
);
|
||||
expect(bootstrap.indexOf('if (identityOnly === "1")')).toBeLessThan(
|
||||
bootstrap.indexOf("const databaseIdentityHash = bootstrapDatabaseIdentityHash"),
|
||||
);
|
||||
expect(bootstrap.indexOf("const databaseIdentityHash = bootstrapDatabaseIdentityHash")).toBeLessThan(
|
||||
bootstrap.indexOf("spawnSync(\"bash\", [DEPLOY_EXISTING_SCRIPT]"),
|
||||
);
|
||||
});
|
||||
@@ -296,7 +305,6 @@ describe("deployment safety contract", () => {
|
||||
].join("\n");
|
||||
expect(bootstrap.parseHistoricalDeploymentIdentity(historicalTarget, historicalProject)).toEqual({
|
||||
deployServer: "deploy.production.invalid",
|
||||
remoteDir: "/srv/hyb-oa-trialrun",
|
||||
composeProjectName: "hyb-oa-trialrun",
|
||||
});
|
||||
expect(bootstrap.selectUniqueRefreshTarget([
|
||||
@@ -319,18 +327,27 @@ describe("deployment safety contract", () => {
|
||||
"POSTGRES_DATA_DIR=/srv/data/postgres",
|
||||
"STORAGE_HOST_DIR=/srv/data/storage",
|
||||
].join(" "));
|
||||
expect(() => bootstrap.assertExactHistoricalContract(
|
||||
expect(() => bootstrap.assertContractForOriginVerification(
|
||||
bootstrap.parseHistoricalDeploymentIdentity(historicalTarget, historicalProject),
|
||||
contract,
|
||||
)).not.toThrow();
|
||||
expect(() => bootstrap.assertExactHistoricalContract(
|
||||
const relocatedContract = {
|
||||
...contract,
|
||||
REMOTE_DIR: "/srv/current-production",
|
||||
REMOTE_ENV_FILE: "/srv/current-production/deploy/trialrun/.env",
|
||||
};
|
||||
expect(() => bootstrap.assertContractForOriginVerification(
|
||||
bootstrap.parseHistoricalDeploymentIdentity(historicalTarget, historicalProject),
|
||||
relocatedContract,
|
||||
)).not.toThrow();
|
||||
expect(() => bootstrap.assertContractForOriginVerification(
|
||||
bootstrap.parseHistoricalDeploymentIdentity(historicalTarget, historicalProject),
|
||||
{ ...contract, COMPOSE_PROJECT_NAME: "different" },
|
||||
)).toThrowError(expect.objectContaining({ code: "SYSTEMD_COMPOSE_PROJECT_MISMATCH" }));
|
||||
expect(() => bootstrap.parseHistoricalDeploymentIdentity(
|
||||
historicalTarget,
|
||||
historicalProject.replace("/srv/hyb-oa-trialrun", "/srv/different"),
|
||||
)).toThrowError(expect.objectContaining({ code: "HISTORICAL_REMOTE_DIR_DIVERGED" }));
|
||||
expect(() => bootstrap.assertContractForOriginVerification(
|
||||
bootstrap.parseHistoricalDeploymentIdentity(historicalTarget, historicalProject),
|
||||
{ ...contract, REMOTE_ENV_FILE: "/srv/different/.env" },
|
||||
)).toThrowError(expect.objectContaining({ code: "SYSTEMD_REMOTE_ENV_FILE_MISMATCH" }));
|
||||
|
||||
const bootstrapSource = await readFile(PRODUCTION_IDENTITY_BOOTSTRAP, "utf8");
|
||||
const remoteSource = await readFile(PRODUCTION_IDENTITY_BOOTSTRAP_REMOTE, "utf8");
|
||||
@@ -339,6 +356,46 @@ describe("deployment safety contract", () => {
|
||||
expect(remoteSource).toContain("DB_SHA256");
|
||||
expect(remoteSource).not.toContain("DB_BASE64");
|
||||
expect(remoteSource).not.toContain("prisma migrate status");
|
||||
expect(remoteSource.indexOf('IDENTITY_BOOTSTRAP_MODE" == "origin"')).toBeLessThan(
|
||||
remoteSource.indexOf("actual_db_name="),
|
||||
);
|
||||
});
|
||||
|
||||
it("verifies both origin samples before reading the remote database name", async () => {
|
||||
const fakeBin = path.join(fixtureRoot, "origin-bin");
|
||||
await mkdir(fakeBin, { recursive: true });
|
||||
await writeFile(path.join(fakeBin, "docker"), [
|
||||
"#!/usr/bin/env bash",
|
||||
"set -euo pipefail",
|
||||
"joined=\"$*\"",
|
||||
"if [[ \"$1\" == compose && \"${2:-}\" == version ]]; then exit 0; fi",
|
||||
"if [[ \"$1\" == ps ]]; then printf 'server1\\n'; exit 0; fi",
|
||||
"if [[ \"$1\" == inspect ]]; then printf 'APP_ORIGIN=https://example.invalid\\n'; exit 0; fi",
|
||||
"printf 'unexpected fake docker call: %s\\n' \"$joined\" >&2",
|
||||
"exit 97",
|
||||
"",
|
||||
].join("\n"), { mode: 0o700 });
|
||||
await chmod(path.join(fakeBin, "docker"), 0o700);
|
||||
const hash = createHash("sha256").update("https://example.invalid", "utf8").digest("hex");
|
||||
const runBootstrapRemote = (mode) => execFileAsync("bash", [PRODUCTION_IDENTITY_BOOTSTRAP_REMOTE], {
|
||||
cwd: remoteDir,
|
||||
env: {
|
||||
...process.env,
|
||||
PATH: `${fakeBin}${path.delimiter}${process.env.PATH}`,
|
||||
IDENTITY_BOOTSTRAP_MODE: mode,
|
||||
REMOTE_DIR: remoteDir,
|
||||
REMOTE_ENV_FILE: envFile,
|
||||
COMPOSE_FILE: "deploy/trialrun/docker-compose.yml",
|
||||
COMPOSE_PROJECT_NAME: "hyb-oa-trialrun",
|
||||
EXPECTED_PRODUCTION_ORIGIN_SHA256: hash,
|
||||
},
|
||||
});
|
||||
await expect(runBootstrapRemote("origin")).resolves.toMatchObject({
|
||||
stdout: "[production-identity-bootstrap][PASS] origin_identity\n",
|
||||
});
|
||||
await expect(runBootstrapRemote("database")).rejects.toMatchObject({
|
||||
stderr: expect.stringContaining("PROD_TARGET_DB_NAME_MISSING"),
|
||||
});
|
||||
});
|
||||
|
||||
it("runs the remote gate against synthetic status and migration history", async () => {
|
||||
|
||||
在新工单中引用
屏蔽一个用户