container update no longer fails on Podman for containers using userns_mode: keep-id
This commit is contained in:
2 files changed
+49
-1
No files matched your search
@@ -18,7 +18,7 @@ import { computeRequestTimeoutMs } from './backups/request-timeout';
|
|||||||
import type { Environment } from './db';
|
import type { Environment } from './db';
|
||||||
import { getSetting } from './db';
|
import { getSetting } from './db';
|
||||||
import { getAdditionalVolumeBinds, dedupeVolumesForRecreate } from './mount-dedupe';
|
import { getAdditionalVolumeBinds, dedupeVolumesForRecreate } from './mount-dedupe';
|
||||||
import { resolveNanoCpusConflict } from './hostconfig-recreate';
|
import { resolveNanoCpusConflict, resolvePodmanUsernsMode } from './hostconfig-recreate';
|
||||||
import { rebaseEnvOntoImage, rebaseLabelsOntoImage, rebaseCommand, describeEnvRebase, describeLabelRebase, type ImageEnvLabels } from './container-env-merge';
|
import { rebaseEnvOntoImage, rebaseLabelsOntoImage, rebaseCommand, describeEnvRebase, describeLabelRebase, type ImageEnvLabels } from './container-env-merge';
|
||||||
import { encodeRegistryAuth } from './registry-auth';
|
import { encodeRegistryAuth } from './registry-auth';
|
||||||
import { isSystemContainer, classifyEmptyDigestImage, localDigestIsIndexChild } from './scheduler/tasks/update-utils';
|
import { isSystemContainer, classifyEmptyDigestImage, localDigestIsIndexChild } from './scheduler/tasks/update-utils';
|
||||||
@@ -2093,6 +2093,20 @@ export async function recreateContainerFromInspect(
|
|||||||
log?.('Dropped CpuPeriod/CpuQuota — they conflict with NanoCpus at create time (kept NanoCpus)');
|
log?.('Dropped CpuPeriod/CpuQuota — they conflict with NanoCpus at create time (kept NanoCpus)');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Podman lowers `--userns keep-id` to UsernsMode:"private" in inspect, which create then
|
||||||
|
// rejects without inline mappings (#1409). Restore the original intent from the annotation
|
||||||
|
// so keep-id survives the recreate; Docker (UsernsMode != "private") is untouched.
|
||||||
|
const usernsFix = resolvePodmanUsernsMode(createConfig.HostConfig?.UsernsMode, config.Annotations);
|
||||||
|
if (usernsFix && createConfig.HostConfig) {
|
||||||
|
if ('mode' in usernsFix) {
|
||||||
|
createConfig.HostConfig.UsernsMode = usernsFix.mode;
|
||||||
|
log?.(`Restored UsernsMode "${usernsFix.mode}" from Podman annotation (was "private")`);
|
||||||
|
} else {
|
||||||
|
delete createConfig.HostConfig.UsernsMode;
|
||||||
|
log?.('Dropped UsernsMode "private" — Podman rejects it without inline UID/GID mappings');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// container:<name> mode shares the network namespace — Docker rejects
|
// container:<name> mode shares the network namespace — Docker rejects
|
||||||
// networking-related fields on the dependent container since they're
|
// networking-related fields on the dependent container since they're
|
||||||
// owned by the network provider container
|
// owned by the network provider container
|
||||||
@@ -2305,6 +2319,14 @@ export async function createContainerFromMetadata(
|
|||||||
delete createConfig.HostConfig.MemorySwappiness;
|
delete createConfig.HostConfig.MemorySwappiness;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Podman keep-id -> UsernsMode:"private" in inspect, rejected on create (#1409).
|
||||||
|
// Restore the intent from the annotation; Docker (UsernsMode != "private") untouched.
|
||||||
|
const usernsFix = resolvePodmanUsernsMode(createConfig.HostConfig?.UsernsMode, config.Annotations);
|
||||||
|
if (usernsFix && createConfig.HostConfig) {
|
||||||
|
if ('mode' in usernsFix) createConfig.HostConfig.UsernsMode = usernsFix.mode;
|
||||||
|
else delete createConfig.HostConfig.UsernsMode;
|
||||||
|
}
|
||||||
|
|
||||||
// container:<name> mode: clean up conflicting network fields
|
// container:<name> mode: clean up conflicting network fields
|
||||||
if (networkMode.startsWith('container:')) {
|
if (networkMode.startsWith('container:')) {
|
||||||
delete createConfig.Hostname;
|
delete createConfig.Hostname;
|
||||||
|
|||||||
@@ -32,3 +32,29 @@ export function resolveNanoCpusConflict(hostConfig: CpuHostConfig | null | undef
|
|||||||
delete hostConfig.CpuQuota;
|
delete hostConfig.CpuQuota;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PODMAN_USERNS_ANNOTATION = 'io.podman.annotations.userns';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Podman lowers `--userns keep-id`/`auto`/`nomap` to `UsernsMode: "private"` plus generated
|
||||||
|
* `IDMappings` in inspect, losing the original intent. Replaying `UsernsMode: "private"` to
|
||||||
|
* create WITHOUT inline mappings is rejected ("must provide at least one UID or GID mapping
|
||||||
|
* to configure a user namespace", #1409). Podman keeps the real intent in the
|
||||||
|
* `io.podman.annotations.userns` annotation, and `create --userns keep-id` regenerates the
|
||||||
|
* mappings - so restore the annotation value to keep keep-id working across a recreate.
|
||||||
|
*
|
||||||
|
* Returns the corrected UsernsMode: the annotation value ("keep-id", "auto",
|
||||||
|
* "keep-id:uid=1000,gid=1000", ...) when present, `undefined` to STRIP a bare "private" with
|
||||||
|
* no recorded intent, or `null` for "leave UsernsMode untouched". Only the lowered "private"
|
||||||
|
* form is ever changed, so Docker (UsernsMode "", "host", "container:x", ...) is never
|
||||||
|
* touched - no Docker regression.
|
||||||
|
*/
|
||||||
|
export function resolvePodmanUsernsMode(
|
||||||
|
usernsMode: string | undefined | null,
|
||||||
|
annotations: Record<string, string> | undefined | null
|
||||||
|
): { mode: string } | { strip: true } | null {
|
||||||
|
if (usernsMode !== 'private') return null;
|
||||||
|
const intent = annotations?.[PODMAN_USERNS_ANNOTATION]?.trim();
|
||||||
|
if (intent) return { mode: intent };
|
||||||
|
return { strip: true };
|
||||||
|
}
|
||||||
Reference in new issue
Block a user