Skip to content

Commit e498c65

Browse files
committed
Quadlet - translate dependencies on other quadlet units
Signed-off-by: Ygal Blum <[email protected]>
1 parent 48423a6 commit e498c65

File tree

12 files changed

+267
-13
lines changed

12 files changed

+267
-13
lines changed

cmd/quadlet/main.go

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,6 @@ var (
4343

4444
var (
4545
void struct{}
46-
// Key: Extension
47-
// Value: Processing order for resource naming dependencies
48-
supportedExtensions = map[string]int{
49-
".container": 4,
50-
".volume": 2,
51-
".kube": 4,
52-
".network": 2,
53-
".image": 1,
54-
".build": 3,
55-
".pod": 5,
56-
}
5746
)
5847

5948
// 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
312301

313302
func isExtSupported(filename string) bool {
314303
ext := filepath.Ext(filename)
315-
_, ok := supportedExtensions[ext]
304+
_, ok := quadlet.SupportedExtensions[ext]
316305
return ok
317306
}
318307

@@ -714,7 +703,7 @@ func process() bool {
714703
sort.Slice(units, func(i, j int) bool {
715704
getOrder := func(i int) int {
716705
ext := filepath.Ext(units[i].Filename)
717-
order, ok := supportedExtensions[ext]
706+
order, ok := quadlet.SupportedExtensions[ext]
718707
if !ok {
719708
return 0
720709
}

docs/source/markdown/podman-systemd.unit.5.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,22 @@ the `network-online.target` unit is active with `systemctl is-active network-onl
258258
This behavior can be disabled by adding `DefaultDependencies=false` in the `Quadlet` section.
259259
Note, the _systemd_ `[Unit]` section has an option with the same name but a different meaning.
260260

261+
### Dependency between Quadlet units
262+
263+
Quadlet will automatically translate dependencies, specified in the keys
264+
`Wants`, `Requires`, `Requisite`, `BindsTo`, `PartOf`, `Upholds`, `Conflicts`, `Before` and `After`
265+
of the `[Unit]` section, between different Quadlet units.
266+
267+
For example the `fedora.container` unit below specifies a dependency on the `basic.container` unit.
268+
```
269+
[Unit]
270+
After=basic.container
271+
Requires=basic.container
272+
273+
[Container]
274+
Image=registry.fedoraproject.org/fedora:41
275+
```
276+
261277
## Container units [Container]
262278

263279
Container units are named with a `.container` extension and contain a `[Container]` section describing

pkg/systemd/quadlet/quadlet.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,33 @@ type UnitInfo struct {
196196
}
197197

198198
var (
199+
// Key: Extension
200+
// Value: Processing order for resource naming dependencies
201+
SupportedExtensions = map[string]int{
202+
".container": 4,
203+
".volume": 2,
204+
".kube": 4,
205+
".network": 2,
206+
".image": 1,
207+
".build": 3,
208+
".pod": 5,
209+
}
210+
199211
URL = regexp.Delayed(`^((https?)|(git)://)|(github\.com/).+$`)
200212
validPortRange = regexp.Delayed(`\d+(-\d+)?(/udp|/tcp)?$`)
201213

214+
unitDependencyKeys = []string{
215+
"Wants",
216+
"Requires",
217+
"Requisite",
218+
"BindsTo",
219+
"PartOf",
220+
"Upholds",
221+
"Conflicts",
222+
"Before",
223+
"After",
224+
}
225+
202226
// Supported keys in "Container" group
203227
supportedContainerKeys = map[string]bool{
204228
KeyAddCapability: true,
@@ -514,6 +538,10 @@ func ConvertContainer(container *parser.UnitFile, isUser bool, unitsInfoMap map[
514538
service := container.Dup()
515539
service.Filename = unitInfo.ServiceFileName()
516540

541+
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
542+
return nil, warnings, err
543+
}
544+
517545
addDefaultDependencies(service, isUser)
518546

519547
if container.Path != "" {
@@ -923,6 +951,10 @@ func ConvertNetwork(network *parser.UnitFile, name string, unitsInfoMap map[stri
923951
service := network.Dup()
924952
service.Filename = unitInfo.ServiceFileName()
925953

954+
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
955+
return nil, warnings, err
956+
}
957+
926958
addDefaultDependencies(service, isUser)
927959

928960
if network.Path != "" {
@@ -1034,6 +1066,10 @@ func ConvertVolume(volume *parser.UnitFile, name string, unitsInfoMap map[string
10341066
service := volume.Dup()
10351067
service.Filename = unitInfo.ServiceFileName()
10361068

1069+
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
1070+
return nil, warnings, err
1071+
}
1072+
10371073
addDefaultDependencies(service, isUser)
10381074

10391075
if volume.Path != "" {
@@ -1173,6 +1209,10 @@ func ConvertKube(kube *parser.UnitFile, unitsInfoMap map[string]*UnitInfo, isUse
11731209
service := kube.Dup()
11741210
service.Filename = unitInfo.ServiceFileName()
11751211

1212+
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
1213+
return nil, err
1214+
}
1215+
11761216
addDefaultDependencies(service, isUser)
11771217

11781218
if kube.Path != "" {
@@ -1318,6 +1358,10 @@ func ConvertImage(image *parser.UnitFile, unitsInfoMap map[string]*UnitInfo, isU
13181358
service := image.Dup()
13191359
service.Filename = unitInfo.ServiceFileName()
13201360

1361+
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
1362+
return nil, err
1363+
}
1364+
13211365
addDefaultDependencies(service, isUser)
13221366

13231367
if image.Path != "" {
@@ -1399,6 +1443,10 @@ func ConvertBuild(build *parser.UnitFile, unitsInfoMap map[string]*UnitInfo, isU
13991443
service := build.Dup()
14001444
service.Filename = unitInfo.ServiceFileName()
14011445

1446+
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
1447+
return nil, warnings, err
1448+
}
1449+
14021450
addDefaultDependencies(service, isUser)
14031451

14041452
/* 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]*
15681616
service := podUnit.Dup()
15691617
service.Filename = unitInfo.ServiceFileName()
15701618

1619+
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
1620+
return nil, err
1621+
}
1622+
15711623
addDefaultDependencies(service, isUser)
15721624

15731625
if podUnit.Path != "" {
@@ -2260,3 +2312,36 @@ func handleExecReload(quadletUnitFile, serviceUnitFile *parser.UnitFile, groupNa
22602312

22612313
return nil
22622314
}
2315+
2316+
func translateUnitDependencies(serviceUnitFile *parser.UnitFile, unitsInfoMap map[string]*UnitInfo) error {
2317+
for _, unitDependencyKey := range unitDependencyKeys {
2318+
deps := serviceUnitFile.LookupAllStrv(UnitGroup, unitDependencyKey)
2319+
if len(deps) == 0 {
2320+
continue
2321+
}
2322+
translatedDeps := make([]string, 0, len(deps))
2323+
translated := false
2324+
for _, dep := range deps {
2325+
var translatedDep string
2326+
2327+
ext := filepath.Ext(dep)
2328+
if _, ok := SupportedExtensions[ext]; ok {
2329+
unitInfo, ok := unitsInfoMap[dep]
2330+
if !ok {
2331+
return fmt.Errorf("unable to translate dependency for %s", dep)
2332+
}
2333+
translatedDep = unitInfo.ServiceFileName()
2334+
translated = true
2335+
} else {
2336+
translatedDep = dep
2337+
}
2338+
translatedDeps = append(translatedDeps, translatedDep)
2339+
}
2340+
if !translated {
2341+
continue
2342+
}
2343+
serviceUnitFile.Unset(UnitGroup, unitDependencyKey)
2344+
serviceUnitFile.Add(UnitGroup, unitDependencyKey, strings.Join(translatedDeps, " "))
2345+
}
2346+
return nil
2347+
}

test/e2e/quadlet/dependent.build

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
## assert-key-is "Unit" "Requires" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
2+
## 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"
3+
4+
[Unit]
5+
After=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
6+
Requires=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
7+
8+
[Build]
9+
ImageTag=localhost/imagename
10+
SetWorkingDirectory=unit
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## assert-key-is "Unit" "Requires" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
2+
## 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"
3+
4+
[Unit]
5+
After=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
6+
Requires=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
7+
8+
[Container]
9+
Image=localhost/imagename
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## assert-failed
2+
## assert-stderr-contains "unable to translate dependency for basic.container"
3+
4+
[Unit]
5+
After=basic.container
6+
Requires=basic.container
7+
8+
[Container]
9+
Image=localhost/imagename

test/e2e/quadlet/dependent.image

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## assert-key-is "Unit" "Requires" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
2+
## 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"
3+
4+
[Unit]
5+
After=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
6+
Requires=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
7+
8+
[Image]
9+
Image=localhost/imagename

test/e2e/quadlet/dependent.kube

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## assert-key-is "Unit" "Requires" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
2+
## 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"
3+
4+
[Unit]
5+
After=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
6+
Requires=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
7+
8+
[Kube]
9+
Yaml=deployment.yml

test/e2e/quadlet/dependent.network

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## assert-key-is "Unit" "Requires" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
2+
## 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"
3+
4+
[Unit]
5+
After=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
6+
Requires=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
7+
8+
[Network]

test/e2e/quadlet/dependent.pod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## assert-key-is "Unit" "Requires" "basic-build.service basic.service basic-image.service basic.service basic-network.service basic-pod.service basic-volume.service"
2+
## 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"
3+
4+
[Unit]
5+
After=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
6+
Requires=basic.build basic.container basic.image basic.kube basic.network basic.pod basic.volume
7+
8+
[Pod]

0 commit comments

Comments
 (0)