mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:21:50 +08:00
301 lines
12 KiB
JavaScript
301 lines
12 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const { test } = require('node:test');
|
|
const yaml = require('js-yaml');
|
|
const {
|
|
createFixture,
|
|
} = require('../../scripts/ql3-plugin-package-recovery-e2e-fixture.cjs');
|
|
const {
|
|
runtimeDeploymentResources,
|
|
runtimeKeyringMaterializationSource,
|
|
} = require('../../scripts/ql3-plugin-package-recovery-e2e-live-contract.cjs');
|
|
const {
|
|
CONTRACT_VERSION,
|
|
MIGRATION_COUNT,
|
|
} = require('../../scripts/ql3-plugin-package-recovery-e2e-live-audit.cjs');
|
|
const {
|
|
postgresqlControlSchemaContract,
|
|
postgresqlMainMigrationStream,
|
|
} = require('../../packages/ql3-cluster-postgres/dist/migration/migration.js');
|
|
|
|
const root = path.resolve(__dirname, '../..');
|
|
const livePath = path.join(
|
|
root,
|
|
'scripts/ql3-plugin-package-recovery-e2e-live-contract.cjs',
|
|
);
|
|
const fixturePath = path.join(
|
|
root,
|
|
'scripts/ql3-plugin-package-recovery-e2e-fixture.cjs',
|
|
);
|
|
const live = fs.readFileSync(livePath, 'utf8');
|
|
const fixture = fs.readFileSync(fixturePath, 'utf8');
|
|
const packageJson = JSON.parse(
|
|
fs.readFileSync(path.join(root, 'package.json'), 'utf8'),
|
|
);
|
|
const workflow = yaml.load(
|
|
fs.readFileSync(path.join(root, '.github/workflows/ql3-ci.yml'), 'utf8'),
|
|
);
|
|
|
|
test('E2E live gate is opt-in and owns only one exact disposable Kind cluster', () => {
|
|
assert.match(live, /QL3_PLUGIN_PACKAGE_RECOVERY_E2E_LIVE !== '1'/);
|
|
assert.match(live, /\^ql3-plugin-recovery-e2e/);
|
|
assert.match(live, /Refusing to reuse or delete pre-existing Kind cluster/);
|
|
assert.match(live, /kind\(\['delete', 'cluster', '--name', clusterName\]/);
|
|
assert.match(live, /QL3_KEEP_KIND_CLUSTER/);
|
|
assert.match(live, /kindest\/node:v1\.32\.8@sha256:/);
|
|
assert.match(
|
|
live,
|
|
/image', 'tag', POSTGRES_IMAGE_REFERENCE, POSTGRES_RUNTIME_IMAGE/,
|
|
);
|
|
assert.match(live, /image: POSTGRES_RUNTIME_IMAGE/);
|
|
assert.match(live, /imagePullPolicy: 'Never'/);
|
|
assert.match(
|
|
live,
|
|
/condition\.type === 'Failed' && condition\.status === 'True'/,
|
|
);
|
|
});
|
|
|
|
test('fixture uses a real HTTPS and content-addressed OCI Distribution surface', () => {
|
|
assert.match(fixture, /https\.createServer/);
|
|
assert.match(fixture, /docker-distribution-api-version/);
|
|
assert.match(fixture, /\/referrers\/sha256:/);
|
|
assert.match(fixture, /PLUGIN_PACKAGE_OCI_SIGNATURE_ARTIFACT_TYPE/);
|
|
assert.match(fixture, /pluginPackagePublisherSignaturePayload/);
|
|
assert.match(fixture, /canonicalTar/);
|
|
assert.match(live, /NODE_EXTRA_CA_CERTS/);
|
|
assert.match(live, /createRegistryCertificate/);
|
|
assert.match(live, /requestCount: packageRequests\.length/);
|
|
});
|
|
|
|
test('report migration evidence follows the complete PostgreSQL stream', () => {
|
|
assert.equal(MIGRATION_COUNT, postgresqlMainMigrationStream.migrations.length);
|
|
assert.equal(
|
|
CONTRACT_VERSION,
|
|
postgresqlControlSchemaContract.contractVersion,
|
|
);
|
|
});
|
|
|
|
test('waits for PostgreSQL Service connectivity before migration', () => {
|
|
const readyProbe = live.indexOf('waitForPostgresService();');
|
|
const migration = live.indexOf(
|
|
"apply(migrationJob(), 'create reviewed migration Job')",
|
|
);
|
|
assert.ok(readyProbe > 0);
|
|
assert.ok(migration > readyProbe);
|
|
assert.match(live, /'pg_isready',[\s\S]*'--host',[\s\S]*POSTGRES_NAME/);
|
|
});
|
|
|
|
test('fixture locks are bound to durable version-three approval dispatches', () => {
|
|
const value = createFixture({
|
|
registry: 'registry.fixture.test',
|
|
architecture: 'amd64',
|
|
createdAtMs: 1_000,
|
|
});
|
|
for (const selected of [value.initial, value.upgrade]) {
|
|
const proposal = selected.authority.proposalCommand.proposal;
|
|
const dispatch = selected.authority.dispatch;
|
|
assert.equal(selected.lock.approval.requestVersion, 3);
|
|
assert.equal(selected.lock.approval.dispatchId, dispatch.id);
|
|
assert.equal(selected.lock.actionDigest, proposal.actionDigest);
|
|
assert.equal(selected.lock.planDigest, proposal.previewDigest);
|
|
assert.equal(dispatch.action.actionRef, proposal.actionRef);
|
|
assert.equal(
|
|
selected.authority.consumptionCommand.dispatchId,
|
|
dispatch.id,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('gate runs migration, healthy activation and a durable rejected upgrade', () => {
|
|
assert.match(live, /operations\/base\/migrate-job\.yaml/);
|
|
assert.match(
|
|
live,
|
|
/CREATE ROLE ql3_automation_manager LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS/,
|
|
);
|
|
assert.match(
|
|
live,
|
|
/CREATE ROLE ql3_approval_manager LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS/,
|
|
);
|
|
assert.match(
|
|
live,
|
|
/CREATE ROLE ql3_run_manager LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS/,
|
|
);
|
|
assert.match(live, /plugin-package-recovery\/base\/recover-job\.yaml/);
|
|
assert.match(fixture, /PostgresPluginPackageInstallRepository/);
|
|
assert.match(fixture, /PostgresPluginPackageInstallProposalRepository/);
|
|
assert.match(fixture, /PostgresApprovalRequestRepository/);
|
|
assert.match(fixture, /PostgresApprovedActionExecutionRepository/);
|
|
assert.match(fixture, /PostgresProjectPolicyRepository/);
|
|
assert.match(fixture, /\.createProposal\(selected\.authority\.proposalCommand\)/);
|
|
assert.match(fixture, /\.consume\(selected\.authority\.consumptionCommand\)/);
|
|
assert.match(fixture, /repository\.admit\(/);
|
|
assert.doesNotMatch(fixture, /pluginPackageInstallCreate/);
|
|
assert.match(
|
|
fixture,
|
|
/PostgresPluginPackagePublisherTrustAuthorityRepository/,
|
|
);
|
|
assert.match(fixture, /createPluginPackagePublisherTrustSnapshot/);
|
|
assert.match(fixture, /role: 'package-manager'/);
|
|
assert.match(fixture, /authorityId: 'cluster'/);
|
|
assert.match(fixture, /publisherTrustStatus/);
|
|
assert.match(live, /QL3_E2E_POSTGRES_PACKAGE_MANAGER_USER/);
|
|
assert.match(live, /key: 'package-manager-password'/);
|
|
assert.match(live, /QL3_E2E_POSTGRES_RUNTIME_USER/);
|
|
assert.match(live, /key: 'runtime-password'/);
|
|
assert.doesNotMatch(fixture, /commit-transition/);
|
|
assert.match(live, /initialSeed\.state, 'queued'/);
|
|
assert.match(live, /upgradeSeed\.state, 'queued'/);
|
|
assert.match(live, /value\.migrationCount, MIGRATION_COUNT/);
|
|
assert.match(live, /value\.capabilityVersion, CONTRACT_VERSION/);
|
|
assert.match(live, /postgresEnvironment\(\s*'PACKAGE_EXECUTOR'/);
|
|
assert.match(fixture, /assertPostgresPackageExecutorSchemaReady/);
|
|
assert.match(live, /value\.initialState, 'active'/);
|
|
assert.match(live, /value\.upgradeState, 'failed'/);
|
|
assert.match(live, /value\.upgradeFailureReason, 'activation_fact_conflict'/);
|
|
assert.match(live, /value\.upgradeRevisionCount, 0/);
|
|
assert.match(live, /value\.recoverableCount, 0/);
|
|
});
|
|
|
|
test('deployment controller rejects the upgrade before creating runtime', () => {
|
|
const rejectionWait = live.indexOf('waitForJob(UPGRADE_RECOVERY_JOB)');
|
|
const pointerProof = live.indexOf(
|
|
'assert.deepEqual(pointerAfterRejection, pointerBeforeUpgrade)',
|
|
);
|
|
const runtimeApply = live.indexOf(
|
|
'const runtime = applyRuntimeAfterRecovery(',
|
|
);
|
|
assert.ok(rejectionWait > 0);
|
|
assert.ok(pointerProof > rejectionWait);
|
|
assert.ok(runtimeApply > pointerProof);
|
|
assert.match(live, /activePointerUnchanged: true/);
|
|
assert.doesNotMatch(live, /missingTransitionFailedClosed/);
|
|
assert.match(live, /qinglong\.io\/plugin-recovery-job-uid/);
|
|
assert.match(live, /qinglong\.io\/plugin-recovery-completed-at/);
|
|
assert.match(live, /rollout[\s\S]*status/);
|
|
assert.match(live, /availableReplicas, 2/);
|
|
assert.match(
|
|
live,
|
|
/new Set\(pods\.items\.map\(\(pod\) => pod\.spec\.nodeName\)\)/,
|
|
);
|
|
});
|
|
|
|
test('runtime deployment preserves private regular-file keyring materialization', () => {
|
|
const resources = runtimeDeploymentResources({
|
|
'qinglong.io/test': 'runtime-materialization',
|
|
});
|
|
const deployment = resources.find(
|
|
(resource) => resource.kind === 'Deployment',
|
|
);
|
|
const pod = deployment.spec.template.spec;
|
|
const materializer = pod.initContainers[0];
|
|
const container = pod.containers[0];
|
|
assert.equal(materializer.name, 'materialize-runtime-files');
|
|
assert.equal(materializer.image, container.image);
|
|
assert.equal(materializer.imagePullPolicy, 'Never');
|
|
assert.deepEqual(materializer.command.slice(0, 2), ['node', '-e']);
|
|
assert.equal(materializer.command[2], runtimeKeyringMaterializationSource());
|
|
assert.match(materializer.command[2], /realpathSync/);
|
|
assert.match(materializer.command[2], /COPYFILE_EXCL/);
|
|
assert.match(materializer.command[2], /chmodSync\(output,0o400\)/);
|
|
assert.equal(
|
|
container.env.find(
|
|
(entry) => entry.name === 'QL3_API_CREDENTIAL_PEPPER_KEYRING_FILE',
|
|
).value,
|
|
'/var/run/secrets/qinglong3/runtime/keyring.json',
|
|
);
|
|
assert.equal(
|
|
container.volumeMounts.some((mount) => mount.name.includes('projected')),
|
|
false,
|
|
);
|
|
assert.deepEqual(
|
|
pod.volumes.map((volume) => volume.name),
|
|
['tmp', 'api-credential-keyring-projected', 'runtime-private'],
|
|
);
|
|
});
|
|
|
|
test('recovery Job keeps exact ConfigMap-only RBAC and runtime cannot read install authority', () => {
|
|
assert.match(
|
|
live,
|
|
/resources: \['configmaps'\],[\s\S]*verbs: \['get', 'create', 'update'\]/,
|
|
);
|
|
assert.match(live, /listConfigMaps: false/);
|
|
assert.match(live, /deleteConfigMaps: false/);
|
|
assert.match(live, /getSecrets: false/);
|
|
assert.match(live, /SELECT count\(\*\) FROM ql3\.plugin_package_installs/);
|
|
assert.match(live, /permission denied/i);
|
|
});
|
|
|
|
test('package script and independent CI job execute the full gate', () => {
|
|
assert.equal(
|
|
packageJson.scripts['test:plugin-package-recovery-e2e:ql3'],
|
|
'pnpm --filter @qinglong/cluster-admin check && pnpm --filter @qinglong/cluster-control check && node scripts/ql3-plugin-package-recovery-e2e-live-contract.cjs',
|
|
);
|
|
const job = workflow.jobs['cluster-plugin-package-recovery-e2e'];
|
|
assert.ok(job);
|
|
assert.equal(job['timeout-minutes'], 35);
|
|
assert.equal(
|
|
packageJson.scripts['audit:plugin-package-recovery-e2e:ql3'],
|
|
'node scripts/ql3-plugin-package-recovery-e2e-live-audit.cjs',
|
|
);
|
|
assert.ok(
|
|
job.steps.some(
|
|
(step) =>
|
|
String(step.run).includes(
|
|
'pnpm test:plugin-package-recovery-e2e:ql3',
|
|
) &&
|
|
String(step.run).includes('--report=') &&
|
|
String(step.run).includes('pnpm audit:plugin-package-recovery-e2e:ql3'),
|
|
),
|
|
);
|
|
assert.ok(
|
|
job.steps.some(
|
|
(step) =>
|
|
step.env?.QL3_SOURCE_REVISION === '${{ github.sha }}' &&
|
|
step.env?.QL3_PLUGIN_PACKAGE_RECOVERY_E2E_LIVE === '1',
|
|
),
|
|
);
|
|
assert.ok(
|
|
job.steps.some(
|
|
(step) =>
|
|
step.if === 'always()' &&
|
|
String(step.uses).startsWith('actions/upload-artifact@') &&
|
|
step.with?.['retention-days'] === 14,
|
|
),
|
|
);
|
|
assert.ok(
|
|
job.steps.some((step) =>
|
|
String(step.run).includes(
|
|
'postgres:18.4-bookworm@sha256:1961f96e6029a02c3812d7cb329a3b03a3ac2bb067058dec17b0f5596aca9296',
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
test('gate persists only a source-bound low-sensitive private report', () => {
|
|
assert.match(live, /privateReportPath\(argv\)/);
|
|
assert.match(live, /writePrivateReport\(reportFile, report\)/);
|
|
assert.match(live, /QL3_SOURCE_REVISION/);
|
|
assert.match(live, /org\.opencontainers\.image\.revision/);
|
|
assert.match(live, /activeJsonDigest/);
|
|
assert.doesNotMatch(live, /activePointer: pointerAfterRejection/);
|
|
assert.match(live, /reportWritten: true/);
|
|
});
|
|
|
|
test('private Registry evidence uses one exact Secret file and authenticated requests', () => {
|
|
assert.match(fixture, /request\.headers\.authorization !== authorization/);
|
|
assert.match(fixture, /www-authenticate/);
|
|
assert.match(fixture, /authenticated,/);
|
|
assert.match(live, /QL3_PLUGIN_PACKAGE_REGISTRY_CREDENTIAL_FILE/);
|
|
assert.match(live, /qinglong\/plugin-package-registry-credentials@v1/);
|
|
assert.match(live, /secretName: 'ql3-e2e-registry-auth'/);
|
|
assert.match(live, /defaultMode: 288/);
|
|
assert.match(live, /authentication: 'exact-registry-basic'/);
|
|
assert.match(live, /fixture\.initial\.routes\.length \* 2/);
|
|
assert.match(live, /fixture\.upgrade\.routes\.length \* 2/);
|
|
assert.match(
|
|
live,
|
|
/packageRequests\.every\(\(event\) => event\.authenticated === true\)/,
|
|
);
|
|
});
|