feat(ql3): bind offline adopted target images

This commit is contained in:
whyour
2026-08-30 19:50:24 +08:00
parent 38d85952e7
commit 0f4b6b43fb
15 changed files with 345 additions and 23 deletions
@@ -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, {