Skip to content

Commit 17f3aed

Browse files
Merge pull request #25899 from ygalblum/quadlet-unit-deps
Quadlet - translate dependencies on other quadlet units
2 parents fd5ac51 + e498c65 commit 17f3aed

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
@@ -197,9 +197,33 @@ type UnitInfo struct {
197197
}
198198

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

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

543+
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
544+
return nil, warnings, err
545+
}
546+
519547
addDefaultDependencies(service, isUser)
520548

521549
if container.Path != "" {
@@ -925,6 +953,10 @@ func ConvertNetwork(network *parser.UnitFile, name string, unitsInfoMap map[stri
925953
service := network.Dup()
926954
service.Filename = unitInfo.ServiceFileName()
927955

956+
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
957+
return nil, warnings, err
958+
}
959+
928960
addDefaultDependencies(service, isUser)
929961

930962
if network.Path != "" {
@@ -1042,6 +1074,10 @@ func ConvertVolume(volume *parser.UnitFile, name string, unitsInfoMap map[string
10421074
service := volume.Dup()
10431075
service.Filename = unitInfo.ServiceFileName()
10441076

1077+
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
1078+
return nil, warnings, err
1079+
}
1080+
10451081
addDefaultDependencies(service, isUser)
10461082

10471083
if volume.Path != "" {
@@ -1181,6 +1217,10 @@ func ConvertKube(kube *parser.UnitFile, unitsInfoMap map[string]*UnitInfo, isUse
11811217
service := kube.Dup()
11821218
service.Filename = unitInfo.ServiceFileName()
11831219

1220+
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
1221+
return nil, err
1222+
}
1223+
11841224
addDefaultDependencies(service, isUser)
11851225

11861226
if kube.Path != "" {
@@ -1326,6 +1366,10 @@ func ConvertImage(image *parser.UnitFile, unitsInfoMap map[string]*UnitInfo, isU
13261366
service := image.Dup()
13271367
service.Filename = unitInfo.ServiceFileName()
13281368

1369+
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
1370+
return nil, err
1371+
}
1372+
13291373
addDefaultDependencies(service, isUser)
13301374

13311375
if image.Path != "" {
@@ -1407,6 +1451,10 @@ func ConvertBuild(build *parser.UnitFile, unitsInfoMap map[string]*UnitInfo, isU
14071451
service := build.Dup()
14081452
service.Filename = unitInfo.ServiceFileName()
14091453

1454+
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
1455+
return nil, warnings, err
1456+
}
1457+
14101458
addDefaultDependencies(service, isUser)
14111459

14121460
/* Rename old Build group to X-Build so that systemd ignores it */
@@ -1576,6 +1624,10 @@ func ConvertPod(podUnit *parser.UnitFile, name string, unitsInfoMap map[string]*
15761624
service := podUnit.Dup()
15771625
service.Filename = unitInfo.ServiceFileName()
15781626

1627+
if err := translateUnitDependencies(service, unitsInfoMap); err != nil {
1628+
return nil, err
1629+
}
1630+
15791631
addDefaultDependencies(service, isUser)
15801632

15811633
if podUnit.Path != "" {
@@ -2268,3 +2320,36 @@ func handleExecReload(quadletUnitFile, serviceUnitFile *parser.UnitFile, groupNa
22682320

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

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)