182 lines
7.1 KiB
TypeScript
182 lines
7.1 KiB
TypeScript
import * as plugins from '../../plugins.ts';
|
|
import { logger } from '../../logging.ts';
|
|
import type { OpsServer } from '../classes.opsserver.ts';
|
|
import * as interfaces from '../../../ts_interfaces/index.ts';
|
|
import { requireValidIdentity } from '../helpers/guards.ts';
|
|
import { getErrorMessage } from '../../utils/error.ts';
|
|
|
|
export class WorkspaceHandler {
|
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
|
|
constructor(private opsServerRef: OpsServer) {
|
|
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
this.registerHandlers();
|
|
}
|
|
|
|
/**
|
|
* Resolve a service name to a container ID (handling Swarm service IDs)
|
|
*/
|
|
private async resolveContainerId(serviceName: string): Promise<string> {
|
|
const service = this.opsServerRef.oneboxRef.services.getService(serviceName);
|
|
if (!service || !service.containerID) {
|
|
throw new plugins.typedrequest.TypedResponseError(`Service not found or has no container: ${serviceName}`);
|
|
}
|
|
return service.containerID;
|
|
}
|
|
|
|
private registerHandlers(): void {
|
|
// Read file from container
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceReadFile>(
|
|
'workspaceReadFile',
|
|
async (dataArg) => {
|
|
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
|
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
|
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
|
containerId,
|
|
['cat', dataArg.path],
|
|
);
|
|
if (result.exitCode !== 0) {
|
|
throw new plugins.typedrequest.TypedResponseError(`Failed to read file: ${result.stderr || 'File not found'}`);
|
|
}
|
|
return { content: result.stdout };
|
|
},
|
|
),
|
|
);
|
|
|
|
// Write file to container
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceWriteFile>(
|
|
'workspaceWriteFile',
|
|
async (dataArg) => {
|
|
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
|
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
|
// Use sh -c with printf to write content (handles special characters)
|
|
const escaped = dataArg.content.replace(/'/g, "'\\''");
|
|
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
|
containerId,
|
|
['sh', '-c', `printf '%s' '${escaped}' > ${dataArg.path}`],
|
|
);
|
|
if (result.exitCode !== 0) {
|
|
throw new plugins.typedrequest.TypedResponseError(`Failed to write file: ${result.stderr}`);
|
|
}
|
|
return {};
|
|
},
|
|
),
|
|
);
|
|
|
|
// Read directory from container
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceReadDir>(
|
|
'workspaceReadDir',
|
|
async (dataArg) => {
|
|
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
|
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
|
// Use ls with -1 -F to get entries with type indicators (/ for dirs)
|
|
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
|
containerId,
|
|
['ls', '-1', '-F', dataArg.path],
|
|
);
|
|
if (result.exitCode !== 0) {
|
|
throw new plugins.typedrequest.TypedResponseError(`Failed to read directory: ${result.stderr}`);
|
|
}
|
|
const entries = result.stdout
|
|
.split('\n')
|
|
.filter((line) => line.trim())
|
|
.map((line) => {
|
|
const isDir = line.endsWith('/');
|
|
const name = isDir ? line.slice(0, -1) : line.replace(/[*@=|]$/, '');
|
|
const basePath = dataArg.path.endsWith('/') ? dataArg.path : dataArg.path + '/';
|
|
return {
|
|
type: (isDir ? 'directory' : 'file') as 'file' | 'directory',
|
|
name,
|
|
path: basePath + name,
|
|
};
|
|
});
|
|
return { entries };
|
|
},
|
|
),
|
|
);
|
|
|
|
// Create directory in container
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceMkdir>(
|
|
'workspaceMkdir',
|
|
async (dataArg) => {
|
|
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
|
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
|
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
|
containerId,
|
|
['mkdir', '-p', dataArg.path],
|
|
);
|
|
if (result.exitCode !== 0) {
|
|
throw new plugins.typedrequest.TypedResponseError(`Failed to create directory: ${result.stderr}`);
|
|
}
|
|
return {};
|
|
},
|
|
),
|
|
);
|
|
|
|
// Remove file/directory from container
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceRm>(
|
|
'workspaceRm',
|
|
async (dataArg) => {
|
|
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
|
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
|
const args = dataArg.recursive ? ['rm', '-rf', dataArg.path] : ['rm', '-f', dataArg.path];
|
|
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
|
containerId,
|
|
args,
|
|
);
|
|
if (result.exitCode !== 0) {
|
|
throw new plugins.typedrequest.TypedResponseError(`Failed to remove: ${result.stderr}`);
|
|
}
|
|
return {};
|
|
},
|
|
),
|
|
);
|
|
|
|
// Check if path exists in container
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceExists>(
|
|
'workspaceExists',
|
|
async (dataArg) => {
|
|
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
|
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
|
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
|
containerId,
|
|
['test', '-e', dataArg.path],
|
|
);
|
|
return { exists: result.exitCode === 0 };
|
|
},
|
|
),
|
|
);
|
|
|
|
// Execute a command in the container (non-interactive)
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceExec>(
|
|
'workspaceExec',
|
|
async (dataArg) => {
|
|
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
|
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
|
const cmd = dataArg.args
|
|
? [dataArg.command, ...dataArg.args]
|
|
: [dataArg.command];
|
|
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
|
containerId,
|
|
cmd,
|
|
);
|
|
return {
|
|
stdout: result.stdout,
|
|
stderr: result.stderr,
|
|
exitCode: result.exitCode,
|
|
};
|
|
},
|
|
),
|
|
);
|
|
|
|
logger.info('Workspace handler registered');
|
|
}
|
|
}
|