file support
parent
6e6c05519d
commit
931dedb79b
@ -0,0 +1,89 @@
|
|||||||
|
import * as pty from 'node-pty'
|
||||||
|
import { getContainerPath, runCommand } from './utils.js'
|
||||||
|
|
||||||
|
import fs from 'fs/promises'
|
||||||
|
|
||||||
|
import { randomUUID } from 'crypto'
|
||||||
|
|
||||||
|
const stat = path =>
|
||||||
|
new Promise(resolve =>
|
||||||
|
fs
|
||||||
|
.access(path)
|
||||||
|
.then(() => resolve(true))
|
||||||
|
.catch(() => resolve(false))
|
||||||
|
)
|
||||||
|
|
||||||
|
const containerIds = new Set()
|
||||||
|
|
||||||
|
await cleanContainers()
|
||||||
|
|
||||||
|
async function storeContainers() {
|
||||||
|
await fs.writeFile('containers-db.local.json', JSON.stringify([...containerIds]))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cleanContainers() {
|
||||||
|
if (!(await stat('containers-db.local.json'))) {
|
||||||
|
storeContainers()
|
||||||
|
}
|
||||||
|
|
||||||
|
const raw = JSON.parse(await fs.readFile('containers-db.local.json', 'utf8'))
|
||||||
|
await Promise.all(raw.map(id => destroyContainer(id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createContainer(tag) {
|
||||||
|
const id = randomUUID()
|
||||||
|
|
||||||
|
await fs.mkdir(getContainerPath('phc', id), { recursive: true })
|
||||||
|
|
||||||
|
await runCommand('docker', [
|
||||||
|
'run',
|
||||||
|
'-d',
|
||||||
|
'--name',
|
||||||
|
id,
|
||||||
|
'--volume',
|
||||||
|
`${getContainerPath('phc', id)}:/project`,
|
||||||
|
tag,
|
||||||
|
'sleep',
|
||||||
|
'infinity',
|
||||||
|
])
|
||||||
|
|
||||||
|
containerIds.add(id)
|
||||||
|
await storeContainers()
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createContainerPty(
|
||||||
|
tag,
|
||||||
|
options = {
|
||||||
|
onExit,
|
||||||
|
onData,
|
||||||
|
|
||||||
|
shellCommand: ['/bin/sh'],
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const shellCommand = options.shellCommand ?? ['/bin/sh']
|
||||||
|
const { onData, onExit } = options
|
||||||
|
|
||||||
|
const id = await createContainer(tag)
|
||||||
|
|
||||||
|
const container = pty.spawn('docker', ['exec', '-it', id, ...shellCommand])
|
||||||
|
|
||||||
|
container.onExit(async e => {
|
||||||
|
onExit?.(e)
|
||||||
|
await destroyContainer(id)
|
||||||
|
})
|
||||||
|
|
||||||
|
container.onData(data => {
|
||||||
|
onData?.(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
return { id, pty: container }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function destroyContainer(id) {
|
||||||
|
await runCommand('docker', ['rm', '-f', id])
|
||||||
|
|
||||||
|
containerIds.delete(id)
|
||||||
|
await storeContainers()
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
import { spawn } from 'child_process'
|
||||||
|
|
||||||
|
export const CONTAINERS_ROOT = '/tmp/phc-run/containers'
|
||||||
|
|
||||||
|
export const getContainerPath = (ns, uuid) => `${CONTAINERS_ROOT}/${ns}/${uuid}`
|
||||||
|
|
||||||
|
export function runCommand(command, args, options = { stdin: null, capture: ['stdout', 'stderr'] }) {
|
||||||
|
console.log(`Running: ${command} ${JSON.stringify(args)}`)
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const child = spawn(command, args)
|
||||||
|
|
||||||
|
let output = ''
|
||||||
|
|
||||||
|
if (options.capture.includes('stdout')) {
|
||||||
|
child.stdout.on('data', data => {
|
||||||
|
output += data.toString()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.capture.includes('stderr')) {
|
||||||
|
child.stderr.on('data', data => {
|
||||||
|
output += data.toString()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.stdin) {
|
||||||
|
child.stdin.write(options.stdin)
|
||||||
|
}
|
||||||
|
|
||||||
|
child.on('close', code => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve(output)
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Command failed with code ${code}: ${output}`))
|
||||||
|
console.error('Command error:', output)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue