/** * Docker Compose stack RPC handlers. */ import * as composeManager from '../utils/composeManager.js' import * as validation from '../utils/validation.js' import { docker } from '../services/docker.js' import { broadcastContainers } from './containers.js' export function registerStackHandlers(session) { session.respond('deployStack', async (args) => { const { composeContent, stackName } = args if (!composeContent || !stackName) { throw new Error('Compose content and stack name required') } const sanitizedStackName = validation.sanitizeString(stackName, 63) if (!validation.isValidContainerName(sanitizedStackName)) { throw new Error('Invalid stack name') } // Validate YAML early for clear errors composeManager.validateComposeFile(composeContent) const profiles = Array.isArray(args.profiles) ? args.profiles.map(String) : typeof args.profiles === 'string' ? args.profiles.split(',').map((s) => s.trim()).filter(Boolean) : [] const result = await composeManager.deployComposeStack( docker, composeContent, sanitizedStackName, { overrideContent: args.overrideContent || args.override || null, envFileContent: args.envFileContent || args.envFile || null, profiles, env: args.env && typeof args.env === 'object' ? args.env : {}, build: Boolean(args.build), // Default true: tear down containers created in a failed partial deploy rollback: args.rollback !== false, } ) await broadcastContainers() return { success: true, ...result } }) session.respond('listStacks', async () => { const stacks = await composeManager.listStacks(docker) return { type: 'stacks', data: stacks } }) session.respond('removeStack', async (args) => { const result = await composeManager.removeComposeStack(docker, args.stackName) await broadcastContainers() return { success: true, ...result } }) session.respond('stackPs', async (args) => { if (!args.stackName) throw new Error('stackName required') return composeManager.stackPs(docker, args.stackName) }) session.respond('stackLogs', async (args) => { if (!args.stackName) throw new Error('stackName required') return composeManager.stackLogs(docker, args.stackName, { tail: args.tail, }) }) session.respond('stackPull', async (args) => { if (!args.stackName) throw new Error('stackName required') return composeManager.stackPull(docker, args.stackName, { composeContent: args.composeContent, }) }) }