Files
portainer/app/react/docker/containers/update/resolveContainerUpdatePath.ts
T
claude code agent f7cb0f3241 feat(automation): "Update now" action (stack-aware) + bulk update (#10, epic #3 M3)
Add a discoverable per-container "Update now" action, shown only when the
image status is `outdated`, plus a bulk "Update selected" action in the
containers list.

Both manual paths share ONE apply primitive (applyContainerUpdate /
useUpdateContainerImage) that also backs the future M4 auto-update job:

- standalone container  -> recreate-with-pull (existing recreate endpoint)
- stack-managed         -> stack redeploy-with-pull (existing git/file stack
                           update mutations), so the container stays in its
                           stack and is never recreated out-of-band
- externally-managed    -> refused; the details button is disabled with an
  compose                  explanatory tooltip and the bulk action skips it

Decision logic lives in the pure, unit-tested resolveContainerUpdatePath /
groupContainersForUpdate helpers. The bulk action filters to outdated
containers and redeploys each owning stack exactly once even when several of
its containers are selected, reporting per-item success/failure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 09:24:10 +03:00

42 lines
1.4 KiB
TypeScript

import { COMPOSE_STACK_NAME_LABEL } from '@/react/constants';
import { Stack } from '@/react/common/stacks/types';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { ContainerUpdatePath } from './types';
/**
* Decide how a container's image update must be applied, given the list of
* Portainer-managed stacks. Pure and side-effect free so it can be unit-tested
* and reused by the details button, the bulk action and the M4 auto-update job.
*
* - No compose project label -> `standalone` (recreate-with-pull).
* - Compose project that matches a Portainer `Stack` (same name + endpoint) ->
* `stack` (redeploy-with-pull, keeps the container in its stack).
* - Compose project with no matching Portainer stack -> `external`: managed
* outside Portainer, so we must not recreate it (would detach it / drift).
*/
export function resolveContainerUpdatePath(
context: { labels?: Record<string, string>; environmentId: EnvironmentId },
stacks: Stack[]
): ContainerUpdatePath {
const projectName = context.labels?.[COMPOSE_STACK_NAME_LABEL];
if (!projectName) {
return { kind: 'standalone' };
}
const stack = stacks.find(
(s) => s.Name === projectName && s.EndpointId === context.environmentId
);
if (!stack) {
return { kind: 'external' };
}
return {
kind: 'stack',
stackId: stack.Id,
isGitStack: !!stack.GitConfig,
};
}