Repository: spegel-org/spegel
Pull request #852 (2025-04-27 09:23:22)

Remove use of Afero in Containerd config
I initially used Afero because I thought it would make testing easier. After some time now it has mostly made things more difficult and Afero was never used in the rest of the codebase. For this reason it is better to remove Afero completely to align with the rest of the project.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/spegel-org/spegel/pull/852?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org) Report
Attention: Patch coverage is `91.17647%` with `3 lines` in your changes missing coverage. Please review.

| [Files with missing lines](https://app.codecov.io/gh/spegel-org/spegel/pull/852?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org) | Patch % | Lines |
|---|---|---|
| [main.go](https://app.codecov.io/gh/spegel-org/spegel/pull/852?src=pr&el=tree&filepath=main.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org#diff-bWFpbi5nbw==) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/spegel-org/spegel/pull/852?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org) |
| [pkg/oci/containerd.go](https://app.codecov.io/gh/spegel-org/spegel/pull/852?src=pr&el=tree&filepath=pkg%2Foci%2Fcontainerd.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org#diff-cGtnL29jaS9jb250YWluZXJkLmdv) | 96.87% | [1 Missing :warning: ](https://app.codecov.io/gh/spegel-org/spegel/pull/852?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org) |

| [Files with missing lines](https://app.codecov.io/gh/spegel-org/spegel/pull/852?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org) | Coverage Δ | |
|---|---|---|
| [pkg/oci/containerd.go](https://app.codecov.io/gh/spegel-org/spegel/pull/852?src=pr&el=tree&filepath=pkg%2Foci%2Fcontainerd.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org#diff-cGtnL29jaS9jb250YWluZXJkLmdv) | `57.57% <96.87%> (+0.49%)` | :arrow_up: |
| [main.go](https://app.codecov.io/gh/spegel-org/spegel/pull/852?src=pr&el=tree&filepath=main.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org#diff-bWFpbi5nbw==) | `0.00% <0.00%> (ø)` | |

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b877695..f8d0cb6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Changed
 
+- [#852](https://github.com/spegel-org/spegel/pull/852) Remove use of Afero in Containerd config.
+
 ### Deprecated
 
 ### Removed
diff --git a/go.mod b/go.mod
index 40b9b89..1d649cd 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,6 @@ require (
 	github.com/pelletier/go-toml/v2 v2.2.4
 	github.com/prometheus/client_golang v1.22.0
 	github.com/prometheus/common v0.63.0
-	github.com/spf13/afero v1.14.0
 	github.com/stretchr/testify v1.10.0
 	go.etcd.io/bbolt v1.4.0
 	golang.org/x/sync v0.13.0
@@ -162,6 +161,7 @@ require (
 	github.com/shopspring/decimal v1.4.0 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/spaolacci/murmur3 v1.1.0 // indirect
+	github.com/spf13/afero v1.14.0 // indirect
 	github.com/spf13/cast v1.7.0 // indirect
 	github.com/spf13/cobra v1.8.1 // indirect
 	github.com/spf13/jwalterweatherman v1.1.0 // indirect
diff --git a/main.go b/main.go
index 3a6ecf0..40ca01f 100644
--- a/main.go
+++ b/main.go
@@ -19,7 +19,6 @@ import (
 	"github.com/alexflint/go-arg"
 	"github.com/go-logr/logr"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
-	"github.com/spf13/afero"
 	"golang.org/x/sync/errgroup"
 	"k8s.io/klog/v2"
 
@@ -126,8 +125,7 @@ func configurationCommand(ctx context.Context, args *ConfigurationCmd) error {
 	if err != nil {
 		return err
 	}
-	fs := afero.NewOsFs()
-	err = oci.AddMirrorConfiguration(ctx, fs, args.ContainerdRegistryConfigPath, args.MirroredRegistries, args.MirrorTargets, args.ResolveTags, args.PrependExisting, username, password)
+	err = oci.AddMirrorConfiguration(ctx, args.ContainerdRegistryConfigPath, args.MirroredRegistries, args.MirrorTargets, args.ResolveTags, args.PrependExisting, username, password)
 	if err != nil {
 		return err
 	}
@@ -286,8 +284,7 @@ func loadBasicAuth() (string, string, error) {
 func cleanupCommand(ctx context.Context, args *CleanupCmd) error {
 	log := logr.FromContextOrDiscard(ctx)
 
-	fs := afero.NewOsFs()
-	err := oci.CleanupMirrorConfiguration(ctx, fs, args.ContainerdRegistryConfigPath)
+	err := oci.CleanupMirrorConfiguration(ctx, args.ContainerdRegistryConfigPath)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/oci/containerd.go b/pkg/oci/containerd.go
index 9a354e8..2cf25a5 100644
--- a/pkg/oci/containerd.go
+++ b/pkg/oci/containerd.go
@@ -25,7 +25,6 @@ import (
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/pelletier/go-toml/v2"
 	tomlu "github.com/pelletier/go-toml/v2/unstable"
-	"github.com/spf13/afero"
 	"google.golang.org/grpc"
 	utilversion "k8s.io/apimachinery/pkg/util/version"
 	runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
@@ -356,21 +355,21 @@ func createFilters(mirroredRegistries []url.URL) (string, string) {
 // Refer to containerd registry configuration documentation for more information about required configuration.
 // https://github.com/containerd/containerd/blob/main/docs/cri/config.md#registry-configuration
 // https://github.com/containerd/containerd/blob/main/docs/hosts.md#registry-configuration---examples
-func AddMirrorConfiguration(ctx context.Context, fs afero.Fs, configPath string, mirroredRegistries, mirrorTargets []url.URL, resolveTags, prependExisting bool, username, password string) error {
+func AddMirrorConfiguration(ctx context.Context, configPath string, mirroredRegistries, mirrorTargets []url.URL, resolveTags, prependExisting bool, username, password string) error {
 	log := logr.FromContextOrDiscard(ctx)
 	err := validateRegistries(mirroredRegistries)
 	if err != nil {
 		return err
 	}
-	err = fs.MkdirAll(configPath, 0o755)
+	err = os.MkdirAll(configPath, 0o755)
 	if err != nil {
 		return err
 	}
-	err = backupConfig(log, fs, configPath)
+	err = backupConfig(log, configPath)
 	if err != nil {
 		return err
 	}
-	err = clearConfig(fs, configPath)
+	err = clearConfig(configPath)
 	if err != nil {
 		return err
 	}
@@ -390,7 +389,7 @@ func AddMirrorConfiguration(ctx context.Context, fs afero.Fs, configPath string,
 			return err
 		}
 		if prependExisting {
-			existingHosts, err := existingHosts(fs, configPath, mirroredRegistry)
+			existingHosts, err := existingHosts(configPath, mirroredRegistry)
 			if err != nil {
 				return err
 			}
@@ -404,11 +403,11 @@ func AddMirrorConfiguration(ctx context.Context, fs afero.Fs, configPath string,
 			hostComp = "_default"
 		}
 		fp := path.Join(configPath, hostComp, "hosts.toml")
-		err = fs.MkdirAll(path.Dir(fp), 0o755)
+		err = os.MkdirAll(filepath.Dir(fp), 0o755)
 		if err != nil {
 			return err
 		}
-		err = afero.WriteFile(fs, fp, []byte(templatedHosts), 0o644)
+		err = os.WriteFile(fp, []byte(templatedHosts), 0o644)
 		if err != nil {
 			return err
 		}
@@ -417,12 +416,12 @@ func AddMirrorConfiguration(ctx context.Context, fs afero.Fs, configPath string,
 	return nil
 }
 
-func CleanupMirrorConfiguration(ctx context.Context, fs afero.Fs, configPath string) error {
+func CleanupMirrorConfiguration(ctx context.Context, configPath string) error {
 	log := logr.FromContextOrDiscard(ctx)
 
 	// If backup directory does not exist it means mirrors was never configured or cleanup has already run.
 	backupDirPath := path.Join(configPath, backupDir)
-	ok, err := afero.DirExists(fs, backupDirPath)
+	ok, err := dirExists(backupDirPath)
 	if err != nil {
 		return err
 	}
@@ -432,20 +431,20 @@ func CleanupMirrorConfiguration(ctx context.Context, fs afero.Fs, configPath str
 	}
 
 	// Remove everything except _backup
-	err = clearConfig(fs, configPath)
+	err = clearConfig(configPath)
 	if err != nil {
 		return err
 	}
 
 	// Move content from backup directory
-	files, err := afero.ReadDir(fs, backupDirPath)
+	files, err := os.ReadDir(backupDirPath)
 	if err != nil {
 		return err
 	}
 	for _, fi := range files {
 		oldPath := path.Join(backupDirPath, fi.Name())
 		newPath := path.Join(configPath, fi.Name())
-		err := fs.Rename(oldPath, newPath)
+		err := os.Rename(oldPath, newPath)
 		if err != nil {
 			return err
 		}
@@ -453,7 +452,7 @@ func CleanupMirrorConfiguration(ctx context.Context, fs afero.Fs, configPath str
 	}
 
 	// Remove backup directory to indicate that cleanup has been run.
-	err = fs.RemoveAll(backupDirPath)
+	err = os.RemoveAll(backupDirPath)
 	if err != nil {
 		return err
 	}
@@ -480,27 +479,27 @@ func validateRegistries(urls []url.URL) error {
 	return errors.Join(errs...)
 }
 
-func backupConfig(log logr.Logger, fs afero.Fs, configPath string) error {
+func backupConfig(log logr.Logger, configPath string) error {
 	backupDirPath := path.Join(configPath, backupDir)
-	ok, err := afero.DirExists(fs, backupDirPath)
+	ok, err := dirExists(backupDirPath)
 	if err != nil {
 		return err
 	}
 	if ok {
 		return nil
 	}
-	files, err := afero.ReadDir(fs, configPath)
+	files, err := os.ReadDir(configPath)
 	if err != nil {
 		return err
 	}
-	err = fs.MkdirAll(backupDirPath, 0o755)
+	err = os.MkdirAll(backupDirPath, 0o755)
 	if err != nil {
 		return err
 	}
 	for _, fi := range files {
 		oldPath := path.Join(configPath, fi.Name())
 		newPath := path.Join(backupDirPath, fi.Name())
-		err := fs.Rename(oldPath, newPath)
+		err := os.Rename(oldPath, newPath)
 		if err != nil {
 			return err
 		}
@@ -509,8 +508,8 @@ func backupConfig(log logr.Logger, fs afero.Fs, configPath string) error {
 	return nil
 }
 
-func clearConfig(fs afero.Fs, configPath string) error {
-	files, err := afero.ReadDir(fs, configPath)
+func clearConfig(configPath string) error {
+	files, err := os.ReadDir(configPath)
 	if err != nil {
 		return err
 	}
@@ -519,7 +518,7 @@ func clearConfig(fs afero.Fs, configPath string) error {
 			continue
 		}
 		filePath := path.Join(configPath, fi.Name())
-		err := fs.RemoveAll(filePath)
+		err := os.RemoveAll(filePath)
 		if err != nil {
 			return err
 		}
@@ -576,10 +575,10 @@ type hostFile struct {
 	Hosts map[string]any `toml:"host"`
 }
 
-func existingHosts(fs afero.Fs, configPath string, mirroredRegistry url.URL) (string, error) {
+func existingHosts(configPath string, mirroredRegistry url.URL) (string, error) {
 	fp := path.Join(configPath, backupDir, mirroredRegistry.Host, "hosts.toml")
-	b, err := afero.ReadFile(fs, fp)
-	if errors.Is(err, afero.ErrFileNotFound) {
+	b, err := os.ReadFile(fp)
+	if errors.Is(err, os.ErrNotExist) {
 		return "", nil
 	}
 	if err != nil {
@@ -629,3 +628,14 @@ func existingHosts(fs afero.Fs, configPath string, mirroredRegistry url.URL) (st
 	}
 	return strings.TrimSpace(strings.Join(ehs, "\n")), nil
 }
+
+func dirExists(path string) (bool, error) {
+	info, err := os.Stat(path)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return false, nil
+		}
+		return false, err
+	}
+	return info.IsDir(), nil
+}


Test patch:
diff --git a/pkg/oci/containerd_test.go b/pkg/oci/containerd_test.go
index 434f795..3aa1a9e 100644
--- a/pkg/oci/containerd_test.go
+++ b/pkg/oci/containerd_test.go
@@ -5,13 +5,13 @@ import (
 	iofs "io/fs"
 	"maps"
 	"net/url"
+	"os"
 	"path/filepath"
 	"testing"
 
 	eventtypes "github.com/containerd/containerd/api/events"
 	"github.com/containerd/typeurl/v2"
 	"github.com/go-logr/logr"
-	"github.com/spf13/afero"
 	"github.com/stretchr/testify/require"
 	runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
 )
@@ -183,29 +183,25 @@ func TestBackupConfig(t *testing.T) {
 
 	log := logr.Discard()
 
-	fs := afero.NewMemMapFs()
-	err := fs.MkdirAll("/config", 0o755)
+	configPath := t.TempDir()
+	err := backupConfig(log, configPath)
 	require.NoError(t, err)
-	err = backupConfig(log, fs, "/config")
-	require.NoError(t, err)
-	ok, err := afero.DirExists(fs, "/config/_backup/")
+	ok, err := dirExists(filepath.Join(configPath, "_backup"))
 	require.NoError(t, err)
 	require.True(t, ok)
-	files, err := afero.ReadDir(fs, "/config/_backup")
+	files, err := os.ReadDir(filepath.Join(configPath, "_backup"))
 	require.NoError(t, err)
 	require.Empty(t, files)
 
-	fs = afero.NewMemMapFs()
-	err = fs.MkdirAll("/config", 0o755)
-	require.NoError(t, err)
-	err = afero.WriteFile(fs, "/config/test.txt", []byte("Hello World"), 0o644)
+	configPath = t.TempDir()
+	err = os.WriteFile(filepath.Join(configPath, "test.txt"), []byte("Hello World"), 0o644)
 	require.NoError(t, err)
-	err = backupConfig(log, fs, "/config/")
+	err = backupConfig(log, configPath)
 	require.NoError(t, err)
-	ok, err = afero.DirExists(fs, "/config/_backup/")
+	ok, err = dirExists(filepath.Join(configPath, "_backup"))
 	require.NoError(t, err)
 	require.True(t, ok)
-	files, err = afero.ReadDir(fs, "/config/_backup/")
+	files, err = os.ReadDir(filepath.Join(configPath, "_backup"))
 	require.NoError(t, err)
 	require.Len(t, files, 1)
 }
@@ -314,8 +310,6 @@ func TestGetEventImage(t *testing.T) {
 func TestMirrorConfiguration(t *testing.T) {
 	t.Parallel()
 
-	registryConfigPath := "/etc/containerd/certs.d"
-
 	tests := []struct {
 		existingFiles       map[string]string
 		expectedFiles       map[string]string
@@ -335,7 +329,7 @@ func TestMirrorConfiguration(t *testing.T) {
 			mirrors:         stringListToUrlList(t, []string{"http://127.0.0.1:5000", "http://127.0.0.2:5000", "http://127.0.0.1:5001"}),
 			prependExisting: false,
 			expectedFiles: map[string]string{
-				"/etc/containerd/certs.d/foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
+				"foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
 
 [host.'http://127.0.0.1:5000']
 capabilities = ['pull', 'resolve']
@@ -354,7 +348,7 @@ capabilities = ['pull', 'resolve']`,
 			mirrors:         stringListToUrlList(t, []string{"http://127.0.0.1:5000"}),
 			prependExisting: false,
 			expectedFiles: map[string]string{
-				"/etc/containerd/certs.d/_default/hosts.toml": `[host.'http://127.0.0.1:5000']
+				"_default/hosts.toml": `[host.'http://127.0.0.1:5000']
 capabilities = ['pull', 'resolve']`,
 			},
 		},
@@ -365,11 +359,11 @@ capabilities = ['pull', 'resolve']`,
 			mirrors:         stringListToUrlList(t, []string{"http://127.0.0.1:5000"}),
 			prependExisting: false,
 			expectedFiles: map[string]string{
-				"/etc/containerd/certs.d/docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
+				"docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
 
 [host.'http://127.0.0.1:5000']
 capabilities = ['pull']`,
-				"/etc/containerd/certs.d/foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
+				"foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
 
 [host.'http://127.0.0.1:5000']
 capabilities = ['pull']`,
@@ -383,11 +377,11 @@ capabilities = ['pull']`,
 			createConfigPathDir: false,
 			prependExisting:     false,
 			expectedFiles: map[string]string{
-				"/etc/containerd/certs.d/docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
+				"docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
 
 [host.'http://127.0.0.1:5000']
 capabilities = ['pull', 'resolve']`,
-				"/etc/containerd/certs.d/foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
+				"foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
 
 [host.'http://127.0.0.1:5000']
 capabilities = ['pull', 'resolve']`,
@@ -401,11 +395,11 @@ capabilities = ['pull', 'resolve']`,
 			createConfigPathDir: true,
 			prependExisting:     false,
 			expectedFiles: map[string]string{
-				"/etc/containerd/certs.d/docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
+				"docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
 
 [host.'http://127.0.0.1:5000']
 capabilities = ['pull', 'resolve']`,
-				"/etc/containerd/certs.d/foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
+				"foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
 
 [host.'http://127.0.0.1:5000']
 capabilities = ['pull', 'resolve']`,
@@ -419,17 +413,17 @@ capabilities = ['pull', 'resolve']`,
 			createConfigPathDir: true,
 			prependExisting:     false,
 			existingFiles: map[string]string{
-				"/etc/containerd/certs.d/docker.io/hosts.toml": "hello = 'world'",
-				"/etc/containerd/certs.d/ghcr.io/hosts.toml":   "foo = 'bar'",
+				"docker.io/hosts.toml": "hello = 'world'",
+				"ghcr.io/hosts.toml":   "foo = 'bar'",
 			},
 			expectedFiles: map[string]string{
-				"/etc/containerd/certs.d/_backup/docker.io/hosts.toml": "hello = 'world'",
-				"/etc/containerd/certs.d/_backup/ghcr.io/hosts.toml":   "foo = 'bar'",
-				"/etc/containerd/certs.d/docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
+				"_backup/docker.io/hosts.toml": "hello = 'world'",
+				"_backup/ghcr.io/hosts.toml":   "foo = 'bar'",
+				"docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
 
 [host.'http://127.0.0.1:5000']
 capabilities = ['pull', 'resolve']`,
-				"/etc/containerd/certs.d/foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
+				"foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
 
 [host.'http://127.0.0.1:5000']
 capabilities = ['pull', 'resolve']`,
@@ -443,19 +437,19 @@ capabilities = ['pull', 'resolve']`,
 			createConfigPathDir: true,
 			prependExisting:     false,
 			existingFiles: map[string]string{
-				"/etc/containerd/certs.d/_backup/docker.io/hosts.toml": "hello = 'world'",
-				"/etc/containerd/certs.d/_backup/ghcr.io/hosts.toml":   "foo = 'bar'",
-				"/etc/containerd/certs.d/test.txt":                     "test",
-				"/etc/containerd/certs.d/foo":                          "bar",
+				"_backup/docker.io/hosts.toml": "hello = 'world'",
+				"_backup/ghcr.io/hosts.toml":   "foo = 'bar'",
+				"test.txt":                     "test",
+				"foo":                          "bar",
 			},
 			expectedFiles: map[string]string{
-				"/etc/containerd/certs.d/_backup/docker.io/hosts.toml": "hello = 'world'",
-				"/etc/containerd/certs.d/_backup/ghcr.io/hosts.toml":   "foo = 'bar'",
-				"/etc/containerd/certs.d/docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
+				"_backup/docker.io/hosts.toml": "hello = 'world'",
+				"_backup/ghcr.io/hosts.toml":   "foo = 'bar'",
+				"docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
 
 [host.'http://127.0.0.1:5000']
 capabilities = ['pull', 'resolve']`,
-				"/etc/containerd/certs.d/foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
+				"foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
 
 [host.'http://127.0.0.1:5000']
 capabilities = ['pull', 'resolve']`,
@@ -469,7 +463,7 @@ capabilities = ['pull', 'resolve']`,
 			createConfigPathDir: true,
 			prependExisting:     true,
 			existingFiles: map[string]string{
-				"/etc/containerd/certs.d/docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
+				"docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
 
 [host.'http://example.com:30020']
 capabilities = ['pull', 'resolve']
@@ -484,7 +478,7 @@ capabilities = ['pull', 'resolve']
 client = ['/etc/certs/xxx/client.cert', '/etc/certs/xxx/client.key']`,
 			},
 			expectedFiles: map[string]string{
-				"/etc/containerd/certs.d/_backup/docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
+				"_backup/docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
 
 [host.'http://example.com:30020']
 capabilities = ['pull', 'resolve']
@@ -497,7 +491,7 @@ capabilities = ['pull', 'resolve']
 [host.'http://bar.com:30020']
 capabilities = ['pull', 'resolve']
 client = ['/etc/certs/xxx/client.cert', '/etc/certs/xxx/client.key']`,
-				"/etc/containerd/certs.d/docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
+				"docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
 
 [host.'http://127.0.0.1:5000']
 capabilities = ['pull', 'resolve']
@@ -513,7 +507,7 @@ client = ['/etc/certs/xxx/client.cert', '/etc/certs/xxx/client.key']
 [host.'http://bar.com:30020']
 capabilities = ['pull', 'resolve']
 client = ['/etc/certs/xxx/client.cert', '/etc/certs/xxx/client.key']`,
-				"/etc/containerd/certs.d/foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
+				"foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
 
 [host.'http://127.0.0.1:5000']
 capabilities = ['pull', 'resolve']`,
@@ -527,7 +521,7 @@ capabilities = ['pull', 'resolve']`,
 			createConfigPathDir: true,
 			prependExisting:     false,
 			existingFiles: map[string]string{
-				"/etc/containerd/certs.d/docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
+				"docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
 
 [host.'http://example.com:30020']
 capabilities = ['pull', 'resolve']
@@ -542,7 +536,7 @@ capabilities = ['pull', 'resolve']
 client = ['/etc/certs/xxx/client.cert', '/etc/certs/xxx/client.key']`,
 			},
 			expectedFiles: map[string]string{
-				"/etc/containerd/certs.d/_backup/docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
+				"_backup/docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
 
 [host.'http://example.com:30020']
 capabilities = ['pull', 'resolve']
@@ -555,11 +549,11 @@ capabilities = ['pull', 'resolve']
 [host.'http://bar.com:30020']
 capabilities = ['pull', 'resolve']
 client = ['/etc/certs/xxx/client.cert', '/etc/certs/xxx/client.key']`,
-				"/etc/containerd/certs.d/docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
+				"docker.io/hosts.toml": `server = 'https://registry-1.docker.io'
 
 [host.'http://127.0.0.1:5000']
 capabilities = ['pull', 'resolve']`,
-				"/etc/containerd/certs.d/foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
+				"foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
 
 [host.'http://127.0.0.1:5000']
 capabilities = ['pull', 'resolve']`,
@@ -574,7 +568,7 @@ capabilities = ['pull', 'resolve']`,
 			username:        "hello",
 			password:        "world",
 			expectedFiles: map[string]string{
-				"/etc/containerd/certs.d/foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
+				"foo.bar:5000/hosts.toml": `server = 'http://foo.bar:5000'
 
 [host.'http://127.0.0.1:5000']
 capabilities = ['pull', 'resolve']
@@ -592,29 +586,34 @@ Authorization = 'Basic aGVsbG86d29ybGQ='`,
 		t.Run(tt.name, func(t *testing.T) {
 			t.Parallel()
 
-			fs := afero.NewMemMapFs()
+			registryConfigPath := filepath.Join(t.TempDir(), "etc", "containerd", "certs.d")
 			if tt.createConfigPathDir {
-				err := fs.Mkdir(registryConfigPath, 0o755)
+				err := os.MkdirAll(registryConfigPath, 0o755)
 				require.NoError(t, err)
 			}
 			for k, v := range tt.existingFiles {
-				err := afero.WriteFile(fs, k, []byte(v), 0o644)
+				path := filepath.Join(registryConfigPath, k)
+				err := os.MkdirAll(filepath.Dir(path), 0o755)
+				require.NoError(t, err)
+				err = os.WriteFile(path, []byte(v), 0o644)
 				require.NoError(t, err)
 			}
-			err := AddMirrorConfiguration(t.Context(), fs, registryConfigPath, tt.registries, tt.mirrors, tt.resolveTags, tt.prependExisting, tt.username, tt.password)
+			err := AddMirrorConfiguration(t.Context(), registryConfigPath, tt.registries, tt.mirrors, tt.resolveTags, tt.prependExisting, tt.username, tt.password)
 			require.NoError(t, err)
-			ok, err := afero.DirExists(fs, "/etc/containerd/certs.d/_backup")
+			ok, err := dirExists(filepath.Join(registryConfigPath, "_backup"))
 			require.NoError(t, err)
 			require.True(t, ok)
 			seenExpectedFiles := maps.Clone(tt.expectedFiles)
-			err = afero.Walk(fs, registryConfigPath, func(path string, fi iofs.FileInfo, _ error) error {
+			err = filepath.Walk(registryConfigPath, func(path string, fi iofs.FileInfo, _ error) error {
 				if fi.IsDir() {
 					return nil
 				}
-				expectedContent, ok := tt.expectedFiles[path]
-				require.True(t, ok, path)
-				delete(seenExpectedFiles, path)
-				b, err := afero.ReadFile(fs, path)
+				relPath, err := filepath.Rel(registryConfigPath, path)
+				require.NoError(t, err)
+				expectedContent, ok := tt.expectedFiles[relPath]
+				require.True(t, ok)
+				delete(seenExpectedFiles, relPath)
+				b, err := os.ReadFile(path)
 				require.NoError(t, err)
 				require.Equal(t, expectedContent, string(b))
 				return nil
@@ -628,34 +627,34 @@ Authorization = 'Basic aGVsbG86d29ybGQ='`,
 func TestMirrorConfigurationInvalidMirrorURL(t *testing.T) {
 	t.Parallel()
 
-	fs := afero.NewMemMapFs()
+	configPath := filepath.Join(t.TempDir(), "etc", "containerd", "certs.d")
 	mirrors := stringListToUrlList(t, []string{"http://127.0.0.1:5000"})
 
 	registries := stringListToUrlList(t, []string{"ftp://docker.io"})
-	err := AddMirrorConfiguration(t.Context(), fs, "/etc/containerd/certs.d", registries, mirrors, true, false, "", "")
+	err := AddMirrorConfiguration(t.Context(), configPath, registries, mirrors, true, false, "", "")
 	require.EqualError(t, err, "invalid registry url scheme must be http or https: ftp://docker.io")
 
 	registries = stringListToUrlList(t, []string{"https://docker.io/foo/bar"})
-	err = AddMirrorConfiguration(t.Context(), fs, "/etc/containerd/certs.d", registries, mirrors, true, false, "", "")
+	err = AddMirrorConfiguration(t.Context(), configPath, registries, mirrors, true, false, "", "")
 	require.EqualError(t, err, "invalid registry url path has to be empty: https://docker.io/foo/bar")
 
 	registries = stringListToUrlList(t, []string{"https://docker.io?foo=bar"})
-	err = AddMirrorConfiguration(t.Context(), fs, "/etc/containerd/certs.d", registries, mirrors, true, false, "", "")
+	err = AddMirrorConfiguration(t.Context(), configPath, registries, mirrors, true, false, "", "")
 	require.EqualError(t, err, "invalid registry url query has to be empty: https://docker.io?foo=bar")
 
 	registries = stringListToUrlList(t, []string{"https://foo@docker.io"})
-	err = AddMirrorConfiguration(t.Context(), fs, "/etc/containerd/certs.d", registries, mirrors, true, false, "", "")
+	err = AddMirrorConfiguration(t.Context(), configPath, registries, mirrors, true, false, "", "")
 	require.EqualError(t, err, "invalid registry url user has to be empty: https://foo@docker.io")
 }
 
 func TestExistingHosts(t *testing.T) {
 	t.Parallel()
 
-	fs := afero.NewMemMapFs()
+	configPath := t.TempDir()
 	u, err := url.Parse("https://ghcr.io")
 	require.NoError(t, err)
 
-	eh, err := existingHosts(fs, "", *u)
+	eh, err := existingHosts(configPath, *u)
 	require.NoError(t, err)
 	require.Empty(t, eh)
 
@@ -693,10 +692,11 @@ func TestExistingHosts(t *testing.T) {
 [host."https://non-compliant-mirror.registry/v2/upstream"]
   capabilities = ["pull"]
   override_path = true`
-
-	err = afero.WriteFile(fs, filepath.Join(backupDir, u.Host, "hosts.toml"), []byte(tomlHosts), 0o644)
+	err = os.MkdirAll(filepath.Join(configPath, backupDir, u.Host), 0o755)
+	require.NoError(t, err)
+	err = os.WriteFile(filepath.Join(configPath, backupDir, u.Host, "hosts.toml"), []byte(tomlHosts), 0o644)
 	require.NoError(t, err)
-	eh, err = existingHosts(fs, "", *u)
+	eh, err = existingHosts(configPath, *u)
 	require.NoError(t, err)
 	expected := `[host.'https://mirror.registry']
 ca = '/etc/certs/mirror.pem'
@@ -736,18 +736,20 @@ override_path = true`
 func TestCleanupMirrorConfiguration(t *testing.T) {
 	t.Parallel()
 
-	fs := afero.NewMemMapFs()
-	err := afero.WriteFile(fs, filepath.Join("certs.d", backupDir, "data.txt"), []byte("hello world"), 0o644)
+	configPath := filepath.Join(t.TempDir(), "certs.d")
+	err := os.MkdirAll(filepath.Join(configPath, "_backup"), 0o755)
 	require.NoError(t, err)
-	err = afero.WriteFile(fs, filepath.Join("certs.d", "foo.bin"), []byte("hello world"), 0o644)
+	err = os.WriteFile(filepath.Join(configPath, backupDir, "data.txt"), []byte("hello world"), 0o644)
 	require.NoError(t, err)
-	err = fs.MkdirAll("certs.d/docker.io", 0o755)
+	err = os.WriteFile(filepath.Join(configPath, "foo.bin"), []byte("hello world"), 0o644)
+	require.NoError(t, err)
+	err = os.MkdirAll(filepath.Join(configPath, "docker.io"), 0o755)
 	require.NoError(t, err)
 
 	for range 2 {
-		err = CleanupMirrorConfiguration(t.Context(), fs, "certs.d")
+		err = CleanupMirrorConfiguration(t.Context(), configPath)
 		require.NoError(t, err)
-		files, err := afero.ReadDir(fs, "certs.d")
+		files, err := os.ReadDir(configPath)
 		require.NoError(t, err)
 		require.Len(t, files, 1)
 		require.Equal(t, "data.txt", files[0].Name())
@@ -756,6 +758,7 @@ func TestCleanupMirrorConfiguration(t *testing.T) {
 
 func stringListToUrlList(t *testing.T, list []string) []url.URL {
 	t.Helper()
+
 	urls := []url.URL{}
 	for _, item := range list {
 		u, err := url.Parse(item)