-
Notifications
You must be signed in to change notification settings - Fork 13
feat(copy): introduces CopyGraphOptions with events support #145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
leonardochaia
wants to merge
33
commits into
oras-project:main
Choose a base branch
from
leonardochaia:feat/improve-copy-performance
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 13 commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
6817504
feat(copy): support mounting existing descriptors from other reposito…
leonardochaia b453fbc
fix: standard header
leonardochaia 6a7a1e9
chore: adds repository tests
leonardochaia 88c6793
fix: copy test
leonardochaia 4c52467
Merge branch 'main' into feat/improve-copy-performance
leonardochaia 36342c9
Merge branch 'main' into feat/improve-copy-performance
leonardochaia dbe3509
chore: removes mounting support to be implemented in separate PR
leonardochaia 571820c
refactor: renames class and events as per review
leonardochaia 138120e
chore: adds copy tests
leonardochaia 77e8079
chore: adds overload to prevent breaking change
leonardochaia 06fd442
chore: introduces overloads to keep previous signature on CopyGraphOp…
leonardochaia 5787c04
fix: copy signature
leonardochaia c9be0a2
Merge branch 'main' into feat/improve-copy-performance
leonardochaia 77ba179
refactor: introduces CopyOptions
leonardochaia ecaf1b1
Merge remote-tracking branch 'origin/feat/improve-copy-performance' i…
leonardochaia de9ee0d
Merge branch 'main' into feat/improve-copy-performance
leonardochaia 6ef7e74
chore: adds license
leonardochaia be7b1c5
Merge remote-tracking branch 'origin/feat/improve-copy-performance' i…
leonardochaia a12ff78
chore: adds more tests from oras-go
leonardochaia a7a1baf
chore: adds tests from oras-go
leonardochaia 493a8d9
doc: adds comments to new option structs
leonardochaia 9e40655
refactor: makes copy events async
leonardochaia 68220bd
feat: introduces `InvokeAsync` extension method to support asynchrono…
leonardochaia e35fc7e
refactor: delegate InvokeAsync to execute handlers in parallel
leonardochaia 827f62b
refactor: Copy events to include sync and async variants.
leonardochaia 6d1b0ba
Merge branch 'main' into feat/improve-copy-performance
Wwwsylvia 2372ac6
address comments
Wwwsylvia f7b326d
fix tests
Wwwsylvia fe10a1a
rename
Wwwsylvia c1c7182
nit
Wwwsylvia 7b31ec3
Merge branch 'main' into feat/improve-copy-performance
Wwwsylvia b9f93d0
refactor
Wwwsylvia 20ac068
update tests
Wwwsylvia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// Copyright The ORAS Authors. | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
using System; | ||
using OrasProject.Oras.Oci; | ||
|
||
namespace OrasProject.Oras; | ||
|
||
public struct CopyGraphOptions | ||
{ | ||
public event Action<Descriptor> PreCopy; | ||
|
||
public event Action<Descriptor> PostCopy; | ||
|
||
public event Action<Descriptor> CopySkipped; | ||
leonardochaia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
internal void OnPreCopy(Descriptor descriptor) | ||
{ | ||
PreCopy?.Invoke(descriptor); | ||
} | ||
|
||
internal void OnPostCopy(Descriptor descriptor) | ||
{ | ||
PostCopy?.Invoke(descriptor); | ||
} | ||
|
||
internal void OnCopySkipped(Descriptor descriptor) | ||
{ | ||
CopySkipped?.Invoke(descriptor); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -16,6 +16,7 @@ | |||||
using System.Text; | ||||||
using System.Text.Json; | ||||||
using Xunit; | ||||||
using Index = OrasProject.Oras.Oci.Index; | ||||||
|
||||||
namespace OrasProject.Oras.Tests; | ||||||
|
||||||
|
@@ -142,4 +143,146 @@ public async Task CanCopyBetweenMemoryTargets() | |||||
|
||||||
} | ||||||
} | ||||||
|
||||||
[Fact] | ||||||
public async Task TestCopyGraph_FullCopy() | ||||||
{ | ||||||
var src = new MemoryStore(); | ||||||
var dst = new MemoryStore(); | ||||||
|
||||||
// Generate test content | ||||||
var blobs = new List<byte[]>(); | ||||||
var descs = new List<Descriptor>(); | ||||||
|
||||||
void AppendBlob(string mediaType, byte[] blob) | ||||||
{ | ||||||
blobs.Add(blob); | ||||||
var desc = new Descriptor | ||||||
{ | ||||||
MediaType = mediaType, | ||||||
Digest = Digest.ComputeSHA256(blob), | ||||||
Size = blob.Length | ||||||
}; | ||||||
descs.Add(desc); | ||||||
} | ||||||
|
||||||
void GenerateManifest(Descriptor config, params Descriptor[] layers) | ||||||
{ | ||||||
var manifest = new Manifest | ||||||
{ | ||||||
Config = config, | ||||||
Layers = layers.ToList() | ||||||
}; | ||||||
var manifestBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(manifest)); | ||||||
AppendBlob(MediaType.ImageManifest, manifestBytes); | ||||||
} | ||||||
|
||||||
void GenerateIndex(params Descriptor[] manifests) | ||||||
{ | ||||||
var index = new Index | ||||||
{ | ||||||
Manifests = manifests.ToList() | ||||||
}; | ||||||
var indexBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(index)); | ||||||
AppendBlob(MediaType.ImageIndex, indexBytes); | ||||||
} | ||||||
|
||||||
// Append blobs and generate manifests and indices | ||||||
var getBytes = (string data) => Encoding.UTF8.GetBytes(data); | ||||||
AppendBlob(MediaType.ImageConfig, getBytes("config")); // Blob 0 | ||||||
AppendBlob(MediaType.ImageLayer, getBytes("foo")); // Blob 1 | ||||||
AppendBlob(MediaType.ImageLayer, getBytes("bar")); // Blob 2 | ||||||
AppendBlob(MediaType.ImageLayer, getBytes("hello")); // Blob 3 | ||||||
GenerateManifest(descs[0], descs[1], descs[2]); // Blob 4 | ||||||
GenerateManifest(descs[0], descs[3]); // Blob 5 | ||||||
GenerateManifest(descs[0], descs[1], descs[2], descs[3]); // Blob 6 | ||||||
GenerateIndex(descs[4], descs[5]); // Blob 7 | ||||||
GenerateIndex(descs[6]); // Blob 8 | ||||||
GenerateIndex(); // Blob 9 | ||||||
GenerateIndex(descs[7], descs[8], descs[9]); // Blob 10 | ||||||
|
||||||
var root = descs[^1]; // The last descriptor as the root | ||||||
|
||||||
// Push blobs to the source memory store | ||||||
for (int i = 0; i < blobs.Count; i++) | ||||||
{ | ||||||
await src.PushAsync(descs[i], new MemoryStream(blobs[i]), CancellationToken.None); | ||||||
} | ||||||
|
||||||
// Set up tracking storage wrappers for verification | ||||||
var srcTracker = new StorageTracker(src); | ||||||
var dstTracker = new StorageTracker(dst); | ||||||
|
||||||
// Perform the copy graph operation | ||||||
var copyOptions = new CopyGraphOptions(); | ||||||
await srcTracker.CopyGraphAsync(dstTracker, root, copyOptions, CancellationToken.None); | ||||||
|
||||||
// Verify contents in the destination | ||||||
foreach (var (desc, blob) in descs.Zip(blobs, Tuple.Create)) | ||||||
{ | ||||||
Assert.True(await dst.ExistsAsync(desc, CancellationToken.None), $"Blob {desc.Digest} should exist in destination."); | ||||||
var fetchedContent = await dst.FetchAsync(desc, CancellationToken.None); | ||||||
using var memoryStream = new MemoryStream(); | ||||||
await fetchedContent.CopyToAsync(memoryStream); | ||||||
Assert.Equal(blob, memoryStream.ToArray()); | ||||||
} | ||||||
|
||||||
// Verify API counts | ||||||
// REMARKS: FetchCount should equal to blobs.Count | ||||||
// but since there's no caching implemented, it is not | ||||||
Assert.Equal(18, srcTracker.FetchCount); | ||||||
Assert.Equal(0, srcTracker.PushCount); | ||||||
Assert.Equal(0, srcTracker.ExistsCount); | ||||||
Assert.Equal(0, dstTracker.FetchCount); | ||||||
Assert.Equal(blobs.Count, dstTracker.PushCount); | ||||||
|
||||||
// REMARKS: ExistsCount should equal to blobs.Count | ||||||
// but since there's no caching implemented, it is not | ||||||
Assert.Equal(16, dstTracker.ExistsCount); | ||||||
} | ||||||
|
||||||
private class StorageTracker : ITarget | ||||||
{ | ||||||
private readonly ITarget _storage; | ||||||
|
||||||
public int FetchCount { get; private set; } | ||||||
public int PushCount { get; private set; } | ||||||
public int ExistsCount { get; private set; } | ||||||
|
||||||
public IList<string> Fetched { get; } = []; | ||||||
Wwwsylvia marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The property 'Fetched' is initialized with an empty array literal, which results in an immutable collection. Consider initializing it with a modifiable collection such as 'new List()' to allow dynamic additions.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||
|
||||||
public StorageTracker(ITarget storage) | ||||||
{ | ||||||
_storage = storage; | ||||||
} | ||||||
|
||||||
public async Task<bool> ExistsAsync(Descriptor desc, CancellationToken cancellationToken) | ||||||
{ | ||||||
ExistsCount++; | ||||||
return await _storage.ExistsAsync(desc, cancellationToken); | ||||||
} | ||||||
|
||||||
public async Task<Stream> FetchAsync(Descriptor desc, CancellationToken cancellationToken) | ||||||
{ | ||||||
FetchCount++; | ||||||
Fetched.Add(desc.Digest); | ||||||
return await _storage.FetchAsync(desc, cancellationToken); | ||||||
} | ||||||
|
||||||
public async Task PushAsync(Descriptor desc, Stream content, CancellationToken cancellationToken) | ||||||
{ | ||||||
PushCount++; | ||||||
await _storage.PushAsync(desc, content, cancellationToken); | ||||||
} | ||||||
|
||||||
public async Task TagAsync(Descriptor desc, string reference, CancellationToken cancellationToken) | ||||||
{ | ||||||
await _storage.TagAsync(desc, reference, cancellationToken); | ||||||
} | ||||||
|
||||||
public Task<Descriptor> ResolveAsync(string reference, CancellationToken cancellationToken) | ||||||
{ | ||||||
return _storage.ResolveAsync(reference, cancellationToken); | ||||||
} | ||||||
} | ||||||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.