mirror of
https://github.com/containers/podman.git
synced 2025-05-17 15:18:43 +08:00
Quadlet - translate dependencies on other quadlet units
Signed-off-by: Ygal Blum <ygal.blum@gmail.com>
This commit is contained in:
@ -43,17 +43,6 @@ var (
|
||||
|
||||
var (
|
||||
void struct{}
|
||||
// Key: Extension
|
||||
// Value: Processing order for resource naming dependencies
|
||||
supportedExtensions = map[string]int{
|
||||
".container": 4,
|
||||
".volume": 2,
|
||||
".kube": 4,
|
||||
".network": 2,
|
||||
".image": 1,
|
||||
".build": 3,
|
||||
".pod": 5,
|
||||
}
|
||||
)
|
||||
|
||||
// We log directly to /dev/kmsg, because that is the only way to get information out
|
||||
@ -312,7 +301,7 @@ func getUserLevelFilter(resolvedUnitDirAdminUser string) func(string, bool) bool
|
||||
|
||||
func isExtSupported(filename string) bool {
|
||||
ext := filepath.Ext(filename)
|
||||
_, ok := supportedExtensions[ext]
|
||||
_, ok := quadlet.SupportedExtensions[ext]
|
||||
return ok
|
||||
}
|
||||
|
||||
@ -714,7 +703,7 @@ func process() bool {
|
||||
sort.Slice(units, func(i, j int) bool {
|
||||
getOrder := func(i int) int {
|
||||
ext := filepath.Ext(units[i].Filename)
|
||||
order, ok := supportedExtensions[ext]
|
||||
order, ok := quadlet.SupportedExtensions[ext]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
@ -258,6 +258,22 @@ the `network-online.target` unit is active with `systemctl is-active network-onl
|
||||
This behavior can be disabled by adding `DefaultDependencies=false` in the `Quadlet` section.
|
||||
Note, the _systemd_ `[Unit]` section has an option with the same name but a different meaning.
|
||||
|
||||
### Dependency between Quadlet units
|
||||
|
||||
Quadlet will automatically translate dependencies, specified in the keys
|
||||
`Wants`, `Requires`, `Requisite`, `BindsTo`, `PartOf`, `Upholds`, `Conflicts`, `Before` and `After`
|
||||
of the `[Unit]` section, between different Quadlet units.
|
||||
|
||||
For example the `fedora.container` unit below specifies a dependency on the `basic.container` unit.
|
||||
```
|
||||
[Unit]
|
||||
After=basic.container
|
||||
Requires=basic.container
|
||||
|
||||
[Container]
|
||||
Image=registry.fedoraproject.org/fedora:41
|
||||
```
|
||||
|
||||
## Container units [Container]
|
||||
|
||||
Container units are named with a `.container` extension and contain a `[Container]` section describing
|
||||
|
@ -196,9 +196,33 @@ type UnitInfo struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// Key: Extension
|
||||
// Value: Processing order for resource naming dependencies
|
||||
SupportedExtensions = map[string]int{
|
||||
".container": 4,
|
||||
".volume": 2,
|
||||
".kube": 4,
|
||||
".network": 2,
|
||||
".image": 1,
|
||||
".build": 3,
|
||||
".pod": 5,
|
||||
}
|
||||
|
||||
URL = regexp.Delayed(`^((https?)|(git)://)|(github\.com/).+$`)
|
||||
validPortRange = regexp.Delayed(`\d+(-\d+)?(/udp|/tcp)?$`)
|
||||
|
||||
unitDependencyKeys = []string{
|
||||
"Wants",
|
||||
"Requires",
|
||||
"Requisite",
|
||||
"BindsTo",
|
||||
"PartOf",
|
||||
"Upholds",
|
||||
"Conflicts",
|
||||
"Before",
|
||||
"After",
|
||||
}
|
||||
|
||||
// Supported keys in "Container" group
|
||||
supportedContainerKeys = map[string]bool{
|
||||
KeyAddCapability: true,
|
||||
@ -514,6 +538,10 @@ func ConvertContainer(container *parser.UnitFile, isUser bool, unitsInfoMap map[
|
||||
service := container.Dup()
|
||||
service.Filename = unitInfo.ServiceFileName()
|
||||
|
||||
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
|
||||
return nil, warnings, err
|
||||
}
|
||||
|
||||
addDefaultDependencies(service, isUser)
|
||||
|
||||
if container.Path != "" {
|
||||
@ -923,6 +951,10 @@ func ConvertNetwork(network *parser.UnitFile, name string, unitsInfoMap map[stri
|
||||
service := network.Dup()
|
||||
service.Filename = unitInfo.ServiceFileName()
|
||||
|
||||
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
|
||||
return nil, warnings, err
|
||||
}
|
||||
|
||||
addDefaultDependencies(service, isUser)
|
||||
|
||||
if network.Path != "" {
|
||||
@ -1034,6 +1066,10 @@ func ConvertVolume(volume *parser.UnitFile, name string, unitsInfoMap map[string
|
||||
service := volume.Dup()
|
||||
service.Filename = unitInfo.ServiceFileName()
|
||||
|
||||
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
|
||||
return nil, warnings, err
|
||||
}
|
||||
|
||||
addDefaultDependencies(service, isUser)
|
||||
|
||||
if volume.Path != "" {
|
||||
@ -1173,6 +1209,10 @@ func ConvertKube(kube *parser.UnitFile, unitsInfoMap map[string]*UnitInfo, isUse
|
||||
service := kube.Dup()
|
||||
service.Filename = unitInfo.ServiceFileName()
|
||||
|
||||
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addDefaultDependencies(service, isUser)
|
||||
|
||||
if kube.Path != "" {
|
||||
@ -1318,6 +1358,10 @@ func ConvertImage(image *parser.UnitFile, unitsInfoMap map[string]*UnitInfo, isU
|
||||
service := image.Dup()
|
||||
service.Filename = unitInfo.ServiceFileName()
|
||||
|
||||
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addDefaultDependencies(service, isUser)
|
||||
|
||||
if image.Path != "" {
|
||||
@ -1399,6 +1443,10 @@ func ConvertBuild(build *parser.UnitFile, unitsInfoMap map[string]*UnitInfo, isU
|
||||
service := build.Dup()
|
||||
service.Filename = unitInfo.ServiceFileName()
|
||||
|
||||
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
|
||||
return nil, warnings, err
|
||||
}
|
||||
|
||||
addDefaultDependencies(service, isUser)
|
||||
|
||||
/* Rename old Build group to X-Build so that systemd ignores it */
|
||||
@ -1568,6 +1616,10 @@ func ConvertPod(podUnit *parser.UnitFile, name string, unitsInfoMap map[string]*
|
||||
service := podUnit.Dup()
|
||||
service.Filename = unitInfo.ServiceFileName()
|
||||
|
||||
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addDefaultDependencies(service, isUser)
|
||||
|
||||
if podUnit.Path != "" {
|
||||
@ -2260,3 +2312,36 @@ func handleExecReload(quadletUnitFile, serviceUnitFile *parser.UnitFile, groupNa
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func translateUnitDependencies(serviceUnitFile *parser.UnitFile, unitsInfoMap map[string]*UnitInfo) error {
|
||||
for _, unitDependencyKey := range unitDependencyKeys {
|
||||
deps := serviceUnitFile.LookupAllStrv(UnitGroup, unitDependencyKey)
|
||||
if len(deps) == 0 {
|
||||
continue
|
||||
}
|
||||
translatedDeps := make([]string, 0, len(deps))
|
||||
translated := false
|
||||
for _, dep := range deps {
|
||||
var translatedDep string
|
||||
|
||||
ext := filepath.Ext(dep)
|
||||
if _, ok := SupportedExtensions[ext]; ok {
|
||||
unitInfo, ok := unitsInfoMap[dep]
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to translate dependency for %s", dep)
|
||||
}
|
||||
translatedDep = unitInfo.ServiceFileName()
|
||||
translated = true
|
||||
} else {
|
||||
translatedDep = dep
|
||||
}
|
||||
translatedDeps = append(translatedDeps, translatedDep)
|
||||
}
|
||||
if !translated {
|
||||
continue
|
||||
}
|
||||
serviceUnitFile.Unset(UnitGroup, unitDependencyKey)
|
||||
serviceUnitFile.Add(UnitGroup, unitDependencyKey, strings.Join(translatedDeps, " "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
10
test/e2e/quadlet/dependent.build
Normal file
10
test/e2e/quadlet/dependent.build
Normal file
@ -0,0 +1,10 @@
|
||||
## assert-key-is "Unit" "Requires" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
|
||||
## assert-key-is-regex "Unit" "After" "network-online.target|podman-user-wait-network-online.service" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
|
||||
|
||||
[Unit]
|
||||
After=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
|
||||
Requires=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
|
||||
|
||||
[Build]
|
||||
ImageTag=localhost/imagename
|
||||
SetWorkingDirectory=unit
|
9
test/e2e/quadlet/dependent.container
Normal file
9
test/e2e/quadlet/dependent.container
Normal file
@ -0,0 +1,9 @@
|
||||
## assert-key-is "Unit" "Requires" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
|
||||
## assert-key-is-regex "Unit" "After" "network-online.target|podman-user-wait-network-online.service" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
|
||||
|
||||
[Unit]
|
||||
After=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
|
||||
Requires=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
|
||||
|
||||
[Container]
|
||||
Image=localhost/imagename
|
9
test/e2e/quadlet/dependent.error.container
Normal file
9
test/e2e/quadlet/dependent.error.container
Normal file
@ -0,0 +1,9 @@
|
||||
## assert-failed
|
||||
## assert-stderr-contains "unable to translate dependency for basic.container"
|
||||
|
||||
[Unit]
|
||||
After=basic.container
|
||||
Requires=basic.container
|
||||
|
||||
[Container]
|
||||
Image=localhost/imagename
|
9
test/e2e/quadlet/dependent.image
Normal file
9
test/e2e/quadlet/dependent.image
Normal file
@ -0,0 +1,9 @@
|
||||
## assert-key-is "Unit" "Requires" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
|
||||
## assert-key-is-regex "Unit" "After" "network-online.target|podman-user-wait-network-online.service" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
|
||||
|
||||
[Unit]
|
||||
After=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
|
||||
Requires=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
|
||||
|
||||
[Image]
|
||||
Image=localhost/imagename
|
9
test/e2e/quadlet/dependent.kube
Normal file
9
test/e2e/quadlet/dependent.kube
Normal file
@ -0,0 +1,9 @@
|
||||
## assert-key-is "Unit" "Requires" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
|
||||
## assert-key-is-regex "Unit" "After" "network-online.target|podman-user-wait-network-online.service" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
|
||||
|
||||
[Unit]
|
||||
After=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
|
||||
Requires=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
|
||||
|
||||
[Kube]
|
||||
Yaml=deployment.yml
|
8
test/e2e/quadlet/dependent.network
Normal file
8
test/e2e/quadlet/dependent.network
Normal file
@ -0,0 +1,8 @@
|
||||
## assert-key-is "Unit" "Requires" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
|
||||
## assert-key-is-regex "Unit" "After" "network-online.target|podman-user-wait-network-online.service" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
|
||||
|
||||
[Unit]
|
||||
After=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
|
||||
Requires=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
|
||||
|
||||
[Network]
|
8
test/e2e/quadlet/dependent.pod
Normal file
8
test/e2e/quadlet/dependent.pod
Normal file
@ -0,0 +1,8 @@
|
||||
## assert-key-is "Unit" "Requires" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
|
||||
## assert-key-is-regex "Unit" "After" "network-online.target|podman-user-wait-network-online.service" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
|
||||
|
||||
[Unit]
|
||||
After=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
|
||||
Requires=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
|
||||
|
||||
[Pod]
|
8
test/e2e/quadlet/dependent.volume
Normal file
8
test/e2e/quadlet/dependent.volume
Normal file
@ -0,0 +1,8 @@
|
||||
## assert-key-is "Unit" "Requires" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
|
||||
## assert-key-is-regex "Unit" "After" "network-online.target|podman-user-wait-network-online.service" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
|
||||
|
||||
[Unit]
|
||||
After=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
|
||||
Requires=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
|
||||
|
||||
[Volume]
|
@ -1102,6 +1102,7 @@ BOGUS=foo
|
||||
Entry("subidmapping-with-remap.container", "subidmapping-with-remap.container", "converting \"subidmapping-with-remap.container\": deprecated Remap keys are set along with explicit mapping keys"),
|
||||
Entry("userns-with-remap.container", "userns-with-remap.container", "converting \"userns-with-remap.container\": deprecated Remap keys are set along with explicit mapping keys"),
|
||||
Entry("reloadboth.container", "reloadboth.container", "converting \"reloadboth.container\": ReloadCmd and ReloadSignal are mutually exclusive but both are set"),
|
||||
Entry("dependent.error.container", "dependent.error.container", "converting \"dependent.error.container\": unable to translate dependency for basic.container"),
|
||||
|
||||
Entry("image-no-image.volume", "image-no-image.volume", "converting \"image-no-image.volume\": the key Image is mandatory when using the image driver"),
|
||||
Entry("Volume - Quadlet image (.build) not found", "build-not-found.quadlet.volume", "converting \"build-not-found.quadlet.volume\": requested Quadlet image not-found.build was not found"),
|
||||
@ -1157,26 +1158,119 @@ BOGUS=foo
|
||||
Entry("Container - Reuse another named container's network", "network.reuse.name.container", []string{"name.container"}),
|
||||
Entry("Container - Reuse another container's network", "a.network.reuse.container", []string{"basic.container"}),
|
||||
Entry("Container - Reuse another named container's network", "a.network.reuse.name.container", []string{"name.container"}),
|
||||
Entry(
|
||||
"Container - Dependency between quadlet units",
|
||||
"dependent.container",
|
||||
[]string{
|
||||
"basic.build",
|
||||
"basic.container",
|
||||
"basic.image",
|
||||
"basic.kube",
|
||||
"basic.network",
|
||||
"basic.pod",
|
||||
"basic.volume",
|
||||
},
|
||||
),
|
||||
|
||||
Entry("Volume - Quadlet image (.build)", "build.quadlet.volume", []string{"basic.build"}),
|
||||
Entry("Volume - Quadlet image (.image)", "image.quadlet.volume", []string{"basic.image"}),
|
||||
Entry("Volume - Quadlet image (.build) overriding service name", "build.quadlet.servicename.volume", []string{"service-name.build"}),
|
||||
Entry("Volume - Quadlet image (.image) overriding service name", "image.quadlet.servicename.volume", []string{"service-name.image"}),
|
||||
Entry(
|
||||
"Volume - Dependency between quadlet units",
|
||||
"dependent.volume",
|
||||
[]string{
|
||||
"basic.build",
|
||||
"basic.container",
|
||||
"basic.image",
|
||||
"basic.kube",
|
||||
"basic.network",
|
||||
"basic.pod",
|
||||
"basic.volume",
|
||||
},
|
||||
),
|
||||
|
||||
Entry("Kube - Quadlet Network", "network.quadlet.kube", []string{"basic.network"}),
|
||||
Entry("Kube - Quadlet Network overriding service name", "network.quadlet.servicename.kube", []string{"service-name.network"}),
|
||||
Entry(
|
||||
"Kube - Dependency between quadlet units",
|
||||
"dependent.kube",
|
||||
[]string{
|
||||
"basic.build",
|
||||
"basic.container",
|
||||
"basic.image",
|
||||
"basic.kube",
|
||||
"basic.network",
|
||||
"basic.pod",
|
||||
"basic.volume",
|
||||
},
|
||||
),
|
||||
|
||||
Entry("Build - Network Key quadlet", "network.quadlet.build", []string{"basic.network"}),
|
||||
Entry("Build - Volume Key", "volume.build", []string{"basic.volume"}),
|
||||
Entry("Build - Volume Key quadlet", "volume.quadlet.build", []string{"basic.volume"}),
|
||||
Entry("Build - Network Key quadlet overriding service name", "network.quadlet.servicename.build", []string{"service-name.network"}),
|
||||
Entry("Build - Volume Key quadlet overriding service name", "volume.quadlet.servicename.build", []string{"service-name.volume"}),
|
||||
Entry(
|
||||
"Build - Dependency between quadlet units",
|
||||
"dependent.build",
|
||||
[]string{
|
||||
"basic.build",
|
||||
"basic.container",
|
||||
"basic.image",
|
||||
"basic.kube",
|
||||
"basic.network",
|
||||
"basic.pod",
|
||||
"basic.volume",
|
||||
},
|
||||
),
|
||||
|
||||
Entry("Pod - Quadlet Network", "network.quadlet.pod", []string{"basic.network"}),
|
||||
Entry("Pod - Quadlet Volume", "volume.pod", []string{"basic.volume"}),
|
||||
Entry("Pod - Quadlet Network overriding service name", "network.servicename.quadlet.pod", []string{"service-name.network"}),
|
||||
Entry("Pod - Quadlet Volume overriding service name", "volume.servicename.pod", []string{"service-name.volume"}),
|
||||
Entry("Pod - Do not autostart a container with pod", "startwithpod.pod", []string{"startwithpod_no.container", "startwithpod_yes.container"}),
|
||||
Entry(
|
||||
"Pod - Dependency between quadlet units",
|
||||
"dependent.pod",
|
||||
[]string{
|
||||
"basic.build",
|
||||
"basic.container",
|
||||
"basic.image",
|
||||
"basic.kube",
|
||||
"basic.network",
|
||||
"basic.pod",
|
||||
"basic.volume",
|
||||
},
|
||||
),
|
||||
|
||||
Entry(
|
||||
"Image - Dependency between quadlet units",
|
||||
"dependent.image",
|
||||
[]string{
|
||||
"basic.build",
|
||||
"basic.container",
|
||||
"basic.image",
|
||||
"basic.kube",
|
||||
"basic.network",
|
||||
"basic.pod",
|
||||
"basic.volume",
|
||||
},
|
||||
),
|
||||
|
||||
Entry(
|
||||
"Network - Dependency between quadlet units",
|
||||
"dependent.network",
|
||||
[]string{
|
||||
"basic.build",
|
||||
"basic.container",
|
||||
"basic.image",
|
||||
"basic.kube",
|
||||
"basic.network",
|
||||
"basic.pod",
|
||||
"basic.volume",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
})
|
||||
|
Reference in New Issue
Block a user