mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-20 22:25:30 +00:00
fix(autofix): pin resolver binary, make image check digest-aware (#9527)
This commit is contained in:
parent
9c5e7282d3
commit
495a13c8e2
4 changed files with 76 additions and 1 deletions
11
.github/scripts/resolve-sandbox-image.test.mjs
vendored
11
.github/scripts/resolve-sandbox-image.test.mjs
vendored
|
|
@ -678,6 +678,17 @@ test('every sandbox-image consumer binds the resolver step output', () => {
|
|||
resolver.id,
|
||||
`${name} job '${jobName}': the resolver step needs an id so its image output is addressable`,
|
||||
);
|
||||
// The export is derived from this binary's stdout: an unpinned
|
||||
// SANDBOX_COMMAND (or the bare `docker` default) is steerable
|
||||
// through the same $GITHUB_ENV-append channel the DOCKER_HOST pin
|
||||
// closes, and a PATH shadow in the runner-writable qwen-bin dir
|
||||
// defeats a bare-name pin — so step env must bind an absolute
|
||||
// path (#9527 review).
|
||||
assert.equal(
|
||||
resolver.env?.SANDBOX_COMMAND,
|
||||
'/usr/bin/docker',
|
||||
`${name} job '${jobName}': pin SANDBOX_COMMAND to an absolute docker path so neither an appended $GITHUB_ENV value nor a $GITHUB_PATH shadow can steer the exported digest`,
|
||||
);
|
||||
}
|
||||
const bindings = resolvers.map(
|
||||
(resolver) => `\${{ steps.${resolver.id}.outputs.image }}`,
|
||||
|
|
|
|||
14
.github/workflows/qwen-autofix.yml
vendored
14
.github/workflows/qwen-autofix.yml
vendored
|
|
@ -1099,6 +1099,13 @@ jobs:
|
|||
id: 'sandbox_image'
|
||||
if: |-
|
||||
${{ steps.scan.outputs.has_candidates == 'true' }}
|
||||
env:
|
||||
# The export is derived from this binary's stdout, so step env
|
||||
# pins it the way the DOCKER_HOST pins do: step env outranks an
|
||||
# appended $GITHUB_ENV value, and the absolute path cannot be
|
||||
# shadowed by a '${RUNNER_TEMP}/qwen-bin' or $GITHUB_PATH plant
|
||||
# (#9527 review).
|
||||
SANDBOX_COMMAND: '/usr/bin/docker'
|
||||
run: |-
|
||||
node .github/scripts/resolve-sandbox-image.mjs \
|
||||
"$(node -p "require('./package.json').config.sandboxImageUri")"
|
||||
|
|
@ -3926,6 +3933,13 @@ jobs:
|
|||
|
||||
- name: 'Resolve sandbox image'
|
||||
id: 'sandbox_image'
|
||||
env:
|
||||
# The export is derived from this binary's stdout, so step env
|
||||
# pins it the way the DOCKER_HOST pins do: step env outranks an
|
||||
# appended $GITHUB_ENV value, and the absolute path cannot be
|
||||
# shadowed by a '${RUNNER_TEMP}/qwen-bin' or $GITHUB_PATH plant
|
||||
# (#9527 review).
|
||||
SANDBOX_COMMAND: '/usr/bin/docker'
|
||||
run: |-
|
||||
node .github/scripts/resolve-sandbox-image.mjs \
|
||||
"$(node -p "require('./package.json').config.sandboxImageUri")"
|
||||
|
|
|
|||
|
|
@ -97,6 +97,51 @@ describe('start_sandbox', () => {
|
|||
child.emit('close', 0);
|
||||
await expect(result).resolves.toBe(0);
|
||||
});
|
||||
|
||||
it('checks image presence with an offline inspect that sees digest references', async () => {
|
||||
vi.stubEnv('SANDBOX_SET_UID_GID', 'false');
|
||||
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
vi.spyOn(fs, 'realpathSync').mockImplementation((filePath) =>
|
||||
String(filePath),
|
||||
);
|
||||
execSyncMock.mockReturnValue(Buffer.from(''));
|
||||
|
||||
const digestImage =
|
||||
'ghcr.io/qwenlm/qwen-code@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef';
|
||||
|
||||
const imageCheck = Object.assign(new EventEmitter(), {
|
||||
stdout: new EventEmitter(),
|
||||
});
|
||||
const child = new EventEmitter();
|
||||
spawnMock
|
||||
.mockImplementationOnce(() => {
|
||||
queueMicrotask(() => {
|
||||
imageCheck.stdout.emit('data', Buffer.from('sha256:local'));
|
||||
imageCheck.emit('close', 0);
|
||||
});
|
||||
return imageCheck;
|
||||
})
|
||||
.mockReturnValueOnce(child);
|
||||
|
||||
const result = start_sandbox({ command: 'docker', image: digestImage }, []);
|
||||
|
||||
await vi.waitFor(() => expect(spawnMock).toHaveBeenCalledTimes(2));
|
||||
// Digest references never appear in a `docker images -q <image>` listing
|
||||
// even when the content is local, which forced a needless network pull
|
||||
// (and a FatalSandboxError whenever the registry was unreachable) at
|
||||
// every consumer startup; the presence check must be the offline
|
||||
// `image inspect` (#9527 review).
|
||||
expect(spawnMock.mock.calls[0]).toEqual([
|
||||
'docker',
|
||||
['image', 'inspect', '--format', '{{.Id}}', digestImage],
|
||||
]);
|
||||
// Content found locally: no pull is attempted — the next spawn is the
|
||||
// sandbox run itself.
|
||||
expect((spawnMock.mock.calls[1]?.[1] as string[])[0]).toBe('run');
|
||||
|
||||
child.emit('close', 0);
|
||||
await expect(result).resolves.toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveSeatbeltProfileFile', () => {
|
||||
|
|
|
|||
|
|
@ -947,7 +947,12 @@ export async function start_sandbox(
|
|||
// Helper functions to ensure sandbox image is present
|
||||
async function imageExists(sandbox: string, image: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const args = ['images', '-q', image];
|
||||
// `images -q` lists repository:tag entries only, so a digest reference
|
||||
// (`repo@sha256:…`) lists empty even when its content is local — forcing
|
||||
// a needless registry round-trip for content already present. `image
|
||||
// inspect` resolves digest references against local content offline
|
||||
// (#9527).
|
||||
const args = ['image', 'inspect', '--format', '{{.Id}}', image];
|
||||
const checkProcess = spawn(sandbox, args);
|
||||
|
||||
let stdoutData = '';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue