Merge pull request #22057 from jbtrystram/quadlet-image-network

quadlet: Add a network requirement on .image and .containers units
This commit is contained in:
openshift-merge-bot[bot]
2024-05-23 06:19:07 +00:00
committed by GitHub
10 changed files with 79 additions and 2 deletions

View File

@@ -1333,6 +1333,11 @@ exists on the host, pulling it if needed.
Using image units allows containers and volumes to depend on images being automatically pulled. This is Using image units allows containers and volumes to depend on images being automatically pulled. This is
particularly interesting when using special options to control image pulls. particularly interesting when using special options to control image pulls.
Note: The generated service have a dependency on `network-online.target` assuring the network is reachable if
an image needs to be pulled.
If the image service needs to run without available network (e.g. early in boot), the requirement can be
overriden simply by adding an empty `After=` in the unit file. This will unset all previously set After's.
Valid options for `[Image]` are listed below: Valid options for `[Image]` are listed below:
| **[Image] options** | **podman image pull equivalent** | | **[Image] options** | **podman image pull equivalent** |

View File

@@ -90,6 +90,11 @@ func (g *unitGroup) addLine(line *unitLine) {
g.lines = append(g.lines, line) g.lines = append(g.lines, line)
} }
func (g *unitGroup) prependLine(line *unitLine) {
n := []*unitLine{line}
g.lines = append(n, g.lines...)
}
func (g *unitGroup) addComment(line *unitLine) { func (g *unitGroup) addComment(line *unitLine) {
g.comments = append(g.comments, line) g.comments = append(g.comments, line)
} }
@@ -923,6 +928,17 @@ func (f *UnitFile) PrependComment(groupName string, comments ...string) {
} }
} }
func (f *UnitFile) PrependUnitLine(groupName string, key string, value string) {
var group *unitGroup
if groupName == "" && len(f.groups) > 0 {
group = f.groups[0]
} else {
// Uses magic "" for first comment-only group if no other groups
group = f.ensureGroup(groupName)
}
group.prependLine(newUnitLine(key, value, false))
}
func (f *UnitFile) GetTemplateParts() (string, string) { func (f *UnitFile) GetTemplateParts() (string, string) {
ext := filepath.Ext(f.Filename) ext := filepath.Ext(f.Filename)
basename := strings.TrimSuffix(f.Filename, ext) basename := strings.TrimSuffix(f.Filename, ext)

View File

@@ -411,6 +411,14 @@ func ConvertContainer(container *parser.UnitFile, names map[string]string, isUse
service := container.Dup() service := container.Dup()
service.Filename = replaceExtension(container.Filename, ".service", "", "") service.Filename = replaceExtension(container.Filename, ".service", "", "")
// Add a dependency on network-online.target so the image pull does not happen
// before network is ready
// https://github.com/containers/podman/issues/21873
// Prepend the lines, so the user-provided values
// override the default ones.
service.PrependUnitLine(UnitGroup, "After", "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)
} }
@@ -1182,6 +1190,14 @@ func ConvertImage(image *parser.UnitFile) (*parser.UnitFile, string, error) {
service := image.Dup() service := image.Dup()
service.Filename = replaceExtension(image.Filename, ".service", "", "-image") service.Filename = replaceExtension(image.Filename, ".service", "", "-image")
// Add a dependency on network-online.target so the image pull does not happen
// before network is ready
// https://github.com/containers/podman/issues/21873
// Prepend the lines, so the user-provided values
// override the default ones.
service.PrependUnitLine(UnitGroup, "After", "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)
} }

View File

@@ -15,6 +15,8 @@
## assert-key-is-regex "Service" "ExecStopPost" "-[/S].*/podman rm -v -f -i --cidfile=%t/%N.cid" ## assert-key-is-regex "Service" "ExecStopPost" "-[/S].*/podman rm -v -f -i --cidfile=%t/%N.cid"
## assert-key-is-regex "Service" "ExecStop" ".*/podman rm -v -f -i --cidfile=%t/%N.cid" ## assert-key-is-regex "Service" "ExecStop" ".*/podman rm -v -f -i --cidfile=%t/%N.cid"
## assert-key-is "Service" "Environment" "PODMAN_SYSTEMD_UNIT=%n" ## assert-key-is "Service" "Environment" "PODMAN_SYSTEMD_UNIT=%n"
## assert-key-is "Unit" "After" "network-online.target"
## assert-key-is "Unit" "Wants" "network-online.target"
[Container] [Container]
Image=localhost/imagename Image=localhost/imagename

View File

@@ -1,4 +1,6 @@
## assert-podman-final-args localhost/imagename ## assert-podman-final-args localhost/imagename
## assert-key-is "Unit" "After" "network-online.target"
## assert-key-is "Unit" "Wants" "network-online.target"
## assert-key-is "Unit" "RequiresMountsFor" "%t/containers" ## assert-key-is "Unit" "RequiresMountsFor" "%t/containers"
## assert-key-is "Service" "Type" "oneshot" ## assert-key-is "Service" "Type" "oneshot"
## assert-key-is "Service" "RemainAfterExit" "yes" ## assert-key-is "Service" "RemainAfterExit" "yes"

View File

@@ -10,7 +10,7 @@ Mount=type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared,U=true
Mount=type=volume,source=vol1,destination=/path/in/container,ro=true Mount=type=volume,source=vol1,destination=/path/in/container,ro=true
## assert-podman-args-key-val "--mount" "," "type=volume,source=systemd-vol2,destination=/path/in/container,ro=true" ## assert-podman-args-key-val "--mount" "," "type=volume,source=systemd-vol2,destination=/path/in/container,ro=true"
## assert-key-is "Unit" "Requires" "vol2-volume.service" ## assert-key-is "Unit" "Requires" "vol2-volume.service"
## assert-key-is "Unit" "After" "vol2-volume.service" ## assert-key-is "Unit" "After" "network-online.target" "vol2-volume.service"
Mount=type=volume,source=vol2.volume,destination=/path/in/container,ro=true Mount=type=volume,source=vol2.volume,destination=/path/in/container,ro=true
## assert-podman-args-key-val "--mount" "," "type=tmpfs,tmpfs-size=512M,destination=/path/in/container" ## assert-podman-args-key-val "--mount" "," "type=tmpfs,tmpfs-size=512M,destination=/path/in/container"
Mount=type=tmpfs,tmpfs-size=512M,destination=/path/in/container Mount=type=tmpfs,tmpfs-size=512M,destination=/path/in/container

View File

@@ -1,6 +1,6 @@
## assert-podman-args "--network=systemd-basic" ## assert-podman-args "--network=systemd-basic"
## assert-key-is "Unit" "Requires" "basic-network.service" ## assert-key-is "Unit" "Requires" "basic-network.service"
## assert-key-is "Unit" "After" "basic-network.service" ## assert-key-is "Unit" "After" "network-online.target" "basic-network.service"
[Container] [Container]
Image=localhost/imagename Image=localhost/imagename

View File

@@ -0,0 +1,7 @@
## assert-last-key-is-regex "Unit" "After" "^$"
[Unit]
After=
[Container]
Image=localhost/imagename

View File

@@ -0,0 +1,7 @@
## assert-last-key-is-regex "Unit" "After" "^$"
[Unit]
After=
[Image]
Image=localhost/imagename

View File

@@ -172,6 +172,24 @@ func (t *quadletTestcase) assertKeyIsRegex(args []string, unit *parser.UnitFile)
return true return true
} }
func (t *quadletTestcase) assertLastKeyIsRegex(args []string, unit *parser.UnitFile) bool {
Expect(len(args)).To(BeNumerically(">=", 3))
group := args[0]
key := args[1]
regex := args[2]
value, ok := unit.LookupLast(group, key)
if !ok {
return false
}
matched, err := regexp.MatchString(regex, value)
if err != nil || !matched {
return false
}
return true
}
func (t *quadletTestcase) assertKeyContains(args []string, unit *parser.UnitFile) bool { func (t *quadletTestcase) assertKeyContains(args []string, unit *parser.UnitFile) bool {
Expect(args).To(HaveLen(3)) Expect(args).To(HaveLen(3))
group := args[0] group := args[0]
@@ -469,6 +487,8 @@ func (t *quadletTestcase) doAssert(check []string, unit *parser.UnitFile, sessio
ok = t.assertKeyIsRegex(args, unit) ok = t.assertKeyIsRegex(args, unit)
case "assert-key-contains": case "assert-key-contains":
ok = t.assertKeyContains(args, unit) ok = t.assertKeyContains(args, unit)
case "assert-last-key-is-regex":
ok = t.assertLastKeyIsRegex(args, unit)
case "assert-podman-args": case "assert-podman-args":
ok = t.assertStartPodmanArgs(args, unit) ok = t.assertStartPodmanArgs(args, unit)
case "assert-podman-args-regex": case "assert-podman-args-regex":
@@ -847,6 +867,7 @@ BOGUS=foo
Entry("merged-override.container", "merged-override.container", 0, ""), Entry("merged-override.container", "merged-override.container", 0, ""),
Entry("template@.container", "template@.container", 0, ""), Entry("template@.container", "template@.container", 0, ""),
Entry("template@instance.container", "template@instance.container", 0, ""), Entry("template@instance.container", "template@instance.container", 0, ""),
Entry("Unit After Override", "unit-after-override.container", 0, ""),
Entry("basic.volume", "basic.volume", 0, ""), Entry("basic.volume", "basic.volume", 0, ""),
Entry("device-copy.volume", "device-copy.volume", 0, ""), Entry("device-copy.volume", "device-copy.volume", 0, ""),
@@ -921,6 +942,7 @@ BOGUS=foo
Entry("Image - Arch and OS", "arch-os.image", 0, ""), Entry("Image - Arch and OS", "arch-os.image", 0, ""),
Entry("Image - global args", "globalargs.image", 0, ""), Entry("Image - global args", "globalargs.image", 0, ""),
Entry("Image - Containers Conf Modules", "containersconfmodule.image", 0, ""), Entry("Image - Containers Conf Modules", "containersconfmodule.image", 0, ""),
Entry("Image - Unit After Override", "unit-after-override.image", 0, ""),
Entry("basic.pod", "basic.pod", 0, ""), Entry("basic.pod", "basic.pod", 0, ""),
Entry("name.pod", "name.pod", 0, ""), Entry("name.pod", "name.pod", 0, ""),