quadlet recursively scan for unit files

Signed-off-by: Hari Kannan <harikannan512@gmail.com>
This commit is contained in:
Hari Kannan
2023-07-20 23:10:28 +01:00
parent 4315611ad2
commit 413552e10e
8 changed files with 222 additions and 16 deletions

View File

@ -8,6 +8,7 @@ import (
"os/user" "os/user"
"path" "path"
"path/filepath" "path/filepath"
"regexp"
"sort" "sort"
"strings" "strings"
"unicode" "unicode"
@ -33,6 +34,10 @@ var (
versionFlag bool // True if -version is used versionFlag bool // True if -version is used
) )
const (
SystemUserDirLevel = 5
)
var ( var (
// data saved between logToKmsg calls // data saved between logToKmsg calls
noKmsg = false noKmsg = false
@ -103,28 +108,84 @@ func Debugf(format string, a ...interface{}) {
func getUnitDirs(rootless bool) []string { func getUnitDirs(rootless bool) []string {
// Allow overriding source dir, this is mainly for the CI tests // Allow overriding source dir, this is mainly for the CI tests
unitDirsEnv := os.Getenv("QUADLET_UNIT_DIRS") unitDirsEnv := os.Getenv("QUADLET_UNIT_DIRS")
dirs := make([]string, 0)
if len(unitDirsEnv) > 0 { if len(unitDirsEnv) > 0 {
return strings.Split(unitDirsEnv, ":") for _, eachUnitDir := range strings.Split(unitDirsEnv, ":") {
if !filepath.IsAbs(eachUnitDir) {
Logf("%s not a valid file path", eachUnitDir)
return nil
}
dirs = appendSubPaths(dirs, eachUnitDir, false, nil)
}
return dirs
} }
dirs := make([]string, 0)
if rootless { if rootless {
configDir, err := os.UserConfigDir() configDir, err := os.UserConfigDir()
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Warning: %v", err) fmt.Fprintf(os.Stderr, "Warning: %v", err)
return nil return nil
} }
dirs = append(dirs, path.Join(configDir, "containers/systemd")) dirs = appendSubPaths(dirs, path.Join(configDir, "containers/systemd"), false, nil)
u, err := user.Current() u, err := user.Current()
if err == nil { if err == nil {
dirs = append(dirs, filepath.Join(quadlet.UnitDirAdmin, "users", u.Uid)) dirs = appendSubPaths(dirs, filepath.Join(quadlet.UnitDirAdmin, "users"), true, nonNumericFilter)
dirs = appendSubPaths(dirs, filepath.Join(quadlet.UnitDirAdmin, "users", u.Uid), true, userLevelFilter)
} else { } else {
fmt.Fprintf(os.Stderr, "Warning: %v", err) fmt.Fprintf(os.Stderr, "Warning: %v", err)
} }
return append(dirs, filepath.Join(quadlet.UnitDirAdmin, "users")) return append(dirs, filepath.Join(quadlet.UnitDirAdmin, "users"))
} }
dirs = append(dirs, quadlet.UnitDirAdmin)
return append(dirs, quadlet.UnitDirDistro) dirs = appendSubPaths(dirs, quadlet.UnitDirAdmin, false, userLevelFilter)
return appendSubPaths(dirs, quadlet.UnitDirDistro, false, nil)
}
func appendSubPaths(dirs []string, path string, isUserFlag bool, filterPtr func(string, bool) bool) []string {
err := filepath.WalkDir(path, func(_path string, info os.DirEntry, err error) error {
if info == nil || info.IsDir() {
if filterPtr == nil || filterPtr(_path, isUserFlag) {
dirs = append(dirs, _path)
}
}
return err
})
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
Debugf("Error occurred walking sub directories %q: %s", path, err)
}
}
return dirs
}
func nonNumericFilter(_path string, isUserFlag bool) bool {
// when running in rootless, only recrusive walk directories that are non numeric
// ignore sub dirs under the user directory that may correspond to a user id
if strings.Contains(_path, filepath.Join(quadlet.UnitDirAdmin, "users")) {
listDirUserPathLevels := strings.Split(_path, string(os.PathSeparator))
if len(listDirUserPathLevels) > SystemUserDirLevel {
if !(regexp.MustCompile(`^[0-9]*$`).MatchString(listDirUserPathLevels[SystemUserDirLevel])) {
return true
}
}
} else {
return true
}
return false
}
func userLevelFilter(_path string, isUserFlag bool) bool {
// if quadlet generator is run rootless, do not recurse other user sub dirs
// if quadlet generator is run as root, ignore users sub dirs
if strings.Contains(_path, filepath.Join(quadlet.UnitDirAdmin, "users")) {
if isUserFlag {
return true
}
} else {
return true
}
return false
} }
func isExtSupported(filename string) bool { func isExtSupported(filename string) bool {

View File

@ -47,10 +47,9 @@ func TestIsUnambiguousName(t *testing.T) {
} }
func TestUnitDirs(t *testing.T) { func TestUnitDirs(t *testing.T) {
rootDirs := []string{ rootDirs := []string{}
quadlet.UnitDirAdmin, rootDirs = appendSubPaths(rootDirs, quadlet.UnitDirAdmin, false, userLevelFilter)
quadlet.UnitDirDistro, rootDirs = appendSubPaths(rootDirs, quadlet.UnitDirDistro, false, userLevelFilter)
}
unitDirs := getUnitDirs(false) unitDirs := getUnitDirs(false)
assert.Equal(t, unitDirs, rootDirs, "rootful unit dirs should match") assert.Equal(t, unitDirs, rootDirs, "rootful unit dirs should match")
@ -59,11 +58,12 @@ func TestUnitDirs(t *testing.T) {
u, err := user.Current() u, err := user.Current()
assert.Nil(t, err) assert.Nil(t, err)
rootlessDirs := []string{ rootlessDirs := []string{}
path.Join(configDir, "containers/systemd"),
filepath.Join(quadlet.UnitDirAdmin, "users", u.Uid), rootlessDirs = appendSubPaths(rootlessDirs, path.Join(configDir, "containers/systemd"), false, nil)
filepath.Join(quadlet.UnitDirAdmin, "users"), rootlessDirs = appendSubPaths(rootlessDirs, filepath.Join(quadlet.UnitDirAdmin, "users"), true, nonNumericFilter)
} rootlessDirs = appendSubPaths(rootlessDirs, filepath.Join(quadlet.UnitDirAdmin, "users", u.Uid), true, userLevelFilter)
rootlessDirs = append(rootlessDirs, filepath.Join(quadlet.UnitDirAdmin, "users"))
unitDirs = getUnitDirs(true) unitDirs = getUnitDirs(true)
assert.Equal(t, unitDirs, rootlessDirs, "rootless unit dirs should match") assert.Equal(t, unitDirs, rootlessDirs, "rootless unit dirs should match")

View File

@ -44,7 +44,9 @@ For rootless containers, when administrators place Quadlet files in the
Quadlet when the login session begins. If the administrator places a Quadlet Quadlet when the login session begins. If the administrator places a Quadlet
file in the /etc/containers/systemd/users/${UID}/ directory, then only the file in the /etc/containers/systemd/users/${UID}/ directory, then only the
user with the matching UID execute the Quadlet when the login user with the matching UID execute the Quadlet when the login
session gets started. session gets started. For unit files placed in subdirectories within
/etc/containers/systemd/user/${UID}/ and the other user unit search paths,
Quadlet will recursively search and run the unit files present in these subdirectories.
### Enabling unit files ### Enabling unit files

View File

@ -5,6 +5,7 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"io"
"math/rand" "math/rand"
"net" "net"
"net/url" "net/url"
@ -15,6 +16,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"syscall"
"testing" "testing"
"time" "time"
@ -1321,3 +1323,89 @@ func useCustomNetworkDir(podmanTest *PodmanTestIntegration, tempdir string) {
podmanTest.RestartRemoteService() podmanTest.RestartRemoteService()
} }
} }
// copy directories recursively from source path to destination path
func CopyDirectory(srcDir, dest string) error {
entries, err := os.ReadDir(srcDir)
if err != nil {
return err
}
for _, entry := range entries {
sourcePath := filepath.Join(srcDir, entry.Name())
destPath := filepath.Join(dest, entry.Name())
fileInfo, err := os.Stat(sourcePath)
if err != nil {
return err
}
stat, ok := fileInfo.Sys().(*syscall.Stat_t)
if !ok {
return fmt.Errorf("failed to get raw syscall.Stat_t data for %q", sourcePath)
}
switch fileInfo.Mode() & os.ModeType {
case os.ModeDir:
if err := os.MkdirAll(destPath, 0755); err != nil {
return fmt.Errorf("failed to create directory: %q, error: %q", destPath, err.Error())
}
if err := CopyDirectory(sourcePath, destPath); err != nil {
return err
}
case os.ModeSymlink:
if err := CopySymLink(sourcePath, destPath); err != nil {
return err
}
default:
if err := Copy(sourcePath, destPath); err != nil {
return err
}
}
if err := os.Lchown(destPath, int(stat.Uid), int(stat.Gid)); err != nil {
return err
}
fInfo, err := entry.Info()
if err != nil {
return err
}
isSymlink := fInfo.Mode()&os.ModeSymlink != 0
if !isSymlink {
if err := os.Chmod(destPath, fInfo.Mode()); err != nil {
return err
}
}
}
return nil
}
func Copy(srcFile, dstFile string) error {
out, err := os.Create(dstFile)
if err != nil {
return err
}
defer out.Close()
in, err := os.Open(srcFile)
if err != nil {
return err
}
_, err = io.Copy(out, in)
if err != nil {
return err
}
defer in.Close()
return nil
}
func CopySymLink(source, dest string) error {
link, err := os.Readlink(source)
if err != nil {
return err
}
return os.Symlink(link, dest)
}

View File

@ -0,0 +1,11 @@
[Unit]
Description=The sleep container
After=local-fs.target
[Container]
Image=registry.access.redhat.com/ubi9-minimal:latest
Exec=sleep 1000
[Install]
# Start by default on boot
WantedBy=multi-user.target default.target

View File

@ -0,0 +1,11 @@
[Unit]
Description=The sleep container
After=local-fs.target
[Container]
Image=registry.access.redhat.com/ubi9-minimal:latest
Exec=sleep 1000
[Install]
# Start by default on boot
WantedBy=multi-user.target default.target

View File

@ -0,0 +1,11 @@
[Unit]
Description=The sleep container
After=local-fs.target
[Container]
Image=registry.access.redhat.com/ubi9-minimal:latest
Exec=sleep 1000
[Install]
# Start by default on boot
WantedBy=multi-user.target default.target

View File

@ -471,6 +471,28 @@ BOGUS=foo
Expect(session).Should(Exit(1)) Expect(session).Should(Exit(1))
}) })
It("Should scan and return output for files in subdirectories", func() {
dirName := "test_subdir"
err = CopyDirectory(filepath.Join("quadlet", dirName), quadletDir)
if err != nil {
GinkgoWriter.Println("error:", err)
}
session := podmanTest.Quadlet([]string{"-dryrun", "-user"}, quadletDir)
session.WaitWithDefaultTimeout()
current := session.OutputToStringArray()
expected := []string{
"---mysleep.service---",
"---mysleep_1.service---",
"---mysleep_2.service---",
}
Expect(current).To(ContainElements(expected))
})
It("Should parse a kube file and print it to stdout", func() { It("Should parse a kube file and print it to stdout", func() {
fileName := "basic.kube" fileName := "basic.kube"
testcase := loadQuadletTestcase(filepath.Join("quadlet", fileName)) testcase := loadQuadletTestcase(filepath.Join("quadlet", fileName))