CI / test (push) Successful in 9m58s
Add tunnel persistence and container one-click tunnels with local Holesail client bind, Swarm services/nodes/tasks view, GitOps stack sync from git, deploy wizard step chrome, virtualized container lists, and structure regression tests. Mark Tracks A–D and optional future done.
117 lines
3.9 KiB
JavaScript
117 lines
3.9 KiB
JavaScript
/**
|
|
* 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'
|
|
import { fetchComposeFromGit } from '../utils/gitops.js'
|
|
import logger from '../utils/logger.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,
|
|
})
|
|
})
|
|
|
|
/**
|
|
* GitOps: fetch compose from git and deploy.
|
|
* args: { stackName, repoUrl, ref?, composePath?, build?, rollback? }
|
|
*/
|
|
session.respond('syncStackFromGit', async (args = {}) => {
|
|
const stackName = validation.sanitizeString(args.stackName, 63)
|
|
if (!stackName || !validation.isValidContainerName(stackName)) {
|
|
throw new Error('Valid stackName required')
|
|
}
|
|
if (!args.repoUrl) throw new Error('repoUrl required')
|
|
|
|
logger.info('GitOps sync starting', {
|
|
stackName,
|
|
repo: String(args.repoUrl).slice(0, 80),
|
|
ref: args.ref || 'main',
|
|
})
|
|
const fetched = await fetchComposeFromGit({
|
|
repoUrl: args.repoUrl,
|
|
ref: args.ref,
|
|
composePath: args.composePath,
|
|
})
|
|
composeManager.validateComposeFile(fetched.composeContent)
|
|
const result = await composeManager.deployComposeStack(
|
|
docker,
|
|
fetched.composeContent,
|
|
stackName,
|
|
{
|
|
build: Boolean(args.build),
|
|
rollback: args.rollback !== false,
|
|
}
|
|
)
|
|
await broadcastContainers()
|
|
return {
|
|
success: true,
|
|
message: `Stack "${stackName}" deployed from git${fetched.commit ? ` @ ${fetched.commit.slice(0, 8)}` : ''}`,
|
|
commit: fetched.commit || null,
|
|
composePath: fetched.path,
|
|
...result,
|
|
}
|
|
})
|
|
}
|