Repository: spegel-org/spegel
Pull request #864 (2025-05-08 10:21:01)

Rename OCI client to store
I do not know why I called the interface a client. If it confuses me at times it probably confuses others too. This allows us to implement an actual OCI client without having the names collide in the package. Changing the name from client to store is more descriptive for what the interface actually does.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/spegel-org/spegel/pull/864?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 `54.16667%` with `11 lines` in your changes missing coverage. Please review.

| [Files with missing lines](https://app.codecov.io/gh/spegel-org/spegel/pull/864?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 |
|---|---|---|
| [pkg/registry/registry.go](https://app.codecov.io/gh/spegel-org/spegel/pull/864?src=pr&el=tree&filepath=pkg%2Fregistry%2Fregistry.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org#diff-cGtnL3JlZ2lzdHJ5L3JlZ2lzdHJ5Lmdv) | 37.50% | [5 Missing :warning: ](https://app.codecov.io/gh/spegel-org/spegel/pull/864?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org) |
| [main.go](https://app.codecov.io/gh/spegel-org/spegel/pull/864?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% | [4 Missing :warning: ](https://app.codecov.io/gh/spegel-org/spegel/pull/864?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org) |
| [pkg/state/state.go](https://app.codecov.io/gh/spegel-org/spegel/pull/864?src=pr&el=tree&filepath=pkg%2Fstate%2Fstate.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org#diff-cGtnL3N0YXRlL3N0YXRlLmdv) | 77.77% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/spegel-org/spegel/pull/864?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/864?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/864?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% <ø> (ø)` | |
| [pkg/oci/memory.go](https://app.codecov.io/gh/spegel-org/spegel/pull/864?src=pr&el=tree&filepath=pkg%2Foci%2Fmemory.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org#diff-cGtnL29jaS9tZW1vcnkuZ28=) | `83.54% <ø> (ø)` | |
| [pkg/oci/oci.go](https://app.codecov.io/gh/spegel-org/spegel/pull/864?src=pr&el=tree&filepath=pkg%2Foci%2Foci.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org#diff-cGtnL29jaS9vY2kuZ28=) | `70.11% <100.00%> (ø)` | |
| [pkg/state/state.go](https://app.codecov.io/gh/spegel-org/spegel/pull/864?src=pr&el=tree&filepath=pkg%2Fstate%2Fstate.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org#diff-cGtnL3N0YXRlL3N0YXRlLmdv) | `49.00% <77.77%> (ø)` | |
| [main.go](https://app.codecov.io/gh/spegel-org/spegel/pull/864?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%> (ø)` | |
| [pkg/registry/registry.go](https://app.codecov.io/gh/spegel-org/spegel/pull/864?src=pr&el=tree&filepath=pkg%2Fregistry%2Fregistry.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org#diff-cGtnL3JlZ2lzdHJ5L3JlZ2lzdHJ5Lmdv) | `64.53% <37.50%> (ø)` | |

... and [1 file with indirect coverage changes](https://app.codecov.io/gh/spegel-org/spegel/pull/864/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spegel-org)

<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 5fd2b5c..5d64399 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - [#852](https://github.com/spegel-org/spegel/pull/852) Remove use of Afero in Containerd config.
 - [#854](https://github.com/spegel-org/spegel/pull/854) Implement unit tests for cleanup logic.
 - [#860](https://github.com/spegel-org/spegel/pull/860) Update Go to 1.24.2.
+- [#864](https://github.com/spegel-org/spegel/pull/864) Rename OCI client to store.
 
 ### Deprecated
 
diff --git a/main.go b/main.go
index 0453943..3a2d017 100644
--- a/main.go
+++ b/main.go
@@ -141,12 +141,12 @@ func registryCommand(ctx context.Context, args *RegistryCmd) (err error) {
 		return err
 	}
 
-	// OCI Client
-	ociClient, err := oci.NewContainerd(args.ContainerdSock, args.ContainerdNamespace, args.ContainerdRegistryConfigPath, args.MirroredRegistries, oci.WithContentPath(args.ContainerdContentPath))
+	// OCI Store
+	ociStore, err := oci.NewContainerd(args.ContainerdSock, args.ContainerdNamespace, args.ContainerdRegistryConfigPath, args.MirroredRegistries, oci.WithContentPath(args.ContainerdContentPath))
 	if err != nil {
 		return err
 	}
-	err = ociClient.Verify(ctx)
+	err = ociStore.Verify(ctx)
 	if err != nil {
 		return err
 	}
@@ -173,7 +173,7 @@ func registryCommand(ctx context.Context, args *RegistryCmd) (err error) {
 
 	// State tracking
 	g.Go(func() error {
-		err := state.Track(ctx, ociClient, router, args.ResolveLatestTag)
+		err := state.Track(ctx, ociStore, router, args.ResolveLatestTag)
 		if err != nil {
 			return err
 		}
@@ -188,7 +188,7 @@ func registryCommand(ctx context.Context, args *RegistryCmd) (err error) {
 		registry.WithLogger(log),
 		registry.WithBasicAuth(username, password),
 	}
-	reg, err := registry.NewRegistry(ociClient, router, registryOpts...)
+	reg, err := registry.NewRegistry(ociStore, router, registryOpts...)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/oci/containerd.go b/pkg/oci/containerd.go
index 2cf25a5..c01261a 100644
--- a/pkg/oci/containerd.go
+++ b/pkg/oci/containerd.go
@@ -34,7 +34,7 @@ const (
 	backupDir = "_backup"
 )
 
-var _ Client = &Containerd{}
+var _ Store = &Containerd{}
 
 type Containerd struct {
 	contentPath        string
diff --git a/pkg/oci/memory.go b/pkg/oci/memory.go
index 325cc66..8d69f14 100644
--- a/pkg/oci/memory.go
+++ b/pkg/oci/memory.go
@@ -11,7 +11,7 @@ import (
 	"github.com/opencontainers/go-digest"
 )
 
-var _ Client = &Memory{}
+var _ Store = &Memory{}
 
 type Memory struct {
 	blobs  map[digest.Digest][]byte
diff --git a/pkg/oci/oci.go b/pkg/oci/oci.go
index d3a8a95..8ec1329 100644
--- a/pkg/oci/oci.go
+++ b/pkg/oci/oci.go
@@ -30,8 +30,8 @@ type ImageEvent struct {
 	Type  EventType
 }
 
-type Client interface {
-	// Name returns the name of the Client implementation.
+type Store interface {
+	// Name returns the name of the store implementation.
 	Name() string
 
 	// Verify checks that all expected configuration is set.
@@ -94,10 +94,10 @@ func DetermineMediaType(b []byte) (string, error) {
 	return "", errors.New("not able to determine media type")
 }
 
-func WalkImage(ctx context.Context, client Client, img Image) ([]string, error) {
+func WalkImage(ctx context.Context, store Store, img Image) ([]string, error) {
 	keys := []string{}
 	err := walk(ctx, []digest.Digest{img.Digest}, func(dgst digest.Digest) ([]digest.Digest, error) {
-		b, mt, err := client.GetManifest(ctx, dgst)
+		b, mt, err := store.GetManifest(ctx, dgst)
 		if err != nil {
 			return nil, err
 		}
@@ -110,7 +110,7 @@ func WalkImage(ctx context.Context, client Client, img Image) ([]string, error)
 			}
 			manifestDgsts := []digest.Digest{}
 			for _, m := range idx.Manifests {
-				_, err := client.Size(ctx, m.Digest)
+				_, err := store.Size(ctx, m.Digest)
 				if errors.Is(err, ErrNotFound) {
 					continue
 				}
diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go
index d7383a1..e4c5a0f 100644
--- a/pkg/registry/registry.go
+++ b/pkg/registry/registry.go
@@ -99,7 +99,7 @@ type Registry struct {
 	client           *http.Client
 	bufferPool       *sync.Pool
 	log              logr.Logger
-	ociClient        oci.Client
+	ociStore         oci.Store
 	router           routing.Router
 	username         string
 	password         string
@@ -108,7 +108,7 @@ type Registry struct {
 	resolveLatestTag bool
 }
 
-func NewRegistry(ociClient oci.Client, router routing.Router, opts ...RegistryOption) (*Registry, error) {
+func NewRegistry(ociStore oci.Store, router routing.Router, opts ...RegistryOption) (*Registry, error) {
 	transport, ok := http.DefaultTransport.(*http.Transport)
 	if !ok {
 		return nil, errors.New("default transporn is not of type http.Transport")
@@ -134,7 +134,7 @@ func NewRegistry(ociClient oci.Client, router routing.Router, opts ...RegistryOp
 		},
 	}
 	r := &Registry{
-		ociClient:        ociClient,
+		ociStore:         ociStore,
 		router:           router,
 		client:           cfg.Client,
 		log:              cfg.Log,
@@ -207,9 +207,9 @@ func (r *Registry) registryHandler(rw mux.ResponseWriter, req *http.Request) {
 		// If content is present locally we should skip the mirroring and just serve it.
 		var ociErr error
 		if dist.Digest == "" {
-			_, ociErr = r.ociClient.Resolve(req.Context(), dist.Reference())
+			_, ociErr = r.ociStore.Resolve(req.Context(), dist.Reference())
 		} else {
-			_, ociErr = r.ociClient.Size(req.Context(), dist.Digest)
+			_, ociErr = r.ociStore.Size(req.Context(), dist.Digest)
 		}
 		if ociErr != nil {
 			rw.SetHandler("mirror")
@@ -294,14 +294,14 @@ func (r *Registry) handleMirror(rw mux.ResponseWriter, req *http.Request, dist o
 
 func (r *Registry) handleManifest(rw mux.ResponseWriter, req *http.Request, dist oci.DistributionPath) {
 	if dist.Digest == "" {
-		dgst, err := r.ociClient.Resolve(req.Context(), dist.Reference())
+		dgst, err := r.ociStore.Resolve(req.Context(), dist.Reference())
 		if err != nil {
 			rw.WriteError(http.StatusNotFound, fmt.Errorf("could not get digest for image %s: %w", dist.Reference(), err))
 			return
 		}
 		dist.Digest = dgst
 	}
-	b, mediaType, err := r.ociClient.GetManifest(req.Context(), dist.Digest)
+	b, mediaType, err := r.ociStore.GetManifest(req.Context(), dist.Digest)
 	if err != nil {
 		rw.WriteError(http.StatusNotFound, fmt.Errorf("could not get manifest content for digest %s: %w", dist.Digest.String(), err))
 		return
@@ -320,7 +320,7 @@ func (r *Registry) handleManifest(rw mux.ResponseWriter, req *http.Request, dist
 }
 
 func (r *Registry) handleBlob(rw mux.ResponseWriter, req *http.Request, dist oci.DistributionPath) {
-	size, err := r.ociClient.Size(req.Context(), dist.Digest)
+	size, err := r.ociStore.Size(req.Context(), dist.Digest)
 	if err != nil {
 		rw.WriteError(http.StatusInternalServerError, fmt.Errorf("could not determine size of blob with digest %s: %w", dist.Digest.String(), err))
 		return
@@ -333,7 +333,7 @@ func (r *Registry) handleBlob(rw mux.ResponseWriter, req *http.Request, dist oci
 		return
 	}
 
-	rc, err := r.ociClient.GetBlob(req.Context(), dist.Digest)
+	rc, err := r.ociStore.GetBlob(req.Context(), dist.Digest)
 	if err != nil {
 		rw.WriteError(http.StatusInternalServerError, fmt.Errorf("could not get reader for blob with digest %s: %w", dist.Digest.String(), err))
 		return
diff --git a/pkg/state/state.go b/pkg/state/state.go
index 0bfa33b..73232ea 100644
--- a/pkg/state/state.go
+++ b/pkg/state/state.go
@@ -14,9 +14,9 @@ import (
 	"github.com/spegel-org/spegel/pkg/routing"
 )
 
-func Track(ctx context.Context, ociClient oci.Client, router routing.Router, resolveLatestTag bool) error {
+func Track(ctx context.Context, ociStore oci.Store, router routing.Router, resolveLatestTag bool) error {
 	log := logr.FromContextOrDiscard(ctx)
-	eventCh, errCh, err := ociClient.Subscribe(ctx)
+	eventCh, errCh, err := ociStore.Subscribe(ctx)
 	if err != nil {
 		return err
 	}
@@ -32,7 +32,7 @@ func Track(ctx context.Context, ociClient oci.Client, router routing.Router, res
 			return nil
 		case <-tickerCh:
 			log.Info("running scheduled image state update")
-			if err := all(ctx, ociClient, router, resolveLatestTag); err != nil {
+			if err := all(ctx, ociStore, router, resolveLatestTag); err != nil {
 				log.Error(err, "received errors when updating all images")
 				continue
 			}
@@ -41,7 +41,7 @@ func Track(ctx context.Context, ociClient oci.Client, router routing.Router, res
 				return errors.New("image event channel closed")
 			}
 			log.Info("received image event", "image", event.Image.String(), "type", event.Type)
-			if _, err := update(ctx, ociClient, router, event, false, resolveLatestTag); err != nil {
+			if _, err := update(ctx, ociStore, router, event, false, resolveLatestTag); err != nil {
 				log.Error(err, "received error when updating image")
 				continue
 			}
@@ -54,9 +54,9 @@ func Track(ctx context.Context, ociClient oci.Client, router routing.Router, res
 	}
 }
 
-func all(ctx context.Context, ociClient oci.Client, router routing.Router, resolveLatestTag bool) error {
+func all(ctx context.Context, ociStore oci.Store, router routing.Router, resolveLatestTag bool) error {
 	log := logr.FromContextOrDiscard(ctx).V(4)
-	imgs, err := ociClient.ListImages(ctx)
+	imgs, err := ociStore.ListImages(ctx)
 	if err != nil {
 		return err
 	}
@@ -74,7 +74,7 @@ func all(ctx context.Context, ociClient oci.Client, router routing.Router, resol
 		// update function from setting metrics values.
 		event := oci.ImageEvent{Image: img, Type: oci.UpdateEvent}
 		log.Info("sync image event", "image", event.Image.String(), "type", event.Type)
-		keyTotal, err := update(ctx, ociClient, router, event, skipDigests, resolveLatestTag)
+		keyTotal, err := update(ctx, ociStore, router, event, skipDigests, resolveLatestTag)
 		if err != nil {
 			errs = append(errs, err)
 			continue
@@ -91,7 +91,7 @@ func all(ctx context.Context, ociClient oci.Client, router routing.Router, resol
 	return errors.Join(errs...)
 }
 
-func update(ctx context.Context, ociClient oci.Client, router routing.Router, event oci.ImageEvent, skipDigests, resolveLatestTag bool) (int, error) {
+func update(ctx context.Context, ociStore oci.Store, router routing.Router, event oci.ImageEvent, skipDigests, resolveLatestTag bool) (int, error) {
 	keys := []string{}
 	//nolint: staticcheck // Simplify in future.
 	if !(!resolveLatestTag && event.Image.IsLatestTag()) {
@@ -108,7 +108,7 @@ func update(ctx context.Context, ociClient oci.Client, router routing.Router, ev
 		return 0, nil
 	}
 	if !skipDigests {
-		dgsts, err := oci.WalkImage(ctx, ociClient, event.Image)
+		dgsts, err := oci.WalkImage(ctx, ociStore, event.Image)
 		if err != nil {
 			return 0, fmt.Errorf("could not get digests for image %s: %w", event.Image.String(), err)
 		}


Test patch:
diff --git a/pkg/oci/oci_test.go b/pkg/oci/oci_test.go
index 73f322c..a0c1a7a 100644
--- a/pkg/oci/oci_test.go
+++ b/pkg/oci/oci_test.go
@@ -96,28 +96,28 @@ func TestOCIClient(t *testing.T) {
 		memoryClient.AddBlob(v, k)
 	}
 
-	for _, ociClient := range []Client{remoteContainerd, localContainerd, memoryClient} {
-		t.Run(ociClient.Name(), func(t *testing.T) {
+	for _, ociStore := range []Store{remoteContainerd, localContainerd, memoryClient} {
+		t.Run(ociStore.Name(), func(t *testing.T) {
 			t.Parallel()
 
-			b, mt, err := ociClient.GetManifest(ctx, digest.FromString("foo"))
+			b, mt, err := ociStore.GetManifest(ctx, digest.FromString("foo"))
 			require.Empty(t, b)
 			require.Empty(t, mt)
 			require.ErrorIs(t, err, ErrNotFound)
-			rc, err := ociClient.GetBlob(ctx, digest.FromString("foo"))
+			rc, err := ociStore.GetBlob(ctx, digest.FromString("foo"))
 			require.Empty(t, rc)
 			require.ErrorIs(t, err, ErrNotFound)
-			size, err := ociClient.Size(ctx, digest.FromString("foo"))
+			size, err := ociStore.Size(ctx, digest.FromString("foo"))
 			require.Empty(t, size)
 			require.ErrorIs(t, err, ErrNotFound)
 
-			imgs, err := ociClient.ListImages(ctx)
+			imgs, err := ociStore.ListImages(ctx)
 			require.NoError(t, err)
 			require.Len(t, imgs, 5)
 			for _, img := range imgs {
 				tagName, ok := img.TagName()
 				require.True(t, ok)
-				_, err := ociClient.Resolve(ctx, tagName)
+				_, err := ociStore.Resolve(ctx, tagName)
 				require.NoError(t, err)
 			}
 
@@ -128,10 +128,10 @@ func TestOCIClient(t *testing.T) {
 			}
 			tagName, ok := noPlatformImg.TagName()
 			require.True(t, ok)
-			dgst, err := ociClient.Resolve(ctx, tagName)
+			dgst, err := ociStore.Resolve(ctx, tagName)
 			require.NoError(t, err)
 			noPlatformImg.Digest = dgst
-			_, err = WalkImage(ctx, ociClient, noPlatformImg)
+			_, err = WalkImage(ctx, ociStore, noPlatformImg)
 			require.EqualError(t, err, "failed to walk image manifests: could not find any platforms with local content in manifest sha256:addc990c58744bdf96364fe89bd4aab38b1e824d51c688edb36c75247cd45fa9")
 
 			contentTests := []struct {
@@ -164,16 +164,16 @@ func TestOCIClient(t *testing.T) {
 				t.Run(tt.mediaType, func(t *testing.T) {
 					t.Parallel()
 
-					size, err := ociClient.Size(ctx, tt.dgst)
+					size, err := ociStore.Size(ctx, tt.dgst)
 					require.NoError(t, err)
 					require.Equal(t, tt.size, size)
 					if tt.mediaType != ocispec.MediaTypeImageLayer {
-						b, mediaType, err := ociClient.GetManifest(ctx, tt.dgst)
+						b, mediaType, err := ociStore.GetManifest(ctx, tt.dgst)
 						require.NoError(t, err)
 						require.Equal(t, tt.mediaType, mediaType)
 						require.Equal(t, blobs[tt.dgst], b)
 					} else {
-						rc, err := ociClient.GetBlob(ctx, tt.dgst)
+						rc, err := ociStore.GetBlob(ctx, tt.dgst)
 						require.NoError(t, err)
 						defer rc.Close()
 						b, err := io.ReadAll(rc)
@@ -287,7 +287,7 @@ func TestOCIClient(t *testing.T) {
 
 					img, err := ParseImageRequireDigest(tt.imageName, digest.Digest(tt.imageDigest))
 					require.NoError(t, err)
-					keys, err := WalkImage(ctx, ociClient, img)
+					keys, err := WalkImage(ctx, ociStore, img)
 					require.NoError(t, err)
 					require.Equal(t, tt.expectedKeys, keys)
 				})
diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go
index 53837c3..7124883 100644
--- a/pkg/state/state_test.go
+++ b/pkg/state/state_test.go
@@ -25,7 +25,7 @@ import (
 
 func TestTrack(t *testing.T) {
 	t.Parallel()
-	ociClient := oci.NewMemory()
+	ociStore := oci.NewMemory()
 
 	imgRefs := []string{
 		"docker.io/library/ubuntu:latest",
@@ -48,10 +48,10 @@ func TestTrack(t *testing.T) {
 		_, err = hash.Write(b)
 		require.NoError(t, err)
 		dgst := digest.NewDigest(digest.SHA256, hash)
-		ociClient.AddBlob(b, dgst)
+		ociStore.AddBlob(b, dgst)
 		img, err := oci.ParseImageRequireDigest(imageStr, dgst)
 		require.NoError(t, err)
-		ociClient.AddImage(img)
+		ociStore.AddImage(img)
 
 		imgs = append(imgs, img)
 	}
@@ -80,7 +80,7 @@ func TestTrack(t *testing.T) {
 			router := routing.NewMemoryRouter(map[string][]netip.AddrPort{}, netip.MustParseAddrPort("127.0.0.1:5000"))
 			g, gCtx := errgroup.WithContext(ctx)
 			g.Go(func() error {
-				return Track(gCtx, ociClient, router, tt.resolveLatestTag)
+				return Track(gCtx, ociStore, router, tt.resolveLatestTag)
 			})
 			time.Sleep(100 * time.Millisecond)