Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 4 additions & 117 deletions oci/skills/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"encoding/json"
"fmt"
"io"
"strings"

"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
Expand All @@ -35,7 +34,6 @@ const maxManifestLayers = 64
// Compile-time interface checks.
var (
_ RegistryClient = (*Registry)(nil)
_ oras.Target = (*storeAdapter)(nil)
_ oras.Target = (*validatingTarget)(nil)
)

Expand Down Expand Up @@ -100,10 +98,8 @@ func (r *Registry) Push(ctx context.Context, store *Store, artifactDigest digest
return err
}

adapter := newStoreAdapter(store)

// Resolve the artifact to get its descriptor for oras.CopyGraph
desc, err := adapter.descriptorFromDigest(ctx, artifactDigest)
// Resolve the artifact to get its full descriptor from the OCI store.
desc, err := store.Target().Resolve(ctx, artifactDigest.String())
if err != nil {
return fmt.Errorf("resolving artifact descriptor: %w", err)
}
Expand All @@ -114,7 +110,7 @@ func (r *Registry) Push(ctx context.Context, store *Store, artifactDigest digest
}

// Copy the content graph (blobs → manifests → index) to the remote
if err := oras.CopyGraph(ctx, adapter, target, desc, oras.DefaultCopyGraphOptions); err != nil {
if err := oras.CopyGraph(ctx, store.Target(), target, desc, oras.DefaultCopyGraphOptions); err != nil {
return fmt.Errorf("pushing to registry: %w", err)
}

Expand All @@ -139,8 +135,7 @@ func (r *Registry) Pull(ctx context.Context, store *Store, ref string) (digest.D
return "", fmt.Errorf("getting repository: %w", err)
}

adapter := newStoreAdapter(store)
validated := newValidatingTarget(adapter)
validated := newValidatingTarget(store.Target())

// Copy from remote to the validated local store
desc, err := oras.Copy(
Expand All @@ -159,114 +154,6 @@ func (r *Registry) Pull(ctx context.Context, store *Store, ref string) (digest.D
return desc.Digest, nil
}

// storeAdapter wraps a Store to satisfy ORAS content interfaces,
// routing content by media type to the appropriate Store methods.
type storeAdapter struct {
store *Store
}

func newStoreAdapter(store *Store) *storeAdapter {
return &storeAdapter{store: store}
}

// Push routes content to PutManifest or PutBlob based on media type.
// Verifies the content digest matches the expected descriptor.
func (a *storeAdapter) Push(ctx context.Context, expected ocispec.Descriptor, content io.Reader) error {
data, err := io.ReadAll(io.LimitReader(content, MaxBlobSize+1))
if err != nil {
return fmt.Errorf("reading content: %w", err)
}
if int64(len(data)) > MaxBlobSize {
return fmt.Errorf("content exceeds maximum size of %d bytes", MaxBlobSize)
}

// Verify digest integrity
actual := digest.FromBytes(data)
if actual != expected.Digest {
return fmt.Errorf("digest mismatch: expected %s, got %s", expected.Digest, actual)
}

if isManifestMediaType(expected.MediaType) {
_, err = a.store.PutManifest(ctx, data)
} else {
_, err = a.store.PutBlob(ctx, data)
}
return err
}

// Fetch retrieves content from the store by descriptor.
func (a *storeAdapter) Fetch(ctx context.Context, target ocispec.Descriptor) (io.ReadCloser, error) {
var data []byte
var err error

if isManifestMediaType(target.MediaType) {
data, err = a.store.GetManifest(ctx, target.Digest)
} else {
data, err = a.store.GetBlob(ctx, target.Digest)
}
if err != nil {
return nil, err
}
return io.NopCloser(bytes.NewReader(data)), nil
}

// Exists checks whether content exists in the store.
func (a *storeAdapter) Exists(ctx context.Context, target ocispec.Descriptor) (bool, error) {
if isManifestMediaType(target.MediaType) {
_, err := a.store.GetManifest(ctx, target.Digest)
if err != nil {
if strings.Contains(err.Error(), "not found") {
return false, nil
}
return false, err
}
return true, nil
}
_, err := a.store.GetBlob(ctx, target.Digest)
if err != nil {
if strings.Contains(err.Error(), "not found") {
return false, nil
}
return false, err
}
return true, nil
}

// Resolve resolves a reference (tag or digest) to a full descriptor.
func (a *storeAdapter) Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) {
d, err := a.store.Resolve(ctx, reference)
if err != nil {
return ocispec.Descriptor{}, err
}
return a.descriptorFromDigest(ctx, d)
}

// Tag associates a reference with a descriptor in the store.
func (a *storeAdapter) Tag(ctx context.Context, desc ocispec.Descriptor, reference string) error {
return a.store.Tag(ctx, desc.Digest, reference)
}

// descriptorFromDigest reads content to build a full OCI descriptor.
func (a *storeAdapter) descriptorFromDigest(ctx context.Context, d digest.Digest) (ocispec.Descriptor, error) {
data, err := a.store.GetManifest(ctx, d)
if err != nil {
return ocispec.Descriptor{}, fmt.Errorf("reading content for descriptor: %w", err)
}

var header struct {
MediaType string `json:"mediaType"`
}
if err := json.Unmarshal(data, &header); err != nil {
return ocispec.Descriptor{}, fmt.Errorf("parsing media type: %w", err)
}

return ocispec.Descriptor{
MediaType: header.MediaType,
Digest: d,
Size: int64(len(data)),
}, nil
}

// validatingTarget wraps an oras.Target to enforce size and count limits
// on pushed content. This prevents OOM and resource exhaustion from
// malicious registries during pull operations.
Expand Down
77 changes: 0 additions & 77 deletions oci/skills/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,83 +212,6 @@ func TestValidateManifestCounts(t *testing.T) {
})
}

