Skip to content

Commit

Permalink
Merge pull request #20828 from alexlarsson/quadlet-snippets
Browse files Browse the repository at this point in the history
quadlet: Support systemd style dropin files
  • Loading branch information
openshift-merge-bot[bot] authored Nov 29, 2023
2 parents 385f852 + 8ee2622 commit 572a769
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 2 deletions.
67 changes: 67 additions & 0 deletions cmd/quadlet/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,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 @@ -474,6 +535,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 @@ -45,6 +45,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 @@ -726,6 +726,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 @@ -812,6 +822,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

0 comments on commit 572a769

Please sign in to comment.