@mieweb/wikigdrive
Version:
Google Drive to MarkDown synchronization
189 lines (188 loc) • 6.8 kB
JavaScript
import path from 'node:path';
import { PassThrough } from 'node:stream';
// import {Buffer} from 'https://deno.land/std/io/buffer.ts';
import Docker from 'dockerode';
import tarFs from 'tar-fs';
import tarStream from 'tar-stream';
import { BufferWritable } from '../../utils/BufferWritable.js';
import process from 'node:process';
export class PodmanContainer {
constructor(logger, id, image, container) {
Object.defineProperty(this, "logger", {
enumerable: true,
configurable: true,
writable: true,
value: logger
});
Object.defineProperty(this, "id", {
enumerable: true,
configurable: true,
writable: true,
value: id
});
Object.defineProperty(this, "image", {
enumerable: true,
configurable: true,
writable: true,
value: image
});
Object.defineProperty(this, "container", {
enumerable: true,
configurable: true,
writable: true,
value: container
});
}
static async create(logger, image, env, repoSubDir) {
const podmanEngine = new Docker({ protocol: 'http', host: 'localhost', port: 5001 });
const container = await podmanEngine.createContainer({
Image: image,
AttachStdin: false,
AttachStdout: true,
AttachStderr: true,
Tty: true,
OpenStdin: false,
StdinOnce: false,
HostConfig: {
AutoRemove: true,
Binds: [
`${process.env.VOLUME_DATA}/${env.DRIVE_ID}/action-cache:/action-cache:rw`,
`${process.env.VOLUME_PREVIEW}/${env.DRIVE_ID}:/output-preview:rw`,
`${process.env.VOLUME_DATA}/${repoSubDir}:/site:O`,
],
},
Env: Object.keys(env).map(key => `${key}=${env[key]}`),
User: String(process.getuid()) + ':' + String(process.getegid())
});
return new PodmanContainer(logger, container.id, image, container);
}
async start() {
await this.container.start();
this.logger.info('podman started: ' + this.id);
}
async stop() {
return this.container.stop();
}
async remove() {
}
async copy(realPath, remotePath, ignoreGit = false) {
this.logger.info('podman copy into ' + remotePath);
const archive = tarFs.pack(realPath, {
ignore(name) {
if (ignoreGit && name.startsWith(path.join(realPath, '.git'))) {
return true;
}
if (name.startsWith(path.join(realPath, '.private'))) {
return true;
}
return false;
},
});
await this.container.putArchive(archive, {
path: remotePath
});
}
async putFile(content, remotePath) {
const archive = tarStream.pack();
archive.entry({ name: remotePath }, content);
archive.finalize();
const writable = new BufferWritable();
archive.pipe(writable);
this.logger.info('podman write into ' + remotePath);
await this.container.putArchive(writable.getBuffer(), {
path: '/'
});
}
async export(remotePath, outputDir) {
this.logger.info('podman export /site/public');
const archive = await this.container.getArchive({
path: remotePath
});
await new Promise((resolve, reject) => {
try {
const stream = archive.pipe(tarFs.extract(outputDir, {
map(header) {
const parts = header.name.split('/');
parts.shift();
header.name = parts.join('/');
return header;
}
}));
stream.on('finish', () => {
resolve();
});
stream.on('error', (err) => {
reject(err);
});
}
catch (err) {
reject(err);
}
});
}
async getFile(remotePath) {
const archive = await this.container.getArchive({
path: remotePath
});
return await new Promise((resolve, reject) => {
const retVal = [];
const extract = tarStream.extract();
extract.on('entry', (header, stream, next) => {
stream.on('data', (data) => {
retVal.push(new Uint8Array(data.buffer, data.byteOffset, data.length));
});
stream.on('end', () => {
next();
});
stream.resume();
});
try {
const stream = archive.pipe(extract);
stream.on('finish', () => {
const totalLength = retVal.reduce((acc, arr) => acc + arr.length, 0);
const combinedArray = new Uint8Array(totalLength);
let offset = 0;
retVal.forEach(arr => {
combinedArray.set(arr, offset); // Copy each array into the combined one
offset += arr.length; // Update the offset for the next array
});
resolve(combinedArray);
});
stream.on('error', (err) => {
reject(err);
});
}
catch (err) {
reject(err);
}
});
}
async exec(command, env, writable) {
this.logger.info(`podman exec ${this.id} ${command}`);
const cancelTimeout = new AbortController();
const exec = await this.container.exec({
Cmd: command.split(' '),
AttachStdin: false,
AttachStdout: true,
AttachStderr: true,
Env: Object.keys(env).map(key => `${key}=${env[key]}`),
//WorkingDir
abortSignal: cancelTimeout.signal,
});
const stream = await exec.start({ hijack: true, Detach: false });
const stdout = new PassThrough();
const stderr = new PassThrough();
this.container.modem.demuxStream(stream, stdout, stderr);
stdout.on('data', (chunk) => {
writable.write(chunk);
});
stderr.on('data', (chunk) => {
writable.write(chunk);
});
await new Promise(resolve => stream.on('end', () => {
resolve(0);
}));
const inspectInfo = await exec.inspect();
return inspectInfo.ExitCode;
}
}