mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 14:09:42 +02:00
feat(docker): let one adopted container back several cases in different dirs
Once a container is adopted, it could not be adopted a second time. But a
container usually holds more than one project directory, and opening a case for
another one had no path forward except starting a second container — precisely
what adoption exists to avoid.
The original reason was in a comment: two cases sharing an adopted container
would make one case's teardown race the other's launch on the same tmux server.
That reason does not hold. The in-container tmux session name is
dockerTmuxSessionName(sessionId), i.e. codeman-dkr-<id8>, keyed by SESSION and
not by case, and buildDockerKillCommand tears down exactly that name, so killing
A never touches B — hosting multiple sessions is what a tmux server is for.
The other three routes into an adopted container's lifecycle do not pass through
here either, confirmed one by one: the stop and remove builders throw outright;
recreate refuses `owned === false` before it even resolves the container name;
and orphan reaping filters on `label=codeman.managed=1`, which a user-built
container does not carry — a structural exclusion.
That leaves exactly three cases worth refusing, none of them tmux-related, split
into the pure, unit-tested classifyAdoptContainerConflict:
- owned-case the container belongs to a Codeman-created case, whose lifecycle
Codeman manages: one recreate or delete there would pull the
container out from under the adopting case.
⚠️ `owned` may be absent and absent means owned (cases predate
the field), so the test is `!== false`, not truthiness.
- other-owner already adopted by a different user. Adoption hands out a shell
inside someone else's container.
- duplicate same container, same directory. The second case would behave
identically to the first, so name the existing one rather than
silently minting a twin. A different in-container directory is
the case this change exists to support and passes.
(cherry picked from commit 1cb6bde891)
This commit is contained in:
committed by
Codeman maintainer
parent
e5684d0bba
commit
cbb7f635ff
@@ -79,6 +79,7 @@ import {
|
||||
dockerContainerName,
|
||||
dockerDisplayPath,
|
||||
probeAdoptableContainer,
|
||||
classifyAdoptContainerConflict,
|
||||
listDockerContainers,
|
||||
browseInContainer,
|
||||
dockerAdoptProbeModes,
|
||||
@@ -906,12 +907,35 @@ export function registerCaseRoutes(app: FastifyInstance, ctx: EventPort & Config
|
||||
) {
|
||||
return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, 'Case already exists');
|
||||
}
|
||||
// Two cases must never share one adopted container: session close kills the
|
||||
// in-container tmux by session id, but a shared adoption would let one case's
|
||||
// teardown and another's launch race over the same tmux server.
|
||||
// One container may back SEVERAL adopted cases, each pointing at a different
|
||||
// directory inside it. What still blocks it, and why, lives in
|
||||
// classifyAdoptContainerConflict — note that none of it is about the shared
|
||||
// in-container tmux server, which is safe precisely because sessions there
|
||||
// are named per SESSION id (`codeman-dkr-<id8>`), never per case.
|
||||
const container = dockerCase.container;
|
||||
if (dockerCases.some((item) => (item.container ?? dockerContainerName(item.name)) === container)) {
|
||||
return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, `Container "${container}" is already linked to a case`);
|
||||
const conflict = classifyAdoptContainerConflict({
|
||||
container,
|
||||
containerWorkdir: dockerCase.containerWorkdir ?? dockerCase.hostWorkspacePath,
|
||||
existing: dockerCases,
|
||||
canAccess: (owner) => canAccessOwned(getAuthUser(req), owner),
|
||||
});
|
||||
if (conflict?.kind === 'owned-case') {
|
||||
return createErrorResponse(
|
||||
ApiErrorCode.ALREADY_EXISTS,
|
||||
`Container "${container}" belongs to case "${conflict.caseName}", which Codeman created and whose lifecycle it manages. Adopt a container you started yourself, or open that case directly.`
|
||||
);
|
||||
}
|
||||
if (conflict?.kind === 'other-owner') {
|
||||
return createErrorResponse(
|
||||
ApiErrorCode.FORBIDDEN,
|
||||
`Container "${container}" is already adopted by another user.`
|
||||
);
|
||||
}
|
||||
if (conflict?.kind === 'duplicate') {
|
||||
return createErrorResponse(
|
||||
ApiErrorCode.ALREADY_EXISTS,
|
||||
`Case "${conflict.caseName}" already adopts "${container}" at that same directory. Point this one at another directory inside the container.`
|
||||
);
|
||||
}
|
||||
|
||||
if (!isWorkingDirAllowed(getAuthUser(req), dockerCase.hostWorkspacePath)) {
|
||||
|
||||
Reference in New Issue
Block a user