mirror of
https://github.com/caprover/caprover
synced 2026-08-23 07:56:28 +00:00
158 lines
4.6 KiB
TypeScript
158 lines
4.6 KiB
TypeScript
import {
|
|
DockerBuildOutputDecoder,
|
|
DockerJsonStreamParser,
|
|
} from '../src/docker/DockerBuildOutput'
|
|
|
|
function varint(value: number): Buffer {
|
|
const bytes: number[] = []
|
|
do {
|
|
const remaining = Math.floor(value / 128)
|
|
bytes.push(value % 128 | (remaining ? 0x80 : 0))
|
|
value = remaining
|
|
} while (value)
|
|
return Buffer.from(bytes)
|
|
}
|
|
|
|
function bytesField(fieldNumber: number, value: string | Buffer): Buffer {
|
|
const data = Buffer.isBuffer(value) ? value : Buffer.from(value)
|
|
return Buffer.concat([
|
|
varint(fieldNumber * 8 + 2),
|
|
varint(data.length),
|
|
data,
|
|
])
|
|
}
|
|
|
|
test('buffers split JSON messages and parses multiple messages', () => {
|
|
const parser = new DockerJsonStreamParser()
|
|
|
|
expect(parser.push('{"stream":"hel')).toEqual([])
|
|
expect(parser.push('lo"}\n{"error":"failed"}\n')).toEqual([
|
|
{ stream: 'hello' },
|
|
{ error: 'failed' },
|
|
])
|
|
})
|
|
|
|
test('keeps buffers isolated and flushes the final message', () => {
|
|
const first = new DockerJsonStreamParser()
|
|
const second = new DockerJsonStreamParser()
|
|
|
|
first.push('{"stream":"first"')
|
|
second.push('{"stream":"second"}')
|
|
|
|
expect(first.push('}\n')).toEqual([{ stream: 'first' }])
|
|
expect(second.flush()).toEqual([{ stream: 'second' }])
|
|
})
|
|
|
|
test('reports malformed JSON without interrupting later messages', () => {
|
|
const parser = new DockerJsonStreamParser()
|
|
|
|
expect(parser.push('not-json\n{"stream":"still running"}\n')).toEqual([
|
|
{ stream: 'Cannot parse not-json' },
|
|
{ stream: 'still running' },
|
|
])
|
|
})
|
|
|
|
test('preserves traditional builder output', () => {
|
|
const decoder = new DockerBuildOutputDecoder()
|
|
const message = { stream: 'Step 1/1\n' }
|
|
|
|
expect(decoder.decode(message)).toEqual([message])
|
|
})
|
|
|
|
test('decodes BuildKit vertices, logs, and warnings', () => {
|
|
const vertex = Buffer.concat([
|
|
bytesField(1, 'sha256:vertex'),
|
|
bytesField(3, '[1/1] RUN npm test'),
|
|
])
|
|
const log = Buffer.concat([
|
|
bytesField(1, 'sha256:vertex'),
|
|
bytesField(4, 'tests passed\n'),
|
|
])
|
|
const warning = bytesField(3, 'deprecated instruction')
|
|
const status = Buffer.concat([
|
|
bytesField(1, vertex),
|
|
bytesField(3, log),
|
|
bytesField(4, warning),
|
|
])
|
|
const decoder = new DockerBuildOutputDecoder()
|
|
|
|
expect(
|
|
decoder.decode({
|
|
id: 'moby.buildkit.trace',
|
|
aux: status.toString('base64'),
|
|
})
|
|
).toEqual([
|
|
{ stream: '[1/1] RUN npm test\n' },
|
|
{ stream: 'tests passed\n' },
|
|
{ stream: 'WARNING: deprecated instruction\n' },
|
|
])
|
|
|
|
expect(
|
|
decoder.decode({
|
|
id: 'moby.buildkit.trace',
|
|
aux: bytesField(1, vertex).toString('base64'),
|
|
})
|
|
).toEqual([])
|
|
})
|
|
|
|
test('decodes BuildKit vertex errors once with error details', () => {
|
|
const vertex = Buffer.concat([
|
|
bytesField(1, 'sha256:failed-vertex'),
|
|
bytesField(3, '[1/1] RUN exit 1'),
|
|
bytesField(7, 'process exited with code 1'),
|
|
])
|
|
const message = {
|
|
id: 'moby.buildkit.trace',
|
|
aux: bytesField(1, vertex).toString('base64'),
|
|
}
|
|
const decoder = new DockerBuildOutputDecoder()
|
|
|
|
expect(decoder.decode(message)).toEqual([
|
|
{ stream: '[1/1] RUN exit 1\n' },
|
|
{
|
|
error: 'process exited with code 1',
|
|
errorDetail: { message: 'process exited with code 1' },
|
|
},
|
|
])
|
|
expect(decoder.decode(message)).toEqual([])
|
|
})
|
|
|
|
test('reports malformed BuildKit protobuf output', () => {
|
|
const decoder = new DockerBuildOutputDecoder()
|
|
|
|
expect(
|
|
decoder.decode({
|
|
id: 'moby.buildkit.trace',
|
|
aux: Buffer.from([0x0a, 0x05, 0x01]).toString('base64'),
|
|
})
|
|
).toEqual([{ stream: 'Cannot parse BuildKit build output\n' }])
|
|
})
|
|
|
|
test('ignores unrelated aux messages', () => {
|
|
const decoder = new DockerBuildOutputDecoder()
|
|
|
|
expect(
|
|
decoder.decode({ id: 'other.message', aux: 'not-protobuf' })
|
|
).toEqual([])
|
|
})
|
|
|
|
test('skips 10-byte protobuf varints without dropping later output', () => {
|
|
const fullWidthVarint = Buffer.from([
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01,
|
|
])
|
|
const log = Buffer.concat([
|
|
Buffer.from([0x08]),
|
|
fullWidthVarint,
|
|
bytesField(4, 'still visible\n'),
|
|
])
|
|
const status = bytesField(3, log)
|
|
const decoder = new DockerBuildOutputDecoder()
|
|
|
|
expect(
|
|
decoder.decode({
|
|
id: 'moby.buildkit.trace',
|
|
aux: status.toString('base64'),
|
|
})
|
|
).toEqual([{ stream: 'still visible\n' }])
|
|
})
|