// --- storeAdapter tests ---

func TestStoreAdapter_PushFetchRoundTrip(t *testing.T) {
t.Parallel()

ctx := t.Context()
store, err := NewStore(t.TempDir())
require.NoError(t, err)
adapter := newStoreAdapter(store)

// Push a blob
blobContent := []byte("test blob data")
blobDesc := ocispec.Descriptor{
MediaType: "application/octet-stream",
Digest: digest.FromBytes(blobContent),
Size: int64(len(blobContent)),
}
err = adapter.Push(ctx, blobDesc, bytes.NewReader(blobContent))
require.NoError(t, err)

// Fetch it back
rc, err := adapter.Fetch(ctx, blobDesc)
require.NoError(t, err)
defer rc.Close()
var buf bytes.Buffer
_, err = buf.ReadFrom(rc)
require.NoError(t, err)
assert.Equal(t, blobContent, buf.Bytes())

// Exists
exists, err := adapter.Exists(ctx, blobDesc)
require.NoError(t, err)
assert.True(t, exists)

// Non-existent
fakeDesc := ocispec.Descriptor{
MediaType: "application/octet-stream",
Digest: digest.FromString("fake"),
Size: 4,
}
exists, err = adapter.Exists(ctx, fakeDesc)
require.NoError(t, err)
assert.False(t, exists)
}

func TestStoreAdapter_ResolveAndTag(t *testing.T) {
t.Parallel()

ctx := t.Context()
store, err := NewStore(t.TempDir())
require.NoError(t, err)
adapter := newStoreAdapter(store)

// Build and store a manifest
manifest := ocispec.Manifest{MediaType: ocispec.MediaTypeImageManifest}
manifestBytes, err := json.Marshal(manifest)
require.NoError(t, err)

manifestDigest, err := store.PutManifest(ctx, manifestBytes)
require.NoError(t, err)

// Tag via adapter
desc := ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageManifest,
Digest: manifestDigest,
Size: int64(len(manifestBytes)),
}
err = adapter.Tag(ctx, desc, "my-tag")
require.NoError(t, err)

// Resolve via adapter
resolved, err := adapter.Resolve(ctx, "my-tag")
require.NoError(t, err)
assert.Equal(t, manifestDigest, resolved.Digest)
assert.Equal(t, ocispec.MediaTypeImageManifest, resolved.MediaType)
}

// --- Integration tests using in-memory target ---

func newTestRegistry(t *testing.T, remoteStore *memory.Store) *Registry {
Expand Down
Loading
Loading