mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 19:29:13 +08:00
feat(ql3): bind offline adopted target images
This commit is contained in:
@@ -228,6 +228,16 @@ function fixture(t, kind) {
|
||||
releaseSelection: releaseSelection(managementRoot),
|
||||
allowRootService: rootAcknowledgement(),
|
||||
}
|
||||
: kind === 'docker-target'
|
||||
? {
|
||||
kind,
|
||||
targetImage: {
|
||||
authority: 'local-image-id',
|
||||
reference: 'qinglong3-local-application:ci-amd64',
|
||||
imageId: `sha256:${'d'.repeat(64)}`,
|
||||
},
|
||||
allowRootService: rootAcknowledgement(),
|
||||
}
|
||||
: {
|
||||
kind,
|
||||
nodeExecutable: fs.realpathSync(process.execPath),
|
||||
@@ -636,7 +646,7 @@ async function createFailedRestoreState(state, currentGeneration, suffix) {
|
||||
return failedCommand;
|
||||
}
|
||||
|
||||
for (const kind of ['systemd', 'openrc', 'compose']) {
|
||||
for (const kind of ['systemd', 'openrc', 'compose', 'docker-target']) {
|
||||
test(`prepares and verifies an exact adopted ${kind} bundle without activation`, (t) => {
|
||||
const state = fixture(t, kind);
|
||||
const prepared = prepareLocalDeploymentAdoptedBundle(state.command);
|
||||
@@ -719,6 +729,43 @@ for (const kind of ['systemd', 'openrc', 'compose']) {
|
||||
'utf8',
|
||||
),
|
||||
);
|
||||
} else if (kind === 'docker-target') {
|
||||
const descriptor = JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.join(state.root, 'service/docker-target.json'),
|
||||
'utf8',
|
||||
),
|
||||
);
|
||||
assert.equal(
|
||||
descriptor.schema,
|
||||
'qinglong/local-adopted-docker-target@v1',
|
||||
);
|
||||
assert.deepEqual(
|
||||
descriptor.image,
|
||||
state.command.options.service.targetImage,
|
||||
);
|
||||
assert.equal(descriptor.container.restartPolicy, 'no');
|
||||
assert.equal(descriptor.container.readOnlyRootFilesystem, true);
|
||||
assert.deepEqual(descriptor.container.dropCapabilities, ['ALL']);
|
||||
assert.deepEqual(descriptor.container.securityOptions, [
|
||||
'no-new-privileges',
|
||||
]);
|
||||
assert.deepEqual(descriptor.container.mounts, [
|
||||
{
|
||||
source: state.root,
|
||||
destination: state.root,
|
||||
readOnly: false,
|
||||
},
|
||||
{
|
||||
source: state.sourcePath,
|
||||
destination: state.sourcePath,
|
||||
readOnly: true,
|
||||
},
|
||||
]);
|
||||
assert.equal(
|
||||
fs.existsSync(path.join(state.root, 'service/compose.image.yaml')),
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
assert.equal(
|
||||
fs.existsSync(path.join(state.root, 'service/compose.image.yaml')),
|
||||
|
||||
@@ -88,6 +88,7 @@ function targetInspection(state) {
|
||||
return JSON.stringify([
|
||||
{
|
||||
Id: state.targetContainerId,
|
||||
Image: state.targetImageId,
|
||||
Created: '2026-08-09T01:00:00.000000000Z',
|
||||
Name: '/qinglong3-target',
|
||||
State: {
|
||||
@@ -193,6 +194,8 @@ function fixture(t) {
|
||||
legacyContainerId: '7'.repeat(64),
|
||||
targetContainerId: '8'.repeat(64),
|
||||
targetImage: `registry.example/qinglong3@sha256:${'9'.repeat(64)}`,
|
||||
targetImageAuthority: 'registry-digest',
|
||||
targetImageId: `sha256:${'a'.repeat(64)}`,
|
||||
targetRunning: false,
|
||||
legacyRunning: false,
|
||||
nextProcessId: 10,
|
||||
@@ -290,7 +293,11 @@ function command(state, generation = 1) {
|
||||
expectedLegacyCommitmentDigest: state.legacyCommitmentDigest,
|
||||
expectedLegacyContainerId: state.legacyContainerId,
|
||||
expectedTargetContainerId: state.targetContainerId,
|
||||
expectedTargetImage: state.targetImage,
|
||||
targetImage: {
|
||||
authority: state.targetImageAuthority,
|
||||
reference: state.targetImage,
|
||||
imageId: state.targetImageId,
|
||||
},
|
||||
applicationConfigPath: state.applicationConfigPath,
|
||||
expectedTargetApplicationConfigPath:
|
||||
state.targetApplicationConfigPath,
|
||||
@@ -497,6 +504,47 @@ test('starts an exact target once and replays the active commitment without Dock
|
||||
assert.equal(replay.recordDigest, active.recordDigest);
|
||||
});
|
||||
|
||||
test('starts an offline Trial Kit image only when its local reference and content ID both match', async (t) => {
|
||||
const state = fixture(t);
|
||||
state.targetImageAuthority = 'local-image-id';
|
||||
state.targetImage = 'qinglong3-local-application:ci-amd64';
|
||||
const active = await runLocalDeploymentDockerTarget(
|
||||
command(state),
|
||||
harness(state),
|
||||
);
|
||||
assert.equal(active.state, 'target_active');
|
||||
});
|
||||
|
||||
test('makes an offline Trial Kit target manual-required when its inspected content ID drifted', async (t) => {
|
||||
const state = fixture(t);
|
||||
state.targetImageAuthority = 'local-image-id';
|
||||
state.targetImage = 'qinglong3-local-application:ci-amd64';
|
||||
const request = command(state);
|
||||
state.targetImageId = `sha256:${'b'.repeat(64)}`;
|
||||
const controller = harness(state);
|
||||
const result = await runLocalDeploymentDockerTarget(request, controller);
|
||||
assert.equal(result.state, 'manual_required');
|
||||
assert.equal(
|
||||
controller.calls.filter((args) => args[1] === 'start').length,
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects a mutable local tag under registry digest authority before Docker access', async (t) => {
|
||||
const state = fixture(t);
|
||||
const request = command(state);
|
||||
request.request.targetImage.reference =
|
||||
'qinglong3-local-application:ci-amd64';
|
||||
await assert.rejects(
|
||||
runLocalDeploymentDockerTarget(request, {
|
||||
validateSocket() {
|
||||
throw new Error('invalid image authority must not reach Docker');
|
||||
},
|
||||
}),
|
||||
/targetImage identity is invalid/,
|
||||
);
|
||||
});
|
||||
|
||||
test('recovers a crash after the start barrier by inspection without repeating start', async (t) => {
|
||||
const state = fixture(t);
|
||||
const crashing = harness(state, {
|
||||
|
||||
Reference in New Issue
Block a user