From 65479c7b454fbeffe0a64e352d98be84f2ac8085 Mon Sep 17 00:00:00 2001 From: Jan Kaluza Date: Fri, 24 Apr 2026 11:06:13 +0200 Subject: [PATCH 1/2] image/docker: use configfile for registries.d Replace the custom registries.d loader with `configfile.File` and `configfile.Read`, aligning registry configuration with the standard drop-in handling used elsewhere. Signed-off-by: Jan Kaluza --- image/docker/registries_d.go | 108 ++++------- image/docker/registries_d_test.go | 229 +++++++++++++++++------- image/docs/containers-registries.d.5.md | 15 +- 3 files changed, 211 insertions(+), 141 deletions(-) diff --git a/image/docker/registries_d.go b/image/docker/registries_d.go index 53bbb53cb1..6fe612160b 100644 --- a/image/docker/registries_d.go +++ b/image/docker/registries_d.go @@ -3,42 +3,29 @@ package docker import ( "errors" "fmt" - "io/fs" + "io" "net/url" - "os" "path" "path/filepath" - "strings" "github.com/opencontainers/go-digest" "github.com/sirupsen/logrus" "go.podman.io/image/v5/docker/reference" "go.podman.io/image/v5/internal/rootless" "go.podman.io/image/v5/types" - "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/configfile" "go.podman.io/storage/pkg/homedir" + "go.podman.io/storage/pkg/unshare" "gopkg.in/yaml.v3" ) -// systemRegistriesDirPath is the path to registries.d, used for locating lookaside Docker signature storage. -// You can override this at build time with -// -ldflags '-X go.podman.io/image/v5/docker.systemRegistriesDirPath=$your_path' -var systemRegistriesDirPath = builtinRegistriesDirPath - -// builtinRegistriesDirPath is the path to registries.d. -// DO NOT change this, instead see systemRegistriesDirPath above. -const builtinRegistriesDirPath = etcDir + "/containers/registries.d" - -// userRegistriesDirPath is the path to the per user registries.d. -var userRegistriesDir = filepath.FromSlash(".config/containers/registries.d") - // defaultUserDockerDir is the default lookaside directory for unprivileged user var defaultUserDockerDir = filepath.FromSlash(".local/share/containers/sigstore") // defaultDockerDir is the default lookaside directory for root var defaultDockerDir = "/var/lib/containers/sigstore" -// registryConfiguration is one of the files in registriesDirPath configuring lookaside locations, or the result of merging them all. +// registryConfiguration is one of the files configuring lookaside locations, or the result of merging them all. // NOTE: Keep this in sync with docs/registries.d.md! type registryConfiguration struct { DefaultDocker *registryNamespace `yaml:"default-docker"` @@ -78,91 +65,56 @@ func SignatureStorageBaseURL(sys *types.SystemContext, ref types.ImageReference, // loadRegistryConfiguration returns a registryConfiguration appropriate for sys. func loadRegistryConfiguration(sys *types.SystemContext) (*registryConfiguration, error) { - dirPath := registriesDirPath(sys) - logrus.Debugf(`Using registries.d directory %s`, dirPath) - return loadAndMergeConfig(dirPath) -} - -// registriesDirPath returns a path to registries.d -func registriesDirPath(sys *types.SystemContext) string { - return registriesDirPathWithHomeDir(sys, homedir.Get()) -} - -// registriesDirPathWithHomeDir is an internal implementation detail of registriesDirPath, -// it exists only to allow testing it with an artificial home directory. -func registriesDirPathWithHomeDir(sys *types.SystemContext, homeDir string) string { - if sys != nil && sys.RegistriesDirPath != "" { - return sys.RegistriesDirPath - } - userRegistriesDirPath := filepath.Join(homeDir, userRegistriesDir) - if err := fileutils.Exists(userRegistriesDirPath); err == nil { - return userRegistriesDirPath - } - if sys != nil && sys.RootForImplicitAbsolutePaths != "" { - return filepath.Join(sys.RootForImplicitAbsolutePaths, systemRegistriesDirPath) + registriesFiles := configfile.File{ + Name: "registries", + Extension: "yaml", + DoNotLoadMainFiles: true, + DoNotUseExtensionForConfigName: true, + UserId: unshare.GetRootlessUID(), + ErrorIfNotFound: false, + } + if sys != nil { + registriesFiles.RootForImplicitAbsolutePaths = sys.RootForImplicitAbsolutePaths + if sys.RegistriesDirPath != "" { + registriesFiles.CustomConfigFileDropInDirectory = sys.RegistriesDirPath + logrus.Debugf(`Using registries.d directory %s`, registriesFiles.CustomConfigFileDropInDirectory) + } } - - return systemRegistriesDirPath -} - -// loadAndMergeConfig loads configuration files in dirPath -// FIXME: Probably rename to loadRegistryConfigurationForPath -func loadAndMergeConfig(dirPath string) (*registryConfiguration, error) { mergedConfig := registryConfiguration{Docker: map[string]registryNamespace{}} dockerDefaultMergedFrom := "" nsMergedFrom := map[string]string{} - - dir, err := os.Open(dirPath) - if err != nil { - if os.IsNotExist(err) { - return &mergedConfig, nil - } - return nil, err - } - configNames, err := dir.Readdirnames(0) - if err != nil { - return nil, err - } - for _, configName := range configNames { - if !strings.HasSuffix(configName, ".yaml") { - continue + for item, err := range configfile.Read(®istriesFiles) { + if err != nil { + return nil, err } - configPath := filepath.Join(dirPath, configName) - configBytes, err := os.ReadFile(configPath) + contents, err := io.ReadAll(item.Reader) if err != nil { - if errors.Is(err, fs.ErrNotExist) { - // file must have been removed between the directory listing - // and the open call, ignore that as it is a expected race - continue - } return nil, err } - + logrus.Debugf(`Reading registries signature storage configuration from %q`, item.Name) var config registryConfiguration - err = yaml.Unmarshal(configBytes, &config) - if err != nil { - return nil, fmt.Errorf("parsing %s: %w", configPath, err) + if err := yaml.Unmarshal(contents, &config); err != nil { + return nil, fmt.Errorf("parsing %s: %w", item.Name, err) } if config.DefaultDocker != nil { if mergedConfig.DefaultDocker != nil { return nil, fmt.Errorf(`Error parsing signature storage configuration: "default-docker" defined both in %q and %q`, - dockerDefaultMergedFrom, configPath) + dockerDefaultMergedFrom, item.Name) } mergedConfig.DefaultDocker = config.DefaultDocker - dockerDefaultMergedFrom = configPath + dockerDefaultMergedFrom = item.Name } - for nsName, nsConfig := range config.Docker { // includes config.Docker == nil + for nsName, nsConfig := range config.Docker { if _, ok := mergedConfig.Docker[nsName]; ok { return nil, fmt.Errorf(`Error parsing signature storage configuration: "docker" namespace %q defined both in %q and %q`, - nsName, nsMergedFrom[nsName], configPath) + nsName, nsMergedFrom[nsName], item.Name) } mergedConfig.Docker[nsName] = nsConfig - nsMergedFrom[nsName] = configPath + nsMergedFrom[nsName] = item.Name } } - return &mergedConfig, nil } diff --git a/image/docker/registries_d_test.go b/image/docker/registries_d_test.go index 3d54e933f3..e91bf5b0f3 100644 --- a/image/docker/registries_d_test.go +++ b/image/docker/registries_d_test.go @@ -21,6 +21,12 @@ func dockerRefFromString(t *testing.T, s string) dockerReference { return dockerRef } +func writeDockerLookaside(t *testing.T, dir, filename, registry, lookaside string) { + t.Helper() + require.NoError(t, os.MkdirAll(dir, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(dir, filename), []byte(fmt.Sprintf("docker:\n %s:\n lookaside: %s\n", registry, lookaside)), 0o644)) +} + func TestSignatureStorageBaseURL(t *testing.T) { emptyDir := t.TempDir() for _, c := range []struct { @@ -66,72 +72,173 @@ func TestSignatureStorageBaseURL(t *testing.T) { } } -func TestRegistriesDirPath(t *testing.T) { - const nondefaultPath = "/this/is/not/the/default/registries.d" - const variableReference = "$HOME" - const rootPrefix = "/root/prefix" - tempHome := t.TempDir() - userRegistriesDir := filepath.FromSlash(".config/containers/registries.d") - userRegistriesDirPath := filepath.Join(tempHome, userRegistriesDir) - for _, c := range []struct { - sys *types.SystemContext - userFilePresent bool - expected string - }{ - // The common case - {nil, false, systemRegistriesDirPath}, - // There is a context, but it does not override the path. - {&types.SystemContext{}, false, systemRegistriesDirPath}, - // Path overridden - {&types.SystemContext{RegistriesDirPath: nondefaultPath}, false, nondefaultPath}, - // Root overridden - { - &types.SystemContext{RootForImplicitAbsolutePaths: rootPrefix}, - false, - filepath.Join(rootPrefix, systemRegistriesDirPath), +func TestLoadRegistryConfiguration(t *testing.T) { + type testcase struct { + setup func(t *testing.T) *types.SystemContext + wantLookaside map[string]string + forbiddenDockerKey string + expectErr bool + } + tests := []testcase{ + { // Explicit override directory: only load from there. + setup: func(t *testing.T) *types.SystemContext { + dir := t.TempDir() + writeDockerLookaside(t, dir, "01.yaml", "example.com", "https://override.example.com") + return &types.SystemContext{RegistriesDirPath: dir} + }, + wantLookaside: map[string]string{ + "example.com": "https://override.example.com", + }, + }, + { // Default configfile search: drop-ins from /usr + /etc (under RootForImplicitAbsolutePaths); main registries.yaml ignored. + setup: func(t *testing.T) *types.SystemContext { + root := t.TempDir() + t.Setenv("XDG_CONFIG_HOME", filepath.Join(root, "xdg")) + + usrRegistriesD := filepath.Join(root, "usr/share/containers/registries.d") + etcRegistriesD := filepath.Join(root, "etc/containers/registries.d") + + writeDockerLookaside(t, usrRegistriesD, "10-vendor.yaml", "example.com", "https://vendor.example.com") + writeDockerLookaside(t, etcRegistriesD, "10-vendor.yaml", "example.com", "https://admin.example.com") + writeDockerLookaside(t, filepath.Join(root, "etc/containers"), "registries.yaml", "should.not.be.loaded", "https://wrong.example.com") + + return &types.SystemContext{RootForImplicitAbsolutePaths: root} + }, + wantLookaside: map[string]string{ + "example.com": "https://admin.example.com", + }, + forbiddenDockerKey: "should.not.be.loaded", }, - // Root and path overrides present simultaneously, - { - &types.SystemContext{ - RootForImplicitAbsolutePaths: rootPrefix, - RegistriesDirPath: nondefaultPath, + { // Explicit RegistriesDirPath bypasses configfile search completely. + setup: func(t *testing.T) *types.SystemContext { + root := t.TempDir() + t.Setenv("XDG_CONFIG_HOME", filepath.Join(root, "xdg")) + userRegistriesD := filepath.Join(root, "xdg/containers/registries.d") + writeDockerLookaside(t, userRegistriesD, "10-user.yaml", "example.com", "https://user.example.com") + + overrideDir := t.TempDir() + writeDockerLookaside(t, overrideDir, "01.yaml", "example.com", "https://explicit.example.com") + + return &types.SystemContext{ + RegistriesDirPath: overrideDir, + RootForImplicitAbsolutePaths: root, // should not matter for the explicit override + } + }, + wantLookaside: map[string]string{ + "example.com": "https://explicit.example.com", }, - false, - nondefaultPath, }, - // User registries.d present, not overridden - {&types.SystemContext{}, true, userRegistriesDirPath}, - // Context and user User registries.d preset simultaneously - {&types.SystemContext{RegistriesDirPath: nondefaultPath}, true, nondefaultPath}, - // Root and user registries.d overrides present simultaneously, - { - &types.SystemContext{ - RootForImplicitAbsolutePaths: rootPrefix, - RegistriesDirPath: nondefaultPath, + { // RootForImplicitAbsolutePaths does not affect explicit RegistriesDirPath. + setup: func(t *testing.T) *types.SystemContext { + overrideDir := t.TempDir() + writeDockerLookaside(t, overrideDir, "01.yaml", "example.com", "https://explicit.example.com") + + root := t.TempDir() + // If RootForImplicitAbsolutePaths were incorrectly applied to RegistriesDirPath, + // we'd look under root+overrideDir which doesn't exist. + return &types.SystemContext{RegistriesDirPath: overrideDir, RootForImplicitAbsolutePaths: root} + }, + wantLookaside: map[string]string{ + "example.com": "https://explicit.example.com", }, - true, - nondefaultPath, }, - // No environment expansion happens in the overridden paths - {&types.SystemContext{RegistriesDirPath: variableReference}, false, variableReference}, - } { - if c.userFilePresent { - err := os.MkdirAll(userRegistriesDirPath, 0o700) - require.NoError(t, err) - } else { - err := os.RemoveAll(userRegistriesDirPath) + { // Explicit RegistriesDirPath is not env-expanded. + setup: func(t *testing.T) *types.SystemContext { + parent := t.TempDir() + literalHomeDir := filepath.Join(parent, "$HOME") + writeDockerLookaside(t, literalHomeDir, "01.yaml", "example.com", "https://literal.example.com") + return &types.SystemContext{RegistriesDirPath: literalHomeDir} + }, + wantLookaside: map[string]string{ + "example.com": "https://literal.example.com", + }, + }, + { // user XDG_CONFIG_HOME/.../registries.d has higher priority than /etc for same filename. + setup: func(t *testing.T) *types.SystemContext { + root := t.TempDir() + t.Setenv("XDG_CONFIG_HOME", filepath.Join(root, "xdg")) + + etcRegistriesD := filepath.Join(root, "etc/containers/registries.d") + writeDockerLookaside(t, etcRegistriesD, "10-same.yaml", "example.com", "https://etc.example.com") + + userRegistriesD := filepath.Join(root, "xdg/containers/registries.d") + writeDockerLookaside(t, userRegistriesD, "10-same.yaml", "example.com", "https://user.example.com") + + return &types.SystemContext{RootForImplicitAbsolutePaths: root} + }, + wantLookaside: map[string]string{ + "example.com": "https://user.example.com", + }, + }, + { // Distinct filenames in user and /etc are both processed. + setup: func(t *testing.T) *types.SystemContext { + root := t.TempDir() + t.Setenv("XDG_CONFIG_HOME", filepath.Join(root, "xdg")) + + etcRegistriesD := filepath.Join(root, "etc/containers/registries.d") + writeDockerLookaside(t, etcRegistriesD, "10-etc-only.yaml", "example.com", "https://etc.example.com") + + userRegistriesD := filepath.Join(root, "xdg/containers/registries.d") + writeDockerLookaside(t, userRegistriesD, "20-user-only.yaml", "example.net", "https://user.example.net") + + return &types.SystemContext{RootForImplicitAbsolutePaths: root} + }, + wantLookaside: map[string]string{ + "example.com": "https://etc.example.com", + "example.net": "https://user.example.net", + }, + }, + { // Duplicate docker namespace across distinct filenames errors. + setup: func(t *testing.T) *types.SystemContext { + root := t.TempDir() + t.Setenv("XDG_CONFIG_HOME", filepath.Join(root, "xdg")) + etcRegistriesD := filepath.Join(root, "etc/containers/registries.d") + writeDockerLookaside(t, etcRegistriesD, "10-a.yaml", "example.com", "https://a.example.com") + writeDockerLookaside(t, etcRegistriesD, "20-b.yaml", "example.com", "https://b.example.com") + return &types.SystemContext{RootForImplicitAbsolutePaths: root} + }, + expectErr: true, + }, + { // Duplicate default-docker across distinct filenames errors. + setup: func(t *testing.T) *types.SystemContext { + root := t.TempDir() + t.Setenv("XDG_CONFIG_HOME", filepath.Join(root, "xdg")) + etcRegistriesD := filepath.Join(root, "etc/containers/registries.d") + require.NoError(t, os.MkdirAll(etcRegistriesD, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(etcRegistriesD, "10-a.yaml"), []byte("default-docker:\n lookaside: https://a.example.com\n"), 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(etcRegistriesD, "20-b.yaml"), []byte("default-docker:\n lookaside: https://b.example.com\n"), 0o644)) + return &types.SystemContext{RootForImplicitAbsolutePaths: root} + }, + expectErr: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run("", func(t *testing.T) { + sys := tt.setup(t) + cfg, err := loadRegistryConfiguration(sys) + if tt.expectErr { + assert.Error(t, err) + return + } require.NoError(t, err) - } - path := registriesDirPathWithHomeDir(c.sys, tempHome) - assert.Equal(t, c.expected, path) + for ns, lookaside := range tt.wantLookaside { + assert.Equal(t, lookaside, cfg.Docker[ns].Lookaside) + } + if tt.forbiddenDockerKey != "" { + _, ok := cfg.Docker[tt.forbiddenDockerKey] + assert.False(t, ok) + } + }) } } -func TestLoadAndMergeConfig(t *testing.T) { +func TestLoadRegistryConfigurationFromRegistriesDirPath(t *testing.T) { tmpDir := t.TempDir() // No registries.d exists - config, err := loadAndMergeConfig(filepath.Join(tmpDir, "thisdoesnotexist")) + config, err := loadRegistryConfiguration(&types.SystemContext{RegistriesDirPath: filepath.Join(tmpDir, "thisdoesnotexist")}) require.NoError(t, err) assert.Equal(t, ®istryConfiguration{Docker: map[string]registryNamespace{}}, config) @@ -139,7 +246,7 @@ func TestLoadAndMergeConfig(t *testing.T) { emptyDir := filepath.Join(tmpDir, "empty") err = os.Mkdir(emptyDir, 0o755) require.NoError(t, err) - config, err = loadAndMergeConfig(emptyDir) + config, err = loadRegistryConfiguration(&types.SystemContext{RegistriesDirPath: emptyDir}) require.NoError(t, err) assert.Equal(t, ®istryConfiguration{Docker: map[string]registryNamespace{}}, config) @@ -147,7 +254,7 @@ func TestLoadAndMergeConfig(t *testing.T) { unreadableDir := filepath.Join(tmpDir, "unreadable") err = os.Mkdir(unreadableDir, 0o000) require.NoError(t, err) - _, err = loadAndMergeConfig(unreadableDir) + _, err = loadRegistryConfiguration(&types.SystemContext{RegistriesDirPath: unreadableDir}) assert.Error(t, err) // An unreadable file in a registries.d directory @@ -158,7 +265,7 @@ func TestLoadAndMergeConfig(t *testing.T) { require.NoError(t, err) err = os.WriteFile(filepath.Join(unreadableFileDir, "1.yaml"), nil, 0o000) require.NoError(t, err) - _, err = loadAndMergeConfig(unreadableFileDir) + _, err = loadRegistryConfiguration(&types.SystemContext{RegistriesDirPath: unreadableFileDir}) assert.Error(t, err) // Invalid YAML @@ -167,7 +274,7 @@ func TestLoadAndMergeConfig(t *testing.T) { require.NoError(t, err) err = os.WriteFile(filepath.Join(invalidYAMLDir, "0.yaml"), []byte("}"), 0o644) require.NoError(t, err) - _, err = loadAndMergeConfig(invalidYAMLDir) + _, err = loadRegistryConfiguration(&types.SystemContext{RegistriesDirPath: invalidYAMLDir}) assert.Error(t, err) // Duplicate DefaultDocker @@ -180,7 +287,7 @@ func TestLoadAndMergeConfig(t *testing.T) { err = os.WriteFile(filepath.Join(duplicateDefault, "1.yaml"), []byte("default-docker:\n lookaside: file:////tmp/different"), 0o644) require.NoError(t, err) - _, err = loadAndMergeConfig(duplicateDefault) + _, err = loadRegistryConfiguration(&types.SystemContext{RegistriesDirPath: duplicateDefault}) assert.ErrorContains(t, err, "0.yaml") assert.ErrorContains(t, err, "1.yaml") @@ -194,12 +301,12 @@ func TestLoadAndMergeConfig(t *testing.T) { err = os.WriteFile(filepath.Join(duplicateNS, "1.yaml"), []byte("docker:\n example.com:\n lookaside: file:////tmp/different"), 0o644) require.NoError(t, err) - _, err = loadAndMergeConfig(duplicateNS) + _, err = loadRegistryConfiguration(&types.SystemContext{RegistriesDirPath: duplicateNS}) assert.ErrorContains(t, err, "0.yaml") assert.ErrorContains(t, err, "1.yaml") // A fully worked example, including an empty-dictionary file and a non-.yaml file - config, err = loadAndMergeConfig("fixtures/registries.d") + config, err = loadRegistryConfiguration(&types.SystemContext{RegistriesDirPath: "fixtures/registries.d"}) require.NoError(t, err) assert.Equal(t, ®istryConfiguration{ DefaultDocker: ®istryNamespace{Lookaside: "file:///mnt/companywide/signatures/for/other/repositories"}, diff --git a/image/docs/containers-registries.d.5.md b/image/docs/containers-registries.d.5.md index 04434de4b6..713aa46709 100644 --- a/image/docs/containers-registries.d.5.md +++ b/image/docs/containers-registries.d.5.md @@ -12,8 +12,19 @@ The registries configuration directory contains configuration for various regist so that the configuration does not have to be provided in command-line options over and over for every command, and so that it can be shared by all users of containers/image. -By default, the registries configuration directory is `$HOME/.config/containers/registries.d` if it exists, otherwise `/etc/containers/registries.d` (unless overridden at compile-time); -applications may allow using a different directory instead. +By default, registries.d configuration may be provided by a vendor in `/usr/share/containers/registries.d/`, overridden by an administrator in `/etc/containers/registries.d/`, +and overridden per-user in `$XDG_CONFIG_HOME/containers/registries.d/` (or `$HOME/.config/containers/registries.d/` if `$XDG_CONFIG_HOME` is not set). + +Rootless/rootful specific drop-in directories are also consulted where applicable: + +- `/usr/share/containers/registries.rootful.d/` (UID == 0) +- `/usr/share/containers/registries.rootless.d/` (UID > 0) +- `/usr/share/containers/registries.rootless.d//` (UID > 0) +- `/etc/containers/registries.rootful.d/` (UID == 0) +- `/etc/containers/registries.rootless.d/` (UID > 0) +- `/etc/containers/registries.rootless.d//` (UID > 0) + +Only files with the `.yaml` extension are read. Applications may allow using a different directory instead (e.g. via an explicit override). ## Directory Structure From 0b40258c6c4e83e34bc4d79dab0fae10115eb26b Mon Sep 17 00:00:00 2001 From: Jan Kaluza Date: Mon, 27 Apr 2026 14:12:43 +0200 Subject: [PATCH 2/2] configfile/parse: support non-regular files in drop-in dir The symlinks and other non-regular files were supported by the previous code loading registries configuration files. This commit brings this support back by changing the directory entry check from `IsRegular()` to `!IsDir()`. Signed-off-by: Jan Kaluza --- storage/pkg/configfile/parse.go | 2 +- storage/pkg/configfile/parse_test.go | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/storage/pkg/configfile/parse.go b/storage/pkg/configfile/parse.go index ad09f98fee..eb397dcd92 100644 --- a/storage/pkg/configfile/parse.go +++ b/storage/pkg/configfile/parse.go @@ -384,7 +384,7 @@ func readDropInsFromPaths(paths []string, suffix string) ([]string, error) { return nil, err } for _, entry := range entries { - if entry.Type().IsRegular() && strings.HasSuffix(entry.Name(), suffix) { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), suffix) { dropInMap[entry.Name()] = filepath.Join(path, entry.Name()) } } diff --git a/storage/pkg/configfile/parse_test.go b/storage/pkg/configfile/parse_test.go index 3c94751da1..9220d64453 100644 --- a/storage/pkg/configfile/parse_test.go +++ b/storage/pkg/configfile/parse_test.go @@ -289,6 +289,22 @@ func Test_Read(t *testing.T) { }, want: []string{"valid"}, }, + { + name: "drop in supports symlink", + arg: File{ + Name: "containers", + Extension: "conf", + }, + setup: func(t *testing.T, tc *testcase) { + realFile := filepath.Join(t.TempDir(), "real.conf") + require.NoError(t, os.WriteFile(realFile, []byte("symlinked"), 0o600)) + + dropInDir := filepath.Join(tc.arg.RootForImplicitAbsolutePaths, systemConfigPath, "containers.conf.d") + require.NoError(t, os.MkdirAll(dropInDir, 0o755)) + require.NoError(t, os.Symlink(realFile, filepath.Join(dropInDir, "10-symlink.conf"))) + }, + want: []string{"symlinked"}, + }, { name: "policy.json main files only (ignore drop-ins)", arg: File{