e63d2ffe9b
Clicking "Update" on a stack member (and the native auto-update daemon updating one) redeployed the whole compose stack instead of updating just that container. Match Watchtower behaviour: always recreate the single container with a re-pull. The recreate endpoint preserves config + compose labels, so the container stays part of its project. Collapse all update surfaces to a single-container recreate and drop the now-dead stack-aware routing: - frontend: "Update now" button, list badge and bulk "Update selected" now recreate each container individually; remove standalone/stack/external routing, the external refusal, the PortainerStackUpdate gate and the stack-update confirm dialog. - daemon: route every outdated candidate through updateStandalone; remove updateStack, the stack/external grouping and the stackDeployer dependency. - add a regression test asserting a Portainer-managed compose-stack member is recreated individually, not stack-redeployed. Behavioural notes: git/external compose containers are now auto-updated too (were detect-only), and updating a stack member no longer requires PortainerStackUpdate (same auth as the normal Recreate action). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
86 lines
2.4 KiB
TypeScript
86 lines
2.4 KiB
TypeScript
import { EnvironmentId } from '@/react/portainer/environments/types';
|
|
import { confirmContainerRecreation } from '@/react/docker/containers/ItemView/ConfirmRecreationModal';
|
|
import { notifySuccess } from '@/portainer/services/notifications';
|
|
|
|
import { ContainerId } from '../types';
|
|
|
|
import { useUpdateContainerImage } from './useUpdateContainerImage';
|
|
import { ContainerUpdateContext } from './types';
|
|
|
|
interface Params {
|
|
environmentId: EnvironmentId;
|
|
containerId: ContainerId;
|
|
nodeName?: string;
|
|
containerImage: string;
|
|
containerName: string;
|
|
labels?: Record<string, string>;
|
|
/** Portainer's own container can't update itself, so the action is disabled. */
|
|
isPortainer: boolean;
|
|
/**
|
|
* Post-success side effect. The details-view button navigates/reloads here;
|
|
* the list badge leaves it empty and lets react-query invalidation refresh the
|
|
* row.
|
|
*/
|
|
onSuccess?: () => void;
|
|
}
|
|
|
|
/**
|
|
* Shared "apply an image update to a single container" action, used by both the
|
|
* details-view "Update now" button and the interactive list badge so the confirm
|
|
* dialog lives in ONE place.
|
|
*
|
|
* Watchtower-style: always recreates just this one container with a fresh image
|
|
* pull (the recreate endpoint preserves config + compose labels, so a stack
|
|
* member stays part of its project). The only container excluded is Portainer's
|
|
* own, which can't recreate itself.
|
|
*/
|
|
export function useApplyContainerImageUpdate({
|
|
environmentId,
|
|
containerId,
|
|
nodeName,
|
|
containerImage,
|
|
containerName,
|
|
labels,
|
|
isPortainer,
|
|
onSuccess,
|
|
}: Params) {
|
|
const updateMutation = useUpdateContainerImage();
|
|
|
|
const canApply = !isPortainer && !updateMutation.isLoading;
|
|
|
|
return {
|
|
apply,
|
|
isLoading: updateMutation.isLoading,
|
|
canApply,
|
|
};
|
|
|
|
async function apply() {
|
|
const context: ContainerUpdateContext = {
|
|
id: containerId,
|
|
name: containerName,
|
|
image: containerImage,
|
|
labels,
|
|
environmentId,
|
|
nodeName,
|
|
};
|
|
|
|
const cannotPullImage =
|
|
!containerImage || containerImage.toLowerCase().startsWith('sha256');
|
|
const result = await confirmContainerRecreation(cannotPullImage);
|
|
if (!result) {
|
|
return;
|
|
}
|
|
const pullImage = result.pullLatest;
|
|
|
|
updateMutation.mutate(
|
|
{ context, pullImage },
|
|
{
|
|
onSuccess: () => {
|
|
notifySuccess('Success', 'Container image update applied');
|
|
onSuccess?.();
|
|
},
|
|
}
|
|
);
|
|
}
|
|
}
|