Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

quadlet: Support systemd style dropin files #20828

Merged
merged 1 commit into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions cmd/quadlet/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,67 @@ func loadUnitsFromDir(sourcePath string) ([]*parser.UnitFile, error) {
return units, prevError
}

func loadUnitDropins(unit *parser.UnitFile, sourcePaths []string) error {
var prevError error
reportError := func(err error) {
if prevError != nil {
err = fmt.Errorf("%s\n%s", prevError, err)
}
prevError = err
}

var dropinPaths = make(map[string]string)
for _, sourcePath := range sourcePaths {
dropinDir := path.Join(sourcePath, unit.Filename+".d")

dropinFiles, err := os.ReadDir(dropinDir)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
reportError(fmt.Errorf("error reading directory %q, %w", dropinDir, err))
}

continue
}

for _, dropinFile := range dropinFiles {
dropinName := dropinFile.Name()
if filepath.Ext(dropinName) != ".conf" {
continue // Only *.conf supported
}

if _, ok := dropinPaths[dropinName]; ok {
continue // We already saw this name
}

dropinPaths[dropinName] = path.Join(dropinDir, dropinName)
}
}

dropinFiles := make([]string, len(dropinPaths))
i := 0
for k := range dropinPaths {
dropinFiles[i] = k
i++
}

// Merge in alpha-numerical order
sort.Strings(dropinFiles)

for _, dropinFile := range dropinFiles {
dropinPath := dropinPaths[dropinFile]

Debugf("Loading source drop-in file %s", dropinPath)

if f, err := parser.ParseUnitFile(dropinPath); err != nil {
reportError(fmt.Errorf("error loading %q, %w", dropinPath, err))
} else {
unit.Merge(f)
}
}

return prevError
}

func generateServiceFile(service *parser.UnitFile) error {
Debugf("writing %q", service.Path)

Expand Down Expand Up @@ -456,6 +517,12 @@ func process() error {
return prevError
}

for _, unit := range units {
if err := loadUnitDropins(unit, sourcePaths); err != nil {
reportError(err)
}
}

if !dryRunFlag {
err := os.MkdirAll(outputPath, os.ModePerm)
if err != nil {
Expand Down
7 changes: 7 additions & 0 deletions docs/source/markdown/podman-systemd.unit.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ Each file type has a custom section (for example, `[Container]`) that is handled
other sections are passed on untouched, allowing the use of any normal systemd configuration options
like dependencies or cgroup limits.

The source files also support drop-ins in the same [way systemd does](https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html).
For a given source file (say `foo.container`), the corresponding `.d`directory (in this
case `foo.container.d`) will be scanned for files with a `.conf` extension that are merged into
the base file in alphabetical order. The format of these drop-in files is the same as the base file.
This is useful to alter or add configuration settings for a unit, without having to modify unit
files.

For rootless containers, when administrators place Quadlet files in the
/etc/containers/systemd/users directory, all users' sessions execute the
Quadlet when the login session begins. If the administrator places a Quadlet
Expand Down
4 changes: 2 additions & 2 deletions pkg/systemd/parser/unitfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (f *UnitFile) ensureGroup(groupName string) *unitGroup {
return g
}

func (f *UnitFile) merge(source *UnitFile) {
func (f *UnitFile) Merge(source *UnitFile) {
for _, srcGroup := range source.groups {
group := f.ensureGroup(srcGroup.name)
group.merge(srcGroup)
Expand All @@ -193,7 +193,7 @@ func (f *UnitFile) merge(source *UnitFile) {
func (f *UnitFile) Dup() *UnitFile {
copy := NewUnitFile()

copy.merge(f)
copy.Merge(f)
copy.Filename = f.Filename
return copy
}
Expand Down
8 changes: 8 additions & 0 deletions test/e2e/quadlet/merged-override.container
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## assert-podman-final-args localhost/imagename
## !assert-podman-args --env "MAIN=mainvalue"
## !assert-podman-args --env "FIRST=value"
## assert-podman-args --env "SECOND=othervalue"

[Container]
Image=localhost/imagename
Environment=MAIN=mainvalue
2 changes: 2 additions & 0 deletions test/e2e/quadlet/merged-override.container.d/10-first.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[Container]
Environment=FIRST=value
4 changes: 4 additions & 0 deletions test/e2e/quadlet/merged-override.container.d/20-second.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[Container]
# Empty previous
Environment=
Environment=SECOND=othervalue
8 changes: 8 additions & 0 deletions test/e2e/quadlet/merged.container
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## assert-podman-final-args localhost/imagename
## assert-podman-args --env "MAIN=mainvalue"
## assert-podman-args --env "FIRST=value"
## assert-podman-args --env "SECOND=othervalue"

[Container]
Image=localhost/imagename
Environment=MAIN=mainvalue
2 changes: 2 additions & 0 deletions test/e2e/quadlet/merged.container.d/10-first.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[Container]
Environment=FIRST=value
2 changes: 2 additions & 0 deletions test/e2e/quadlet/merged.container.d/20-second.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[Container]
Environment=SECOND=othervalue
12 changes: 12 additions & 0 deletions test/e2e/quadlet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,16 @@ BOGUS=foo
err = os.WriteFile(filepath.Join(quadletDir, fileName), testcase.data, 0644)
Expect(err).ToNot(HaveOccurred())

// Also copy any extra snippets
dotdDir := filepath.Join("quadlet", fileName+".d")
if s, err := os.Stat(dotdDir); err == nil && s.IsDir() {
dotdDirDest := filepath.Join(quadletDir, fileName+".d")
err = os.Mkdir(dotdDirDest, os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = CopyDirectory(dotdDir, dotdDirDest)
Expect(err).ToNot(HaveOccurred())
}

// Run quadlet to convert the file
session := podmanTest.Quadlet([]string{"--user", "--no-kmsg-log", generatedDir}, quadletDir)
session.WaitWithDefaultTimeout()
Expand Down Expand Up @@ -748,6 +758,8 @@ BOGUS=foo
Entry("workingdir.container", "workingdir.container", 0, ""),
Entry("Container - global args", "globalargs.container", 0, ""),
Entry("Container - Containers Conf Modules", "containersconfmodule.container", 0, ""),
Entry("merged.container", "merged.container", 0, ""),
Entry("merged-override.container", "merged-override.container", 0, ""),

Entry("basic.volume", "basic.volume", 0, ""),
Entry("device-copy.volume", "device-copy.volume", 0, ""),
Expand Down