mirror of https://github.com/containers/podman.git
Allow removing implicit quadlet systemd dependencies
Quadlet inserts network-online.target Wants/After dependencies to ensure pulling works. Those systemd statements cannot be subsequently reset. In the cases where those dependencies are not wanted, we add a new configuration item called `DefaultDependencies=` in a new section called [Quadlet]. This section is shared between different unit types. fixes #24193 Signed-off-by: Farya L. Maerten <me@ltow.me>
This commit is contained in:
parent
6b0ad8269c
commit
bac655a6b1
|
@ -236,6 +236,14 @@ QUADLET_UNIT_DIRS=<Directory> /usr/lib/systemd/system-generators/podman-system-g
|
||||||
This will instruct Quadlet to look for units in this directory instead of the common ones and by
|
This will instruct Quadlet to look for units in this directory instead of the common ones and by
|
||||||
that limit the output to only the units you are debugging.
|
that limit the output to only the units you are debugging.
|
||||||
|
|
||||||
|
### Implicit network dependencies
|
||||||
|
|
||||||
|
In the case of Container, Image and Build units, Quadlet will add dependencies on the `network-online.target`
|
||||||
|
by adding `After=` and `Wants=` properties to the unit. This is to ensure that the network is reachable if
|
||||||
|
an image needs to be pulled.
|
||||||
|
|
||||||
|
This behavior can be disabled by adding `DefaultDependencies=false` in the `Quadlet` section.
|
||||||
|
|
||||||
## Container units [Container]
|
## Container units [Container]
|
||||||
|
|
||||||
Container units are named with a `.container` extension and contain a `[Container]` section describing
|
Container units are named with a `.container` extension and contain a `[Container]` section describing
|
||||||
|
@ -1914,6 +1922,22 @@ Override the default architecture variant of the container image.
|
||||||
|
|
||||||
This is equivalent to the Podman `--variant` option.
|
This is equivalent to the Podman `--variant` option.
|
||||||
|
|
||||||
|
## Quadlet section [Quadlet]
|
||||||
|
Some quadlet specific configuration is shared between different unit types. Those settings
|
||||||
|
can be configured in the `[Quadlet]` section.
|
||||||
|
|
||||||
|
Valid options for `[Quadlet]` are listed below:
|
||||||
|
|
||||||
|
| **[Quadlet] options** | **Description** |
|
||||||
|
|----------------------------|---------------------------------------------------|
|
||||||
|
| DefaultDependencies=false | Disable implicit network dependencies to the unit |
|
||||||
|
|
||||||
|
### `DefaultDependencies=`
|
||||||
|
|
||||||
|
Add Quadlet's default network dependencies to the unit (default is `true`).
|
||||||
|
|
||||||
|
When set to false, Quadlet will **not** add a dependency (After=, Wants=) to `network-online.target` to the generated unit.
|
||||||
|
|
||||||
## EXAMPLES
|
## EXAMPLES
|
||||||
|
|
||||||
Example `test.container`:
|
Example `test.container`:
|
||||||
|
|
|
@ -171,7 +171,7 @@ sub crossref_doc {
|
||||||
chomp $line;
|
chomp $line;
|
||||||
|
|
||||||
# New section, with its own '| table |' and '### Keyword blocks'
|
# New section, with its own '| table |' and '### Keyword blocks'
|
||||||
if ($line =~ /^##\s+(\S+)\s+units\s+\[(\S+)\]/) {
|
if ($line =~ /^##\s+(\S+)\s+(?:units|section)\s+\[(\S+)\]/) {
|
||||||
my $new_unit = $1;
|
my $new_unit = $1;
|
||||||
$new_unit eq $2
|
$new_unit eq $2
|
||||||
or warn "$ME: $path:$.: inconsistent block names in '$line'\n";
|
or warn "$ME: $path:$.: inconsistent block names in '$line'\n";
|
||||||
|
@ -227,7 +227,7 @@ sub crossref_doc {
|
||||||
}
|
}
|
||||||
|
|
||||||
grep { $_ eq $key } @found_in_table
|
grep { $_ eq $key } @found_in_table
|
||||||
or warn "$ME: $path:$.: key '$key' is not listed in table for unit '$unit'\n";
|
or warn "$ME: $path:$.: key '$key' is not listed in table for unit/section '$unit'\n";
|
||||||
|
|
||||||
push @described, $key;
|
push @described, $key;
|
||||||
$documented{$key}++;
|
$documented{$key}++;
|
||||||
|
|
|
@ -38,6 +38,7 @@ const (
|
||||||
VolumeGroup = "Volume"
|
VolumeGroup = "Volume"
|
||||||
ImageGroup = "Image"
|
ImageGroup = "Image"
|
||||||
BuildGroup = "Build"
|
BuildGroup = "Build"
|
||||||
|
QuadletGroup = "Quadlet"
|
||||||
XContainerGroup = "X-Container"
|
XContainerGroup = "X-Container"
|
||||||
XKubeGroup = "X-Kube"
|
XKubeGroup = "X-Kube"
|
||||||
XNetworkGroup = "X-Network"
|
XNetworkGroup = "X-Network"
|
||||||
|
@ -45,6 +46,7 @@ const (
|
||||||
XVolumeGroup = "X-Volume"
|
XVolumeGroup = "X-Volume"
|
||||||
XImageGroup = "X-Image"
|
XImageGroup = "X-Image"
|
||||||
XBuildGroup = "X-Build"
|
XBuildGroup = "X-Build"
|
||||||
|
XQuadletGroup = "X-Quadlet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Systemd Unit file keys
|
// Systemd Unit file keys
|
||||||
|
@ -70,6 +72,7 @@ const (
|
||||||
KeyCopy = "Copy"
|
KeyCopy = "Copy"
|
||||||
KeyCreds = "Creds"
|
KeyCreds = "Creds"
|
||||||
KeyDecryptionKey = "DecryptionKey"
|
KeyDecryptionKey = "DecryptionKey"
|
||||||
|
KeyDefaultDependencies = "DefaultDependencies"
|
||||||
KeyDevice = "Device"
|
KeyDevice = "Device"
|
||||||
KeyDisableDNS = "DisableDNS"
|
KeyDisableDNS = "DisableDNS"
|
||||||
KeyDNS = "DNS"
|
KeyDNS = "DNS"
|
||||||
|
@ -414,6 +417,11 @@ var (
|
||||||
KeyUserNS: true,
|
KeyUserNS: true,
|
||||||
KeyVolume: true,
|
KeyVolume: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Supported keys in "Quadlet" group
|
||||||
|
supportedQuadletKeys = map[string]bool{
|
||||||
|
KeyDefaultDependencies: true,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (u *UnitInfo) ServiceFileName() string {
|
func (u *UnitInfo) ServiceFileName() string {
|
||||||
|
@ -439,16 +447,26 @@ func isPortRange(port string) bool {
|
||||||
return validPortRange.MatchString(port)
|
return validPortRange.MatchString(port)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkForUnknownKeys(unit *parser.UnitFile, groupName string, supportedKeys map[string]bool) error {
|
func checkForUnknownKeysInSpecificGroup(unit *parser.UnitFile, groupName string, supportedKeys map[string]bool) error {
|
||||||
keys := unit.ListKeys(groupName)
|
keys := unit.ListKeys(groupName)
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
if !supportedKeys[key] {
|
if !supportedKeys[key] {
|
||||||
return fmt.Errorf("unsupported key '%s' in group '%s' in %s", key, groupName, unit.Path)
|
return fmt.Errorf("unsupported key '%s' in group '%s' in %s", key, groupName, unit.Path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkForUnknownKeys(unit *parser.UnitFile, groupName string, supportedKeys map[string]bool) error {
|
||||||
|
err := checkForUnknownKeysInSpecificGroup(unit, groupName, supportedKeys)
|
||||||
|
if err == nil {
|
||||||
|
return checkForUnknownKeysInSpecificGroup(unit, QuadletGroup, supportedQuadletKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func splitPorts(ports string) []string {
|
func splitPorts(ports string) []string {
|
||||||
parts := make([]string, 0)
|
parts := make([]string, 0)
|
||||||
|
|
||||||
|
@ -509,10 +527,10 @@ func ConvertContainer(container *parser.UnitFile, isUser bool, unitsInfoMap map[
|
||||||
// Add a dependency on network-online.target so the image pull does not happen
|
// Add a dependency on network-online.target so the image pull does not happen
|
||||||
// before network is ready
|
// before network is ready
|
||||||
// https://github.com/containers/podman/issues/21873
|
// https://github.com/containers/podman/issues/21873
|
||||||
// Prepend the lines, so the user-provided values
|
if service.LookupBooleanWithDefault(QuadletGroup, KeyDefaultDependencies, true) {
|
||||||
// override the default ones.
|
|
||||||
service.PrependUnitLine(UnitGroup, "After", "network-online.target")
|
service.PrependUnitLine(UnitGroup, "After", "network-online.target")
|
||||||
service.PrependUnitLine(UnitGroup, "Wants", "network-online.target")
|
service.PrependUnitLine(UnitGroup, "Wants", "network-online.target")
|
||||||
|
}
|
||||||
|
|
||||||
if container.Path != "" {
|
if container.Path != "" {
|
||||||
service.Add(UnitGroup, "SourcePath", container.Path)
|
service.Add(UnitGroup, "SourcePath", container.Path)
|
||||||
|
@ -525,6 +543,9 @@ func ConvertContainer(container *parser.UnitFile, isUser bool, unitsInfoMap map[
|
||||||
// Rename old Container group to x-Container so that systemd ignores it
|
// Rename old Container group to x-Container so that systemd ignores it
|
||||||
service.RenameGroup(ContainerGroup, XContainerGroup)
|
service.RenameGroup(ContainerGroup, XContainerGroup)
|
||||||
|
|
||||||
|
// Rename common quadlet group
|
||||||
|
service.RenameGroup(QuadletGroup, XQuadletGroup)
|
||||||
|
|
||||||
// One image or rootfs must be specified for the container
|
// One image or rootfs must be specified for the container
|
||||||
image, _ := container.Lookup(ContainerGroup, KeyImage)
|
image, _ := container.Lookup(ContainerGroup, KeyImage)
|
||||||
rootfs, _ := container.Lookup(ContainerGroup, KeyRootfs)
|
rootfs, _ := container.Lookup(ContainerGroup, KeyRootfs)
|
||||||
|
@ -887,6 +908,9 @@ func ConvertNetwork(network *parser.UnitFile, name string, unitsInfoMap map[stri
|
||||||
/* Rename old Network group to x-Network so that systemd ignores it */
|
/* Rename old Network group to x-Network so that systemd ignores it */
|
||||||
service.RenameGroup(NetworkGroup, XNetworkGroup)
|
service.RenameGroup(NetworkGroup, XNetworkGroup)
|
||||||
|
|
||||||
|
// Rename common quadlet group
|
||||||
|
service.RenameGroup(QuadletGroup, XQuadletGroup)
|
||||||
|
|
||||||
// Derive network name from unit name (with added prefix), or use user-provided name.
|
// Derive network name from unit name (with added prefix), or use user-provided name.
|
||||||
networkName, ok := network.Lookup(NetworkGroup, KeyNetworkName)
|
networkName, ok := network.Lookup(NetworkGroup, KeyNetworkName)
|
||||||
if !ok || len(networkName) == 0 {
|
if !ok || len(networkName) == 0 {
|
||||||
|
@ -994,6 +1018,9 @@ func ConvertVolume(volume *parser.UnitFile, name string, unitsInfoMap map[string
|
||||||
/* Rename old Volume group to x-Volume so that systemd ignores it */
|
/* Rename old Volume group to x-Volume so that systemd ignores it */
|
||||||
service.RenameGroup(VolumeGroup, XVolumeGroup)
|
service.RenameGroup(VolumeGroup, XVolumeGroup)
|
||||||
|
|
||||||
|
// Rename common quadlet group
|
||||||
|
service.RenameGroup(QuadletGroup, XQuadletGroup)
|
||||||
|
|
||||||
// Derive volume name from unit name (with added prefix), or use user-provided name.
|
// Derive volume name from unit name (with added prefix), or use user-provided name.
|
||||||
volumeName, ok := volume.Lookup(VolumeGroup, KeyVolumeName)
|
volumeName, ok := volume.Lookup(VolumeGroup, KeyVolumeName)
|
||||||
if !ok || len(volumeName) == 0 {
|
if !ok || len(volumeName) == 0 {
|
||||||
|
@ -1132,6 +1159,9 @@ func ConvertKube(kube *parser.UnitFile, unitsInfoMap map[string]*UnitInfo, isUse
|
||||||
// Rename old Kube group to x-Kube so that systemd ignores it
|
// Rename old Kube group to x-Kube so that systemd ignores it
|
||||||
service.RenameGroup(KubeGroup, XKubeGroup)
|
service.RenameGroup(KubeGroup, XKubeGroup)
|
||||||
|
|
||||||
|
// Rename common quadlet group
|
||||||
|
service.RenameGroup(QuadletGroup, XQuadletGroup)
|
||||||
|
|
||||||
yamlPath, ok := kube.Lookup(KubeGroup, KeyYaml)
|
yamlPath, ok := kube.Lookup(KubeGroup, KeyYaml)
|
||||||
if !ok || len(yamlPath) == 0 {
|
if !ok || len(yamlPath) == 0 {
|
||||||
return nil, fmt.Errorf("no Yaml key specified")
|
return nil, fmt.Errorf("no Yaml key specified")
|
||||||
|
@ -1264,10 +1294,10 @@ func ConvertImage(image *parser.UnitFile, unitsInfoMap map[string]*UnitInfo) (*p
|
||||||
// Add a dependency on network-online.target so the image pull does not happen
|
// Add a dependency on network-online.target so the image pull does not happen
|
||||||
// before network is ready
|
// before network is ready
|
||||||
// https://github.com/containers/podman/issues/21873
|
// https://github.com/containers/podman/issues/21873
|
||||||
// Prepend the lines, so the user-provided values
|
if service.LookupBooleanWithDefault(QuadletGroup, KeyDefaultDependencies, true) {
|
||||||
// override the default ones.
|
|
||||||
service.PrependUnitLine(UnitGroup, "After", "network-online.target")
|
service.PrependUnitLine(UnitGroup, "After", "network-online.target")
|
||||||
service.PrependUnitLine(UnitGroup, "Wants", "network-online.target")
|
service.PrependUnitLine(UnitGroup, "Wants", "network-online.target")
|
||||||
|
}
|
||||||
|
|
||||||
if image.Path != "" {
|
if image.Path != "" {
|
||||||
service.Add(UnitGroup, "SourcePath", image.Path)
|
service.Add(UnitGroup, "SourcePath", image.Path)
|
||||||
|
@ -1285,6 +1315,9 @@ func ConvertImage(image *parser.UnitFile, unitsInfoMap map[string]*UnitInfo) (*p
|
||||||
/* Rename old Network group to x-Network so that systemd ignores it */
|
/* Rename old Network group to x-Network so that systemd ignores it */
|
||||||
service.RenameGroup(ImageGroup, XImageGroup)
|
service.RenameGroup(ImageGroup, XImageGroup)
|
||||||
|
|
||||||
|
// Rename common quadlet group
|
||||||
|
service.RenameGroup(QuadletGroup, XQuadletGroup)
|
||||||
|
|
||||||
// Need the containers filesystem mounted to start podman
|
// Need the containers filesystem mounted to start podman
|
||||||
service.Add(UnitGroup, "RequiresMountsFor", "%t/containers")
|
service.Add(UnitGroup, "RequiresMountsFor", "%t/containers")
|
||||||
|
|
||||||
|
@ -1349,14 +1382,17 @@ func ConvertBuild(build *parser.UnitFile, unitsInfoMap map[string]*UnitInfo) (*p
|
||||||
// Add a dependency on network-online.target so the image pull does not happen
|
// Add a dependency on network-online.target so the image pull does not happen
|
||||||
// before network is ready
|
// before network is ready
|
||||||
// https://github.com/containers/podman/issues/21873
|
// https://github.com/containers/podman/issues/21873
|
||||||
// Prepend the lines, so the user-provided values
|
if service.LookupBooleanWithDefault(QuadletGroup, KeyDefaultDependencies, true) {
|
||||||
// override the default ones.
|
|
||||||
service.PrependUnitLine(UnitGroup, "After", "network-online.target")
|
service.PrependUnitLine(UnitGroup, "After", "network-online.target")
|
||||||
service.PrependUnitLine(UnitGroup, "Wants", "network-online.target")
|
service.PrependUnitLine(UnitGroup, "Wants", "network-online.target")
|
||||||
|
}
|
||||||
|
|
||||||
/* Rename old Build group to X-Build so that systemd ignores it */
|
/* Rename old Build group to X-Build so that systemd ignores it */
|
||||||
service.RenameGroup(BuildGroup, XBuildGroup)
|
service.RenameGroup(BuildGroup, XBuildGroup)
|
||||||
|
|
||||||
|
// Rename common quadlet group
|
||||||
|
service.RenameGroup(QuadletGroup, XQuadletGroup)
|
||||||
|
|
||||||
// Need the containers filesystem mounted to start podman
|
// Need the containers filesystem mounted to start podman
|
||||||
service.Add(UnitGroup, "RequiresMountsFor", "%t/containers")
|
service.Add(UnitGroup, "RequiresMountsFor", "%t/containers")
|
||||||
|
|
||||||
|
@ -1531,6 +1567,9 @@ func ConvertPod(podUnit *parser.UnitFile, name string, unitsInfoMap map[string]*
|
||||||
/* Rename old Pod group to x-Pod so that systemd ignores it */
|
/* Rename old Pod group to x-Pod so that systemd ignores it */
|
||||||
service.RenameGroup(PodGroup, XPodGroup)
|
service.RenameGroup(PodGroup, XPodGroup)
|
||||||
|
|
||||||
|
// Rename common quadlet group
|
||||||
|
service.RenameGroup(QuadletGroup, XQuadletGroup)
|
||||||
|
|
||||||
// Need the containers filesystem mounted to start podman
|
// Need the containers filesystem mounted to start podman
|
||||||
service.Add(UnitGroup, "RequiresMountsFor", "%t/containers")
|
service.Add(UnitGroup, "RequiresMountsFor", "%t/containers")
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
## assert-key-is-empty "Unit" "Wants"
|
||||||
|
## assert-key-is-empty "Unit" "After"
|
||||||
|
## assert-key-is-empty "Unit" "Before"
|
||||||
|
|
||||||
|
[Quadlet]
|
||||||
|
DefaultDependencies=no
|
||||||
|
|
||||||
|
[Build]
|
||||||
|
ImageTag=localhost/imagename
|
||||||
|
File=Containerfile
|
||||||
|
SetWorkingDirectory=dir
|
|
@ -0,0 +1,9 @@
|
||||||
|
## assert-key-is-empty "Unit" "Wants"
|
||||||
|
## assert-key-is-empty "Unit" "After"
|
||||||
|
## assert-key-is-empty "Unit" "Before"
|
||||||
|
|
||||||
|
[Quadlet]
|
||||||
|
DefaultDependencies=no
|
||||||
|
|
||||||
|
[Container]
|
||||||
|
Image=localhost/imagename
|
|
@ -0,0 +1,9 @@
|
||||||
|
## assert-key-is-empty "Unit" "Wants"
|
||||||
|
## assert-key-is-empty "Unit" "After"
|
||||||
|
## assert-key-is-empty "Unit" "Before"
|
||||||
|
|
||||||
|
[Quadlet]
|
||||||
|
DefaultDependencies=no
|
||||||
|
|
||||||
|
[Image]
|
||||||
|
Image=localhost/imagename
|
|
@ -170,6 +170,15 @@ func (t *quadletTestcase) assertKeyIs(args []string, unit *parser.UnitFile) bool
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *quadletTestcase) assertKeyIsEmpty(args []string, unit *parser.UnitFile) bool {
|
||||||
|
Expect(args).To(HaveLen(2))
|
||||||
|
group := args[0]
|
||||||
|
key := args[1]
|
||||||
|
|
||||||
|
realValues := unit.LookupAll(group, key)
|
||||||
|
return len(realValues) == 0
|
||||||
|
}
|
||||||
|
|
||||||
func (t *quadletTestcase) assertKeyIsRegex(args []string, unit *parser.UnitFile) bool {
|
func (t *quadletTestcase) assertKeyIsRegex(args []string, unit *parser.UnitFile) bool {
|
||||||
Expect(len(args)).To(BeNumerically(">=", 3))
|
Expect(len(args)).To(BeNumerically(">=", 3))
|
||||||
group := args[0]
|
group := args[0]
|
||||||
|
@ -501,6 +510,8 @@ func (t *quadletTestcase) doAssert(check []string, unit *parser.UnitFile, sessio
|
||||||
ok = t.assertStdErrContains(args, session)
|
ok = t.assertStdErrContains(args, session)
|
||||||
case "assert-key-is":
|
case "assert-key-is":
|
||||||
ok = t.assertKeyIs(args, unit)
|
ok = t.assertKeyIs(args, unit)
|
||||||
|
case "assert-key-is-empty":
|
||||||
|
ok = t.assertKeyIsEmpty(args, unit)
|
||||||
case "assert-key-is-regex":
|
case "assert-key-is-regex":
|
||||||
ok = t.assertKeyIsRegex(args, unit)
|
ok = t.assertKeyIsRegex(args, unit)
|
||||||
case "assert-key-contains":
|
case "assert-key-contains":
|
||||||
|
@ -899,6 +910,7 @@ BOGUS=foo
|
||||||
Entry("Unit After Override", "unit-after-override.container"),
|
Entry("Unit After Override", "unit-after-override.container"),
|
||||||
Entry("NetworkAlias", "network-alias.container"),
|
Entry("NetworkAlias", "network-alias.container"),
|
||||||
Entry("CgroupMode", "cgroups-mode.container"),
|
Entry("CgroupMode", "cgroups-mode.container"),
|
||||||
|
Entry("Container - No Default Dependencies", "no_deps.container"),
|
||||||
|
|
||||||
Entry("basic.volume", "basic.volume"),
|
Entry("basic.volume", "basic.volume"),
|
||||||
Entry("device-copy.volume", "device-copy.volume"),
|
Entry("device-copy.volume", "device-copy.volume"),
|
||||||
|
@ -967,6 +979,7 @@ BOGUS=foo
|
||||||
Entry("Image - global args", "globalargs.image"),
|
Entry("Image - global args", "globalargs.image"),
|
||||||
Entry("Image - Containers Conf Modules", "containersconfmodule.image"),
|
Entry("Image - Containers Conf Modules", "containersconfmodule.image"),
|
||||||
Entry("Image - Unit After Override", "unit-after-override.image"),
|
Entry("Image - Unit After Override", "unit-after-override.image"),
|
||||||
|
Entry("Image - No Default Dependencies", "no_deps.image"),
|
||||||
|
|
||||||
Entry("Build - Basic", "basic.build"),
|
Entry("Build - Basic", "basic.build"),
|
||||||
Entry("Build - Annotation Key", "annotation.build"),
|
Entry("Build - Annotation Key", "annotation.build"),
|
||||||
|
@ -1000,6 +1013,7 @@ BOGUS=foo
|
||||||
Entry("Build - Target Key", "target.build"),
|
Entry("Build - Target Key", "target.build"),
|
||||||
Entry("Build - TLSVerify Key", "tls-verify.build"),
|
Entry("Build - TLSVerify Key", "tls-verify.build"),
|
||||||
Entry("Build - Variant Key", "variant.build"),
|
Entry("Build - Variant Key", "variant.build"),
|
||||||
|
Entry("Build - No Default Dependencies", "no_deps.build"),
|
||||||
|
|
||||||
Entry("Pod - Basic", "basic.pod"),
|
Entry("Pod - Basic", "basic.pod"),
|
||||||
Entry("Pod - DNS", "dns.pod"),
|
Entry("Pod - DNS", "dns.pod"),
|
||||||
|
|
Loading…
Reference in New Issue