Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions lib/docker/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,19 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { Extract, Pack } from 'tar-stream'
import type { Pack } from 'tar-stream'

/**
* Present a `tar-stream` stream as the Node.js stream its consumers expect.
*
* `tar-stream` is typed as the `streamx` streams it is built on. Those behave
* like Node.js streams at runtime, but are not structurally assignable to them, so both
* `dockerode` and `stream.pipeline` need the stream to be cast.
* like Node.js streams at runtime, but are not structurally assignable to them,
* so `dockerode` needs the stream to be cast.
*
* @param stream The pack or extract stream to cast
* @param stream The pack stream to cast
*/
export function asNodeStream(stream: Pack): NodeJS.ReadableStream
export function asNodeStream(stream: Extract): NodeJS.WritableStream
/**
* @param stream The pack or extract stream to cast
*/
export function asNodeStream(stream: Pack | Extract) {
return stream as unknown as NodeJS.ReadableStream & NodeJS.WritableStream
export function asNodeStream(stream: Pack): NodeJS.ReadableStream {
return stream as unknown as NodeJS.ReadableStream
}

/**
Expand Down
28 changes: 11 additions & 17 deletions lib/docker/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ import type { Container } from 'dockerode'

import { mkdirSync, writeFileSync } from 'fs'
import { dirname, resolve } from 'path'
import { pipeline } from 'stream/promises'
import tarStreamer from 'tar-stream'
import { getContainer } from './client.ts'
import { asNodeStream } from './internal.ts'
import { pathExists, runExec } from './exec.ts'

/** Path of the server log inside the container, it lives on the tmpfs mounted data directory */
const NEXTCLOUD_LOG = '/var/www/html/data/nextcloud.log'
Expand All @@ -20,31 +18,27 @@ const NEXTCLOUD_LOG = '/var/www/html/data/nextcloud.log'
*
* The data directory is a tmpfs and the container is removed after the run,
* so the log has to be fetched while the container still exists.
* It also has to be read by a command run inside the container:
* the Docker copy API (`docker cp` / `getArchive`) only sees the container's own
* filesystem and answers `404` for any path below a tmpfs mount.
*
* @param container Optional server container to use (defaults to current container)
* @return The log contents, or an empty string if the server has not written a log
*/
export async function getNextcloudLog(container?: Container): Promise<string> {
container = container ?? getContainer()

let archive: NodeJS.ReadableStream
try {
archive = await container.getArchive({ path: NEXTCLOUD_LOG })
if (!await pathExists(NEXTCLOUD_LOG, container)) {
// No log written (yet)
return ''
}
const { stdout } = await runExec(['cat', NEXTCLOUD_LOG], { container })
return stdout
} catch {
// No log written (yet), or the container is already gone
// The container is already gone
return ''
}

// `getArchive` always answers with a tar stream, containing the single log entry
const extract = tarStreamer.extract()
const chunks: Buffer[] = []
extract.on('entry', (_header, stream, next) => {
stream.on('data', (chunk) => chunks.push(chunk as Buffer))
stream.on('end', () => next())
})
await pipeline(archive, asNodeStream(extract))

return Buffer.concat(chunks).toString('utf8')
}

/**
Expand Down
Loading