mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
352 lines
11 KiB
JavaScript
352 lines
11 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { test } = require('node:test');
|
|
|
|
const {
|
|
ClusterPluginPackageExecutorProcessConfigError,
|
|
loadClusterPluginPackageExecutorProcessConfig,
|
|
runClusterPluginPackageExecutorProcess,
|
|
} = require('@qinglong/cluster-admin/plugin-package-executor-process');
|
|
|
|
function environment(overrides = {}) {
|
|
return {
|
|
QL3_PLUGIN_PACKAGE_EXECUTOR_ENABLED: 'true',
|
|
QL3_PLUGIN_PACKAGE_EXECUTOR_OWNER: 'cluster_package_executor_1',
|
|
QL3_PLUGIN_PACKAGE_EXECUTOR_APPROVAL_BATCH_SIZE: '4',
|
|
QL3_PLUGIN_PACKAGE_EXECUTOR_DISPATCH_BATCH_SIZE: '4',
|
|
QL3_PLUGIN_PACKAGE_EXECUTOR_MAX_BATCHES: '2',
|
|
QL3_PLUGIN_PACKAGE_EXECUTOR_LEASE_DURATION_MS: '600000',
|
|
QL3_PLUGIN_PACKAGE_EXECUTOR_REVOCATION_PAGE_SIZE: '8',
|
|
QL3_PLUGIN_PACKAGE_EXECUTOR_REVOCATION_MAX_PAGES: '4',
|
|
QL3_PLUGIN_PACKAGE_EXECUTOR_SECRET_ROOT:
|
|
'/var/run/secrets/qinglong3/plugin-package-values',
|
|
QL3_POSTGRES_PACKAGE_EXECUTOR_URL:
|
|
'postgresql://ql3_package_executor:secret@postgres/qinglong',
|
|
QL3_POSTGRES_TLS_MODE: 'disable',
|
|
QL3_POSTGRES_ALLOW_INSECURE: 'true',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function actionControllerEnvironment(overrides = {}) {
|
|
return environment({
|
|
QL3_PLUGIN_PACKAGE_EXECUTOR_SECRET_ROOT: undefined,
|
|
QL3_PLUGIN_PACKAGE_SECRET_ACTION_CONTROLLER_ENABLED: 'true',
|
|
QL3_PLUGIN_PACKAGE_SECRET_ACTION_CONTROLLER_LIMIT: '6',
|
|
QL3_PLUGIN_PACKAGE_SECRET_ACTION_IMAGE:
|
|
'registry.example.com/qinglong/qinglong3-cluster-admin@sha256:' +
|
|
'c'.repeat(64),
|
|
QL3_PLUGIN_PACKAGE_SECRET_ACTION_POSTGRES_CA_SECRET:
|
|
'ql3-cluster-plugin-package-executor',
|
|
QL3_PLUGIN_PACKAGE_SECRET_ACTION_POSTGRES_CA_KEY: 'postgres-ca.crt',
|
|
QL3_PLUGIN_PACKAGE_SECRET_ACTION_POSTGRES_SERVERNAME:
|
|
'postgres.qinglong3-system.svc',
|
|
QL3_PLUGIN_PACKAGE_SECRET_ACTION_POSTGRES_URL_SECRET:
|
|
'ql3-cluster-plugin-package-executor',
|
|
QL3_PLUGIN_PACKAGE_SECRET_ACTION_POSTGRES_URL_KEY:
|
|
'postgres-package-executor-url',
|
|
...overrides,
|
|
});
|
|
}
|
|
|
|
test('disabled executor opens no PostgreSQL authority', async () => {
|
|
let opened = 0;
|
|
const result = await runClusterPluginPackageExecutorProcess({
|
|
environment: { QL3_PLUGIN_PACKAGE_EXECUTOR_ENABLED: 'false' },
|
|
async openDatabase() {
|
|
opened += 1;
|
|
throw new Error('must not open');
|
|
},
|
|
});
|
|
assert.deepEqual(result, { status: 'disabled' });
|
|
assert.equal(opened, 0);
|
|
});
|
|
|
|
test('loads bounded low-footprint Package-executor configuration', () => {
|
|
const config = loadClusterPluginPackageExecutorProcessConfig(environment());
|
|
assert.equal(config.enabled, true);
|
|
assert.equal(config.owner, 'cluster_package_executor_1');
|
|
assert.equal(config.approvalBatchSize, 4);
|
|
assert.equal(config.dispatchBatchSize, 4);
|
|
assert.equal(config.maxBatches, 2);
|
|
assert.equal(config.revocationPageSize, 8);
|
|
assert.equal(config.revocationMaxPages, 4);
|
|
assert.equal(config.dispatchId, null);
|
|
assert.equal(
|
|
config.secretProjectionRoot,
|
|
'/var/run/secrets/qinglong3/plugin-package-values',
|
|
);
|
|
assert.equal(config.database.pool.maxConnections, 2);
|
|
assert.equal(config.database.connection.tls.mode, 'disable');
|
|
});
|
|
|
|
test('loads one bounded action-scoped dispatch without widening batch limits', () => {
|
|
const config = loadClusterPluginPackageExecutorProcessConfig(
|
|
environment({
|
|
QL3_PLUGIN_PACKAGE_EXECUTOR_DISPATCH_ID: 'dispatch.secret-binding.42',
|
|
QL3_PLUGIN_PACKAGE_EXECUTOR_APPROVAL_BATCH_SIZE: undefined,
|
|
QL3_PLUGIN_PACKAGE_EXECUTOR_DISPATCH_BATCH_SIZE: undefined,
|
|
QL3_PLUGIN_PACKAGE_EXECUTOR_MAX_BATCHES: undefined,
|
|
}),
|
|
);
|
|
assert.equal(config.enabled, true);
|
|
assert.equal(config.dispatchId, 'dispatch.secret-binding.42');
|
|
assert.equal(config.approvalBatchSize, 8);
|
|
assert.equal(config.dispatchBatchSize, 8);
|
|
assert.equal(config.maxBatches, 4);
|
|
});
|
|
|
|
test('loads a bounded digest-pinned Kubernetes Secret action controller', () => {
|
|
const config = loadClusterPluginPackageExecutorProcessConfig(
|
|
actionControllerEnvironment(),
|
|
);
|
|
assert.equal(config.enabled, true);
|
|
assert.equal(config.secretProjectionRoot, null);
|
|
assert.equal(config.kubernetesSecretActions.limit, 6);
|
|
assert.equal(
|
|
config.kubernetesSecretActions.job.serviceAccountName,
|
|
'ql3-plugin-package-secret-action',
|
|
);
|
|
assert.equal(
|
|
config.kubernetesSecretActions.job.postgres.connection.mode,
|
|
'url',
|
|
);
|
|
assert.equal(
|
|
config.kubernetesSecretActions.job.postgres.connection.secretName,
|
|
'ql3-cluster-plugin-package-executor',
|
|
);
|
|
});
|
|
|
|
test('action-scoped mode skips every Approval consumer and shared queue scan', async () => {
|
|
const calls = [];
|
|
const pool = {};
|
|
const readiness = {
|
|
ready: true,
|
|
writablePrimary: true,
|
|
serverVersionNum: 180004,
|
|
serverMajor: 18,
|
|
currentUser: 'ql3_package_executor',
|
|
contractName: 'control-core',
|
|
contractVersion: 62,
|
|
migrationIds: ['pg-0063-plugin-package-secret-binding-transition-receipts'],
|
|
};
|
|
const rejectConsumer = async () => {
|
|
throw new Error('action-scoped executor must not consume approvals');
|
|
};
|
|
const result = await runClusterPluginPackageExecutorProcess({
|
|
environment: environment({
|
|
QL3_PLUGIN_PACKAGE_EXECUTOR_DISPATCH_ID: 'dispatch.secret-binding.42',
|
|
}),
|
|
async openDatabase() {
|
|
calls.push('open');
|
|
return {
|
|
pool,
|
|
async close() {
|
|
calls.push('close');
|
|
},
|
|
};
|
|
},
|
|
async assertReady(candidate) {
|
|
assert.equal(candidate, pool);
|
|
calls.push('ready');
|
|
return readiness;
|
|
},
|
|
consumeApprovals: rejectConsumer,
|
|
consumeTrustTransitionApprovals: rejectConsumer,
|
|
consumeSecretBindingApprovals: rejectConsumer,
|
|
consumeSecretBindingTransitionApprovals: rejectConsumer,
|
|
createDispatcher(options) {
|
|
assert.equal(options.pool, pool);
|
|
assert.equal(typeof options.secretExistenceInspector.assertExists, 'function');
|
|
return {
|
|
async dispatchBatch() {
|
|
throw new Error('action-scoped executor must not scan the queue');
|
|
},
|
|
async dispatchById({ dispatchId }) {
|
|
calls.push(`dispatch:${dispatchId}`);
|
|
return {
|
|
scanned: 1,
|
|
claimed: 1,
|
|
started: 1,
|
|
succeeded: 1,
|
|
failed: 0,
|
|
blocked: 0,
|
|
retrying: 0,
|
|
deferred: 0,
|
|
recoveryRequired: 0,
|
|
alreadyTerminal: 0,
|
|
unavailable: 0,
|
|
truncated: false,
|
|
};
|
|
},
|
|
};
|
|
},
|
|
});
|
|
assert.deepEqual(calls, [
|
|
'open',
|
|
'ready',
|
|
'dispatch:dispatch.secret-binding.42',
|
|
'close',
|
|
]);
|
|
assert.equal(result.status, 'completed');
|
|
assert.equal(result.batches.length, 1);
|
|
assert.equal(result.batches[0].approvals.scanned, 0);
|
|
assert.equal(result.batches[0].dispatch.succeeded, 1);
|
|
});
|
|
|
|
test('batch mode consumes approvals before reconciling exact Secret action Jobs', async () => {
|
|
const calls = [];
|
|
const pool = {
|
|
async query() {
|
|
throw new Error('repositories are injected behind the controller factory');
|
|
},
|
|
async connect() {
|
|
throw new Error('repositories are injected behind the controller factory');
|
|
},
|
|
};
|
|
const approvalSummary = {
|
|
scanned: 0,
|
|
consumed: 0,
|
|
existing: 0,
|
|
expired: 0,
|
|
blocked: 0,
|
|
};
|
|
const readiness = {
|
|
ready: true,
|
|
writablePrimary: true,
|
|
serverVersionNum: 180004,
|
|
serverMajor: 18,
|
|
currentUser: 'ql3_package_executor',
|
|
contractName: 'control-core',
|
|
contractVersion: 62,
|
|
migrationIds: ['pg-0063-plugin-package-secret-binding-transition-receipts'],
|
|
};
|
|
const result = await runClusterPluginPackageExecutorProcess({
|
|
environment: actionControllerEnvironment(),
|
|
async openDatabase() {
|
|
calls.push('open');
|
|
return {
|
|
pool,
|
|
async close() {
|
|
calls.push('close');
|
|
},
|
|
};
|
|
},
|
|
async assertReady() {
|
|
calls.push('ready');
|
|
return readiness;
|
|
},
|
|
async consumeApprovals() {
|
|
calls.push('publisher-approvals');
|
|
return approvalSummary;
|
|
},
|
|
async consumeTrustTransitionApprovals() {
|
|
calls.push('trust-approvals');
|
|
return approvalSummary;
|
|
},
|
|
async consumeSecretBindingApprovals() {
|
|
calls.push('binding-approvals');
|
|
return { ...approvalSummary, scanned: 1, consumed: 1 };
|
|
},
|
|
async consumeSecretBindingTransitionApprovals() {
|
|
calls.push('transition-approvals');
|
|
return approvalSummary;
|
|
},
|
|
async createSecretActionController(options) {
|
|
assert.equal(options.job.image.endsWith('c'.repeat(64)), true);
|
|
calls.push('controller-open');
|
|
return {
|
|
controller: {
|
|
async reconcile({ limit }) {
|
|
calls.push(`reconcile:${limit}`);
|
|
return {
|
|
scanned: 1,
|
|
created: 1,
|
|
existing: 0,
|
|
active: 0,
|
|
recoveryRequired: 0,
|
|
unavailable: 0,
|
|
truncated: false,
|
|
};
|
|
},
|
|
},
|
|
dispose() {
|
|
calls.push('controller-close');
|
|
},
|
|
};
|
|
},
|
|
createDispatcher() {
|
|
return {
|
|
async dispatchById() {
|
|
throw new Error('batch mode must not exact dispatch');
|
|
},
|
|
async dispatchBatch() {
|
|
calls.push('dispatch-batch');
|
|
return {
|
|
scanned: 0,
|
|
claimed: 0,
|
|
started: 0,
|
|
succeeded: 0,
|
|
failed: 0,
|
|
blocked: 0,
|
|
retrying: 0,
|
|
deferred: 0,
|
|
recoveryRequired: 0,
|
|
alreadyTerminal: 0,
|
|
unavailable: 0,
|
|
truncated: false,
|
|
};
|
|
},
|
|
};
|
|
},
|
|
});
|
|
assert.equal(result.batches.length, 1);
|
|
assert.equal(result.batches[0].secretActionJobs.created, 1);
|
|
assert.deepEqual(calls, [
|
|
'open',
|
|
'ready',
|
|
'publisher-approvals',
|
|
'trust-approvals',
|
|
'binding-approvals',
|
|
'transition-approvals',
|
|
'controller-open',
|
|
'reconcile:6',
|
|
'dispatch-batch',
|
|
'controller-close',
|
|
'close',
|
|
]);
|
|
});
|
|
|
|
test('rejects implicit insecure PostgreSQL and unbounded work', () => {
|
|
for (const invalid of [
|
|
environment({ QL3_POSTGRES_ALLOW_INSECURE: undefined }),
|
|
environment({ QL3_PLUGIN_PACKAGE_EXECUTOR_MAX_BATCHES: '65' }),
|
|
environment({ QL3_PLUGIN_PACKAGE_EXECUTOR_REVOCATION_PAGE_SIZE: '129' }),
|
|
environment({ QL3_PLUGIN_PACKAGE_EXECUTOR_OWNER: 'not safe' }),
|
|
environment({ QL3_PLUGIN_PACKAGE_EXECUTOR_SECRET_ROOT: 'relative/path' }),
|
|
environment({
|
|
QL3_PLUGIN_PACKAGE_EXECUTOR_DISPATCH_ID: 'dispatch id with spaces',
|
|
}),
|
|
actionControllerEnvironment({
|
|
QL3_PLUGIN_PACKAGE_SECRET_ACTION_IMAGE: 'tag-only:latest',
|
|
}),
|
|
actionControllerEnvironment({
|
|
QL3_PLUGIN_PACKAGE_SECRET_ACTION_POSTGRES_URL_SECRET: undefined,
|
|
}),
|
|
]) {
|
|
assert.throws(
|
|
() => loadClusterPluginPackageExecutorProcessConfig(invalid),
|
|
ClusterPluginPackageExecutorProcessConfigError,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('keeps executor authority off the cluster-admin root', () => {
|
|
const root = require('@qinglong/cluster-admin');
|
|
const manifest = require('../package.json');
|
|
assert.equal(root.runClusterPluginPackageExecutorProcess, undefined);
|
|
assert.equal(
|
|
manifest.bin['ql3-plugin-package-execute'],
|
|
'dist/plugin-package/executor/pluginPackageExecutorCli.js',
|
|
);
|
|
});
|