mirror of https://github.com/containers/podman.git
quadlet: Support systemd style dropin files
For a source file like `foo.container`, look for drop in named `foo.container.d/*.conf` and merged them into the main file. The dropins are applied in alphabetical order, and files in earlier diretories override later files with same name. This is similar to how systemd dropins work, see: https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html Also adds some tests for these Signed-off-by: Alexander Larsson <alexl@redhat.com>
This commit is contained in:
parent
8387d2dfaa
commit
8ee2622028
|
|
@ -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)
|
||||
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
[Container]
|
||||
Environment=FIRST=value
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
[Container]
|
||||
# Empty previous
|
||||
Environment=
|
||||
Environment=SECOND=othervalue
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
[Container]
|
||||
Environment=FIRST=value
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
[Container]
|
||||
Environment=SECOND=othervalue
|
||||
|
|
@ -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()
|
||||
|
|
@ -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, ""),
|
||||
|
|
|
|||
Loading…
Reference in New Issue