b3ae5f3659
Add an optional periodic auto-update daemon that detects outdated container
images and applies updates, replacing the containrrr/watchtower sidecar. It
extends M1's containerautomation service/scheduler/labels infrastructure and
reuses the existing zlib image-detection engine, the standalone Recreate path
and the stack deployer.
Backend:
- api/containerautomation/autoupdate.go: scheduler job iterating Docker
(non-edge) endpoints -> in-scope running containers -> ContainerImageStatus;
for Outdated: standalone -> ContainerService.Recreate(pull); stack-managed ->
one stack redeploy-with-pull per stack per tick (git via RedeployWhenChanged,
file via the deployer directly); external compose -> detect only. Monitor-only
containers are status-checked (warms the badge cache) but never applied.
Overlap guard (atomic), pull/registry-auth failure -> leave running container
untouched, conservative cleanup of the dangling old image on the Cleanup flag
(non-forced ImageRemove only succeeds when truly unused).
- labels.go: update enable / monitor-only labels with watchtower aliases,
InUpdateScope, IsMonitorOnly, and pure resolveContainerUpdateRouting /
groupContainersForUpdate (Go analogue of M3's TS routing + grouping).
- service.go: run both jobs, Reload restarts/stops each per settings; NewService
also takes ContainerService, StackDeployer and GitService.
- Settings.ContainerAutomation.AutoUpdate {Enabled, PollInterval, Scope,
Cleanup} with fresh-install defaults and a 2.43.0 backfill (extends M1's
migration; golden test data updated). settings handler validates + reloads.
Frontend:
- Global AutoUpdatePanel in SettingsView (enable / poll interval / scope /
cleanup) via useUpdateSettingsMutation, plus settings TS types.
- Read-only per-container Auto-update row in the container details view
(Docker labels are immutable at runtime), surfacing monitor-only.
Tests: Go unit tests for the update label aliases, scope, monitor-only, the
routing decision and the one-redeploy-per-stack grouping; vitest for the panel
and the per-container row.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
66 lines
2.3 KiB
TypeScript
66 lines
2.3 KiB
TypeScript
import { DetailsTable } from '@@/DetailsTable';
|
|
|
|
interface Props {
|
|
labels?: Record<string, string>;
|
|
}
|
|
|
|
// Label keys (with watchtower aliases) mirroring the backend
|
|
// (api/containerautomation/labels.go).
|
|
const ENABLE_LABEL = 'io.portainer.update.enable';
|
|
const ENABLE_LABEL_ALIAS = 'com.centurylinklabs.watchtower.enable';
|
|
const MONITOR_ONLY_LABEL = 'io.portainer.update.monitor-only';
|
|
const MONITOR_ONLY_LABEL_ALIAS = 'com.centurylinklabs.watchtower.monitor-only';
|
|
|
|
function parseBool(value?: string) {
|
|
if (value === undefined) {
|
|
return undefined;
|
|
}
|
|
// Mirror Go's strconv.ParseBool (used by the backend label parser): accept
|
|
// 1/t/true case-insensitively as truthy. Any other present-but-invalid value
|
|
// (including 0/f/false and garbage) counts as present & false, matching how
|
|
// the backend treats an unparseable label.
|
|
const normalized = value.toLowerCase();
|
|
return normalized === '1' || normalized === 't' || normalized === 'true';
|
|
}
|
|
|
|
/**
|
|
* AutoUpdateRow shows the per-container auto-update opt-in state, resolved from
|
|
* the container's immutable Docker labels. It is read-only: because labels
|
|
* cannot be changed on a running container, opt-in is set through the
|
|
* Create/Edit form labels and the global behavior is controlled in Settings.
|
|
*/
|
|
export function AutoUpdateRow({ labels }: Props) {
|
|
const enabled =
|
|
parseBool(labels?.[ENABLE_LABEL]) ?? parseBool(labels?.[ENABLE_LABEL_ALIAS]);
|
|
const monitorOnly =
|
|
parseBool(labels?.[MONITOR_ONLY_LABEL]) ??
|
|
parseBool(labels?.[MONITOR_ONLY_LABEL_ALIAS]);
|
|
|
|
let stateLabel: string;
|
|
if (enabled === true) {
|
|
stateLabel = 'Enabled';
|
|
} else if (enabled === false) {
|
|
stateLabel = 'Disabled (opted out)';
|
|
} else {
|
|
stateLabel = 'Not labeled (follows global scope)';
|
|
}
|
|
|
|
return (
|
|
<DetailsTable.Row label="Auto-update">
|
|
<div className="space-y-1">
|
|
<div>{stateLabel}</div>
|
|
{monitorOnly === true && (
|
|
<div className="small text-muted">
|
|
Monitor-only: updates are detected but never applied automatically.
|
|
</div>
|
|
)}
|
|
<div className="small text-muted">
|
|
Set via the <code>{ENABLE_LABEL}</code> label (immutable at runtime;
|
|
edit through the container Create/Edit form). Global behavior is
|
|
configured in Settings.
|
|
</div>
|
|
</div>
|
|
</DetailsTable.Row>
|
|
);
|
|
}
|