This commit is contained in:
@@ -570,24 +570,75 @@ export async function listStacks(docker) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Built-in Docker networks that must never be removed. */
|
||||
const PREDEFINED_NETWORKS = new Set(['bridge', 'host', 'none'])
|
||||
|
||||
/**
|
||||
* Networks owned by a compose project (created via `docker compose up`).
|
||||
* @param {import('dockerode')} docker
|
||||
* @param {string} stackName
|
||||
*/
|
||||
async function listProjectNetworks(docker, stackName) {
|
||||
const networks = await docker.listNetworks()
|
||||
return networks.filter((n) => {
|
||||
if (!n?.Name || PREDEFINED_NETWORKS.has(n.Name)) return false
|
||||
const labels = n.Labels || {}
|
||||
return labels['com.docker.compose.project'] === stackName
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove compose project networks after containers are gone.
|
||||
* Matches `docker compose down` behavior (networks yes, volumes no).
|
||||
* @param {import('dockerode')} docker
|
||||
* @param {string} stackName
|
||||
* @returns {Promise<Array<{ id: string, name: string, success: boolean, error?: string }>>}
|
||||
*/
|
||||
async function removeProjectNetworks(docker, stackName) {
|
||||
const projectNetworks = await listProjectNetworks(docker, stackName)
|
||||
const results = []
|
||||
for (const netInfo of projectNetworks) {
|
||||
const id = netInfo.Id
|
||||
const name = netInfo.Name
|
||||
try {
|
||||
await docker.getNetwork(id).remove()
|
||||
results.push({ id, name, success: true })
|
||||
logger.info('Removed stack network', { stackName, name, id: id?.slice?.(0, 12) })
|
||||
} catch (error) {
|
||||
results.push({ id, name, success: false, error: error.message })
|
||||
logger.warn('Failed to remove stack network', {
|
||||
stackName,
|
||||
name,
|
||||
error: error.message,
|
||||
})
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('dockerode')} docker
|
||||
* @param {string} stackName
|
||||
*/
|
||||
export async function removeComposeStack(docker, stackName) {
|
||||
// Try compose down if we can find any container with project label (CLI needs a file — use stub)
|
||||
// Remove project containers, then project-scoped networks (compose down parity).
|
||||
// CLI `compose down` needs a compose file; we use label-based dockerode cleanup instead.
|
||||
const containers = await docker.listContainers({ all: true })
|
||||
const stackContainers = containers.filter((c) => {
|
||||
const labels = c.Labels || {}
|
||||
return labels['com.docker.compose.project'] === stackName
|
||||
})
|
||||
|
||||
if (stackContainers.length === 0) {
|
||||
const networkCandidates = await listProjectNetworks(docker, stackName)
|
||||
|
||||
if (stackContainers.length === 0 && networkCandidates.length === 0) {
|
||||
return {
|
||||
success: true,
|
||||
stackName,
|
||||
removed: 0,
|
||||
networksRemoved: 0,
|
||||
results: [],
|
||||
networkResults: [],
|
||||
message: `Stack "${stackName}" not found or already removed`,
|
||||
}
|
||||
}
|
||||
@@ -606,12 +657,24 @@ export async function removeComposeStack(docker, stackName) {
|
||||
}
|
||||
}
|
||||
|
||||
// Containers must be gone (or disconnected) before networks can be removed.
|
||||
const networkResults = await removeProjectNetworks(docker, stackName)
|
||||
const removed = results.filter((r) => r.success).length
|
||||
const networksRemoved = networkResults.filter((r) => r.success).length
|
||||
|
||||
const parts = []
|
||||
if (removed) parts.push(`${removed} container(s)`)
|
||||
if (networksRemoved) parts.push(`${networksRemoved} network(s)`)
|
||||
const detail = parts.length ? parts.join(', ') : 'nothing left to remove'
|
||||
|
||||
return {
|
||||
success: true,
|
||||
stackName,
|
||||
removed: results.filter((r) => r.success).length,
|
||||
removed,
|
||||
networksRemoved,
|
||||
results,
|
||||
message: `Stack "${stackName}" removed successfully`,
|
||||
networkResults,
|
||||
message: `Stack "${stackName}" removed successfully (${detail})`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user