/** * Network RPC handlers. */ import { docker } from '../services/docker.js' import * as validation from '../utils/validation.js' export function registerNetworkHandlers(session) { session.respond('listNetworks', async () => { const networks = await docker.listNetworks() const containers = await docker.listContainers({ all: true }) const networkUsage = {} for (const container of containers) { const nets = container.NetworkSettings?.Networks if (!nets) continue for (const networkName of Object.keys(nets)) { if (!networkUsage[networkName]) networkUsage[networkName] = [] networkUsage[networkName].push({ id: container.Id, name: container.Names[0]?.replace(/^\//, '') || container.Id.substring(0, 12), state: container.State, }) } } return { type: 'networks', data: networks.map((network) => ({ ...network, usage: networkUsage[network.Name] || [], })), } }) session.respond('createNetwork', async (args) => { const name = validation.sanitizeString(args.name, 128) if (!name) throw new Error('Network name required') const networkConfig = { Name: name, Driver: args.driver || 'bridge', CheckDuplicate: true, Internal: Boolean(args.internal), Attachable: args.attachable !== false, EnableIPv6: Boolean(args.enableIPv6 || args.ipv6), Labels: args.labels && typeof args.labels === 'object' ? args.labels : undefined, } // Auto-fill IPAM when client asks (or subnet provided) let subnet = args.subnet let gateway = args.gateway if ((!subnet || args.autoIPAM) && networkConfig.Driver === 'bridge') { const { suggestNetworkIPAM } = await import('../utils/suggestions.js') const sug = await suggestNetworkIPAM() if (!subnet && sug.subnet) { subnet = sug.subnet gateway = gateway || sug.gateway } } if (subnet || gateway || args.ipRange) { networkConfig.IPAM = { Config: [ { ...(subnet ? { Subnet: subnet } : {}), ...(gateway ? { Gateway: gateway } : {}), ...(args.ipRange ? { IPRange: args.ipRange } : {}), }, ], } } if (args.options && typeof args.options === 'object') { networkConfig.Options = args.options } const network = await docker.createNetwork(networkConfig) return { success: true, message: `Network "${name}" created successfully`, data: network.id, applied: { name, driver: networkConfig.Driver, subnet, gateway }, } }) session.respond('removeNetwork', async (args) => { await docker.getNetwork(args.id).remove() return { success: true, message: `Network ${args.id} removed` } }) session.respond('inspectNetwork', async (args) => { const networkData = await docker.getNetwork(args.id).inspect() return { type: 'networkConfig', data: networkData } }) session.respond('connectNetwork', async (args) => { const networkId = args.networkId || args.id || args.network const containerId = args.containerId || args.container if (!networkId || !containerId) { throw Object.assign(new Error('networkId and containerId required'), { code: 'INVALID_ARGS', }) } /** @type {Record} */ const endpointConfig = {} if (Array.isArray(args.aliases) && args.aliases.length) { endpointConfig.Aliases = args.aliases .map((a) => String(a || '').trim()) .filter(Boolean) } const ipv4 = args.ipv4Address || args.IPv4Address const ipv6 = args.ipv6Address || args.IPv6Address if (ipv4 || ipv6) { endpointConfig.IPAMConfig = {} if (ipv4) endpointConfig.IPAMConfig.IPv4Address = String(ipv4).trim() if (ipv6) endpointConfig.IPAMConfig.IPv6Address = String(ipv6).trim() } const connectOpts = { Container: containerId } if (Object.keys(endpointConfig).length) { connectOpts.EndpointConfig = endpointConfig } await docker.getNetwork(networkId).connect(connectOpts) return { success: true, message: 'Container connected to network', networkId, containerId, } }) session.respond('disconnectNetwork', async (args) => { const networkId = args.networkId || args.id || args.network const containerId = args.containerId || args.container if (!networkId || !containerId) { throw Object.assign(new Error('networkId and containerId required'), { code: 'INVALID_ARGS', }) } await docker .getNetwork(networkId) .disconnect({ Container: containerId, Force: args.force || false }) return { success: true, message: 'Container disconnected from network', networkId, containerId, } }) session.respond('pruneNetworks', async (args) => { const opts = {} if (args.filters) opts.filters = args.filters const result = await docker.pruneNetworks(opts) return { success: true, type: 'pruneNetworks', message: 'Unused networks pruned', data: result, } }) }