diff --git a/src/OrasProject.Oras/Content/Digest.cs b/src/OrasProject.Oras/Content/Digest.cs
index 28c594b..919d130 100644
--- a/src/OrasProject.Oras/Content/Digest.cs
+++ b/src/OrasProject.Oras/Content/Digest.cs
@@ -27,7 +27,7 @@ internal static class Digest
/// Verifies the digest header and throws an exception if it is invalid.
///
///
- internal static string Validate(string digest)
+ internal static string Validate(string? digest)
{
if (string.IsNullOrEmpty(digest) || !digestRegex.IsMatch(digest))
{
diff --git a/src/OrasProject.Oras/Registry/Reference.cs b/src/OrasProject.Oras/Registry/Reference.cs
new file mode 100644
index 0000000..1fd1edd
--- /dev/null
+++ b/src/OrasProject.Oras/Registry/Reference.cs
@@ -0,0 +1,226 @@
+// 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 OrasProject.Oras.Exceptions;
+using System;
+using System.Text.RegularExpressions;
+
+namespace OrasProject.Oras.Registry;
+
+public class Reference
+{
+ ///
+ /// Registry is the name of the registry. It is usually the domain name of the registry optionally with a port.
+ ///
+ public string Registry
+ {
+ get => _registry;
+ set => _registry = ValidateRegistry(value);
+ }
+
+ ///
+ /// Repository is the name of the repository.
+ ///
+ public string? Repository
+ {
+ get => _repository;
+ set => _repository = value == null ? null : ValidateRepository(value);
+ }
+
+ ///
+ /// Reference is the reference of the object in the repository. This field
+ /// can take any one of the four valid forms (see ParseReference). In the
+ /// case where it's the empty string, it necessarily implies valid form D,
+ /// and where it is non-empty, then it is either a tag, or a digest
+ /// (implying one of valid forms A, B, or C).
+ ///
+ public string? ContentReference
+ {
+ get => _reference;
+ set
+ {
+ if (value == null)
+ {
+ _reference = value;
+ _isTag = false;
+ return;
+ }
+
+ if (value.Contains(':'))
+ {
+ _reference = ValidateReferenceAsDigest(value);
+ _isTag = false;
+ return;
+ }
+
+ _reference = ValidateReferenceAsTag(value);
+ _isTag = true;
+ }
+ }
+
+ ///
+ /// Host returns the host name of the registry
+ ///
+ public string Host => _registry == "docker.io" ? "registry-1.docker.io" : _registry;
+
+ ///
+ /// Digest returns the reference as a Digest
+ ///
+ public string Digest
+ {
+ get
+ {
+ if (_reference == null)
+ {
+ throw new InvalidReferenceException("null content reference");
+ }
+ if (_isTag)
+ {
+ throw new InvalidReferenceException("not a digest");
+ }
+ return _reference;
+ }
+ }
+
+ ///
+ /// Digest returns the reference as a Tag
+ ///
+ public string Tag
+ {
+ get
+ {
+ if (_reference == null)
+ {
+ throw new InvalidReferenceException("null content reference");
+ }
+ if (!_isTag)
+ {
+ throw new InvalidReferenceException("not a tag");
+ }
+ return _reference;
+ }
+ }
+
+ private string _registry;
+ private string? _repository;
+ private string? _reference;
+ private bool _isTag;
+
+ ///
+ /// repositoryRegexp is adapted from the distribution implementation. The
+ /// repository name set under OCI distribution spec is a subset of the docker
+ /// repositoryRegexp is adapted from the distribution implementation. The
+ /// spec. For maximum compatability, the docker spec is verified client-side.
+ /// Further checks are left to the server-side.
+ /// References:
+ /// - https://github.com/distribution/distribution/blob/v2.7.1/reference/regexp.go#L53
+ /// - https://github.com/opencontainers/distribution-spec/blob/v1.0.1/spec.md#pulling-manifests
+ ///
+ private const string _repositoryRegexPattern = @"^[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9]+)*(?:/[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9]+)*)*$";
+ private static readonly Regex _repositoryRegex = new Regex(_repositoryRegexPattern, RegexOptions.Compiled);
+
+ ///
+ /// tagRegexp checks the tag name.
+ /// The docker and OCI spec have the same regular expression.
+ /// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.0.1/spec.md#pulling-manifests
+ ///
+ private const string _tagRegexPattern = @"^[\w][\w.-]{0,127}$";
+ private static readonly Regex _tagRegex = new Regex(_tagRegexPattern, RegexOptions.Compiled);
+
+ public static Reference Parse(string reference)
+ {
+ var parts = reference.Split('/', 2);
+ if (parts.Length == 1)
+ {
+ throw new InvalidReferenceException("missing repository");
+ }
+ var registry = parts[0];
+ var path = parts[1];
+
+ var index = path.IndexOf('@');
+ if (index != -1)
+ {
+ // digest found; Valid From A (if not B)
+ var repository = path[..index];
+ var contentReference = path[(index + 1)..];
+ index = repository.IndexOf(':');
+ if (index != -1)
+ {
+ // tag found ( and now dropped without validation ) since the
+ // digest already present; Valid Form B
+ repository = repository[..index];
+ }
+ var instance = new Reference(registry, repository)
+ {
+ _reference = ValidateReferenceAsDigest(contentReference),
+ _isTag = false
+ };
+ return instance;
+ }
+
+ index = path.IndexOf(':');
+ if (index != -1)
+ {
+ // tag found; Valid Form C
+ var repository = path[..index];
+ var contentReference = path[(index + 1)..];
+ var instance = new Reference(registry, repository)
+ {
+ _reference = ValidateReferenceAsTag(contentReference),
+ _isTag = true
+ };
+ return instance;
+ }
+
+ // empty `reference`; Valid Form D
+ return new Reference(registry, path);
+ }
+
+ public Reference(string registry) => _registry = ValidateRegistry(registry);
+
+ public Reference(string registry, string? repository) : this(registry)
+ => _repository = ValidateRepository(repository);
+
+ public Reference(string registry, string? repository, string? reference) : this(registry, repository)
+ => ContentReference = reference;
+
+ private static string ValidateRegistry(string registry)
+ {
+ var url = "dummy://" + registry;
+ if (!Uri.IsWellFormedUriString(url, UriKind.Absolute) || new Uri(url).Authority != registry)
+ {
+ throw new InvalidReferenceException("invalid registry");
+ };
+ return registry;
+ }
+
+ private static string ValidateRepository(string? repository)
+ {
+ if (repository == null || !_repositoryRegex.IsMatch(repository))
+ {
+ throw new InvalidReferenceException("invalid respository");
+ }
+ return repository;
+ }
+
+ private static string ValidateReferenceAsDigest(string? reference) => Content.Digest.Validate(reference);
+
+ private static string ValidateReferenceAsTag(string? reference)
+ {
+ if (reference == null || !_tagRegex.IsMatch(reference))
+ {
+ throw new InvalidReferenceException("invalid tag");
+ }
+ return reference;
+ }
+}
diff --git a/src/OrasProject.Oras/Registry/Remote/BlobStore.cs b/src/OrasProject.Oras/Registry/Remote/BlobStore.cs
index dd74572..3824445 100644
--- a/src/OrasProject.Oras/Registry/Remote/BlobStore.cs
+++ b/src/OrasProject.Oras/Registry/Remote/BlobStore.cs
@@ -41,7 +41,7 @@ public async Task FetchAsync(Descriptor target, CancellationToken cancel
{
var remoteReference = Repository.RemoteReference;
Digest.Validate(target.Digest);
- remoteReference.Reference = target.Digest;
+ remoteReference.ContentReference = target.Digest;
var url = URLUtiliity.BuildRepositoryBlobURL(Repository.PlainHTTP, remoteReference);
var resp = await Repository.HttpClient.GetAsync(url, cancellationToken);
switch (resp.StatusCode)
@@ -167,14 +167,14 @@ public async Task PushAsync(Descriptor expected, Stream content, CancellationTok
public async Task ResolveAsync(string reference, CancellationToken cancellationToken = default)
{
var remoteReference = Repository.ParseReference(reference);
- var refDigest = remoteReference.Digest();
+ var refDigest = remoteReference.Digest;
var url = URLUtiliity.BuildRepositoryBlobURL(Repository.PlainHTTP, remoteReference);
var requestMessage = new HttpRequestMessage(HttpMethod.Head, url);
using var resp = await Repository.HttpClient.SendAsync(requestMessage, cancellationToken);
return resp.StatusCode switch
{
HttpStatusCode.OK => Repository.GenerateBlobDescriptor(resp, refDigest),
- HttpStatusCode.NotFound => throw new NotFoundException($"{remoteReference.Reference}: not found"),
+ HttpStatusCode.NotFound => throw new NotFoundException($"{remoteReference.ContentReference}: not found"),
_ => throw await ErrorUtility.ParseErrorResponse(resp)
};
}
@@ -200,7 +200,7 @@ public async Task DeleteAsync(Descriptor target, CancellationToken cancellationT
public async Task<(Descriptor Descriptor, Stream Stream)> FetchAsync(string reference, CancellationToken cancellationToken = default)
{
var remoteReference = Repository.ParseReference(reference);
- var refDigest = remoteReference.Digest();
+ var refDigest = remoteReference.Digest;
var url = URLUtiliity.BuildRepositoryBlobURL(Repository.PlainHTTP, remoteReference);
var resp = await Repository.HttpClient.GetAsync(url, cancellationToken);
switch (resp.StatusCode)
diff --git a/src/OrasProject.Oras/Registry/Remote/IRepositoryOption.cs b/src/OrasProject.Oras/Registry/Remote/IRepositoryOption.cs
index 5db0845..29d72b0 100644
--- a/src/OrasProject.Oras/Registry/Remote/IRepositoryOption.cs
+++ b/src/OrasProject.Oras/Registry/Remote/IRepositoryOption.cs
@@ -11,7 +11,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-using OrasProject.Oras.Remote;
using System.Net.Http;
namespace OrasProject.Oras.Registry.Remote;
@@ -29,7 +28,7 @@ public interface IRepositoryOption
///
/// Reference references the remote repository.
///
- public RemoteReference RemoteReference { get; set; }
+ public Reference RemoteReference { get; set; }
///
/// PlainHTTP signals the transport to access the remote repository via HTTP
diff --git a/src/OrasProject.Oras/Registry/Remote/ManifestStore.cs b/src/OrasProject.Oras/Registry/Remote/ManifestStore.cs
index 2baef97..7170d50 100644
--- a/src/OrasProject.Oras/Registry/Remote/ManifestStore.cs
+++ b/src/OrasProject.Oras/Registry/Remote/ManifestStore.cs
@@ -47,7 +47,7 @@ public ManifestStore(Repository repository)
public async Task FetchAsync(Descriptor target, CancellationToken cancellationToken = default)
{
var remoteReference = Repository.RemoteReference;
- remoteReference.Reference = target.Digest;
+ remoteReference.ContentReference = target.Digest;
var url = URLUtiliity.BuildRepositoryManifestURL(Repository.PlainHTTP, remoteReference);
var req = new HttpRequestMessage(HttpMethod.Get, url);
req.Headers.Add("Accept", target.MediaType);
@@ -120,7 +120,7 @@ public async Task PushAsync(Descriptor expected, Stream content, CancellationTok
private async Task InternalPushAsync(Descriptor expected, Stream stream, string reference, CancellationToken cancellationToken)
{
var remoteReference = Repository.RemoteReference;
- remoteReference.Reference = reference;
+ remoteReference.ContentReference = reference;
var url = URLUtiliity.BuildRepositoryManifestURL(Repository.PlainHTTP, remoteReference);
var req = new HttpRequestMessage(HttpMethod.Put, url);
req.Content = new StreamContent(stream);
@@ -159,7 +159,7 @@ public async Task ResolveAsync(string reference, CancellationToken c
///
///
///
- public async Task GenerateDescriptorAsync(HttpResponseMessage res, RemoteReference reference, HttpMethod httpMethod)
+ public async Task GenerateDescriptorAsync(HttpResponseMessage res, Reference reference, HttpMethod httpMethod)
{
string mediaType;
try
@@ -183,7 +183,7 @@ public async Task GenerateDescriptorAsync(HttpResponseMessage res, R
string refDigest = string.Empty;
try
{
- refDigest = reference.Digest();
+ refDigest = reference.Digest;
}
catch (Exception)
{
@@ -325,7 +325,7 @@ public async Task PushAsync(Descriptor expected, Stream content, string referenc
CancellationToken cancellationToken = default)
{
var remoteReference = Repository.ParseReference(reference);
- await InternalPushAsync(expected, content, remoteReference.Reference, cancellationToken);
+ await InternalPushAsync(expected, content, remoteReference.ContentReference, cancellationToken);
}
///
@@ -339,6 +339,6 @@ public async Task TagAsync(Descriptor descriptor, string reference, Cancellation
{
var remoteReference = Repository.ParseReference(reference);
var rc = await FetchAsync(descriptor, cancellationToken);
- await InternalPushAsync(descriptor, rc, remoteReference.Reference, cancellationToken);
+ await InternalPushAsync(descriptor, rc, remoteReference.ContentReference, cancellationToken);
}
}
diff --git a/src/OrasProject.Oras/Registry/Remote/Registry.cs b/src/OrasProject.Oras/Registry/Remote/Registry.cs
index 608d5ca..297795c 100644
--- a/src/OrasProject.Oras/Registry/Remote/Registry.cs
+++ b/src/OrasProject.Oras/Registry/Remote/Registry.cs
@@ -30,31 +30,21 @@ public class Registry : IRegistry, IRepositoryOption
{
public HttpClient HttpClient { get; set; }
- public RemoteReference RemoteReference { get; set; }
+ public Reference RemoteReference { get; set; }
public bool PlainHTTP { get; set; }
public string[] ManifestMediaTypes { get; set; }
public int TagListPageSize { get; set; }
public Registry(string name)
{
- var reference = new RemoteReference
- {
- Registry = name,
- };
- reference.ValidateRegistry();
- RemoteReference = reference;
+ RemoteReference = new Reference(name);
HttpClient = new HttpClient();
HttpClient.AddUserAgent();
}
public Registry(string name, HttpClient httpClient)
{
- var reference = new RemoteReference
- {
- Registry = name,
- };
- reference.ValidateRegistry();
- RemoteReference = reference;
+ RemoteReference = new Reference(name);
HttpClient = httpClient;
}
@@ -91,29 +81,25 @@ public async Task PingAsync(CancellationToken cancellationToken)
///
public Task GetRepository(string name, CancellationToken cancellationToken)
{
- var reference = new RemoteReference
- {
- Registry = RemoteReference.Registry,
- Repository = name,
- };
+ var reference = new Reference(RemoteReference.Registry, name);
return Task.FromResult(new Repository(reference, this));
- }
-
-
+ }
+
+
///
/// Repositories returns a list of repositories from the remote registry.
///
///
///
///
- public async IAsyncEnumerable ListRepositoriesAsync(string? last = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
- {
- var url = URLUtiliity.BuildRegistryCatalogURL(PlainHTTP, RemoteReference);
- var done = false;
- while (!done)
- {
- IEnumerable repositories = Array.Empty();
+ public async IAsyncEnumerable ListRepositoriesAsync(string? last = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ var url = URLUtiliity.BuildRegistryCatalogURL(PlainHTTP, RemoteReference);
+ var done = false;
+ while (!done)
+ {
+ IEnumerable repositories = Array.Empty();
try
{
url = await RepositoryPageAsync(last, values => repositories = values, url, cancellationToken);
@@ -123,9 +109,9 @@ public async IAsyncEnumerable ListRepositoriesAsync(string? last = null,
{
done = true;
}
- foreach (var repository in repositories)
- {
- yield return repository;
+ foreach (var repository in repositories)
+ {
+ yield return repository;
}
}
}
@@ -167,6 +153,6 @@ private async Task RepositoryPageAsync(string? last, Action fn
var repositories = JsonSerializer.Deserialize(data);
fn(repositories.Repositories);
return LinkUtility.ParseLink(response);
- }
+ }
}
}
diff --git a/src/OrasProject.Oras/Registry/Remote/RemoteReference.cs b/src/OrasProject.Oras/Registry/Remote/RemoteReference.cs
deleted file mode 100644
index e13873c..0000000
--- a/src/OrasProject.Oras/Registry/Remote/RemoteReference.cs
+++ /dev/null
@@ -1,216 +0,0 @@
-// 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 OrasProject.Oras.Exceptions;
-using System;
-using System.Text.RegularExpressions;
-
-namespace OrasProject.Oras.Remote
-{
- public class RemoteReference
- {
- ///
- /// Registry is the name of the registry. It is usually the domain name of the registry optionally with a port.
- ///
- public string Registry { get; set; }
-
- ///
- /// Repository is the name of the repository.
- ///
- public string Repository { get; set; }
-
- ///
- /// Reference is the reference of the object in the repository. This field
- /// can take any one of the four valid forms (see ParseReference). In the
- /// case where it's the empty string, it necessarily implies valid form D,
- /// and where it is non-empty, then it is either a tag, or a digest
- /// (implying one of valid forms A, B, or C).
- ///
- public string Reference { get; set; }
-
- ///
- /// repositoryRegexp is adapted from the distribution implementation. The
- /// repository name set under OCI distribution spec is a subset of the docker
- /// repositoryRegexp is adapted from the distribution implementation. The
- /// spec. For maximum compatability, the docker spec is verified client-side.
- /// Further checks are left to the server-side.
- /// References:
- /// - https://github.com/distribution/distribution/blob/v2.7.1/reference/regexp.go#L53
- /// - https://github.com/opencontainers/distribution-spec/blob/v1.0.1/spec.md#pulling-manifests
- ///
- private const string repositoryRegexPattern = @"^[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9]+)*(?:/[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9]+)*)*$";
-
- private static Regex repositoryRegex = new Regex(repositoryRegexPattern, RegexOptions.Compiled);
-
- ///
- /// tagRegexp checks the tag name.
- /// The docker and OCI spec have the same regular expression.
- /// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.0.1/spec.md#pulling-manifests
- ///
- private const string tagRegexPattern = @"^[\w][\w.-]{0,127}$";
-
- private static Regex tagRegex = new Regex(tagRegexPattern, RegexOptions.Compiled);
-
- public static RemoteReference ParseReference(string artifact)
- {
- var parts = artifact.Split('/', 2);
- if (parts.Length == 1)
- {
- throw new InvalidReferenceException($"missing repository");
- }
- (var registry, var path) = (parts[0], parts[1]);
- bool isTag = false;
- string repository;
- string reference = String.Empty;
-
- if (path.IndexOf('@') is var index && index != -1)
- {
- // digest found; Valid From A (if not B)
- isTag = false;
- repository = path[..index];
- reference = path[(index + 1)..];
-
- if (repository.IndexOf(':') is var indexOfColon && indexOfColon != -1)
- {
- // tag found ( and now dropped without validation ) since the
- // digest already present; Valid Form B
- repository = repository[..indexOfColon];
- }
- }
- else if (path.IndexOf(':') is var indexOfColon && indexOfColon != -1)
- {
- // tag found; Valid Form C
- isTag = true;
- repository = path[..indexOfColon];
- reference = path[(indexOfColon + 1)..];
- }
- else
- {
- // empty `reference`; Valid Form D
- repository = path;
- }
- var remoteReference = new RemoteReference
- {
- Registry = registry,
- Repository = repository,
- Reference = reference
- };
-
- remoteReference.ValidateRegistry();
- remoteReference.ValidateRepository();
-
- if (string.IsNullOrEmpty(reference))
- {
- return remoteReference;
- }
-
- if (isTag)
- {
- remoteReference.ValidateReferenceAsTag();
- }
- else
- {
- remoteReference.ValidateReferenceAsDigest();
- }
- return remoteReference;
- }
-
- ///
- /// ValidateReferenceAsDigest validates the reference as a digest.
- ///
- public void ValidateReferenceAsDigest()
- {
- Content.Digest.Validate(Reference);
- }
-
-
- ///
- /// ValidateRepository checks if the repository name is valid.
- ///
- ///
- public void ValidateRepository()
- {
- if (!repositoryRegex.IsMatch(Repository))
- {
- throw new InvalidReferenceException("Invalid Respository");
- }
- }
-
- ///
- /// ValidateRegistry checks if the registry path is valid.
- ///
- ///
- public void ValidateRegistry()
- {
- var url = "dummy://" + this.Registry;
- if (!Uri.IsWellFormedUriString(url, UriKind.Absolute) || new Uri(url).Authority != Registry)
- {
- throw new InvalidReferenceException("Invalid Registry");
- };
-
- }
-
- public void ValidateReferenceAsTag()
- {
- if (!tagRegex.IsMatch(Reference))
- {
- throw new InvalidReferenceException("Invalid Tag");
- }
- }
-
- ///
- /// ValidateReference where the reference is first tried as an ampty string, then
- /// as a digest, and if that fails, as a tag.
- ///
- public void ValidateReference()
- {
- if (string.IsNullOrEmpty(Reference))
- {
- return;
- }
- if (Reference.IndexOf(':') != -1)
- {
- ValidateReferenceAsDigest();
- return;
- }
- else
- {
- ValidateReferenceAsTag();
- }
- }
-
- ///
- /// Host returns the host name of the registry
- ///
- ///
- public string Host()
- {
- if (Registry == "docker.io")
- {
- return "registry-1.docker.io";
- }
- return Registry;
- }
-
- ///
- /// Digest returns the reference as a Digest
- ///
- ///
- public string Digest()
- {
- ValidateReferenceAsDigest();
- return Reference;
- }
-
- }
-}
diff --git a/src/OrasProject.Oras/Registry/Remote/Repository.cs b/src/OrasProject.Oras/Registry/Remote/Repository.cs
index c284e42..adab7dc 100644
--- a/src/OrasProject.Oras/Registry/Remote/Repository.cs
+++ b/src/OrasProject.Oras/Registry/Remote/Repository.cs
@@ -42,7 +42,7 @@ public class Repository : IRepository, IRepositoryOption
///
/// ReferenceObj references the remote repository.
///
- public RemoteReference RemoteReference { get; set; }
+ public Reference RemoteReference { get; set; }
///
/// PlainHTTP signals the transport to access the remote repository via HTTP
@@ -73,7 +73,7 @@ public class Repository : IRepository, IRepositoryOption
///
public Repository(string reference)
{
- RemoteReference = RemoteReference.ParseReference(reference);
+ RemoteReference = Reference.Parse(reference);
HttpClient = new HttpClient();
HttpClient.DefaultRequestHeaders.Add("User-Agent", new string[] { "oras-dotnet" });
}
@@ -85,7 +85,7 @@ public Repository(string reference)
///
public Repository(string reference, HttpClient httpClient)
{
- RemoteReference = RemoteReference.ParseReference(reference);
+ RemoteReference = Reference.Parse(reference);
HttpClient = httpClient;
}
@@ -95,9 +95,8 @@ public Repository(string reference, HttpClient httpClient)
///
///
///
- internal Repository(RemoteReference reference, IRepositoryOption option)
+ internal Repository(Reference reference, IRepositoryOption option)
{
- reference.ValidateRepository();
HttpClient = option.HttpClient;
RemoteReference = reference;
ManifestMediaTypes = option.ManifestMediaTypes;
@@ -319,7 +318,7 @@ private async Task TagsPageAsync(string? last, Action fn, stri
internal async Task DeleteAsync(Descriptor target, bool isManifest, CancellationToken cancellationToken)
{
var remoteReference = RemoteReference;
- remoteReference.Reference = target.Digest;
+ remoteReference.ContentReference = target.Digest;
string url;
if (isManifest)
{
@@ -402,35 +401,29 @@ internal static void VerifyContentDigest(HttpResponseMessage resp, string expect
///
///
///
- public RemoteReference ParseReference(string reference)
+ public Reference ParseReference(string reference)
{
- RemoteReference remoteReference;
+ Reference remoteReference;
var hasError = false;
try
{
- remoteReference = RemoteReference.ParseReference(reference);
+ remoteReference = Reference.Parse(reference);
}
catch (Exception)
{
- hasError = true;
- remoteReference = new RemoteReference
- {
- Registry = RemoteReference.Registry,
- Repository = RemoteReference.Repository,
- Reference = reference
- };
- //reference is not a FQDN
- if (reference.IndexOf("@") is var index && index != -1)
- {
- // `@` implies *digest*, so drop the *tag* (irrespective of what it is).
- remoteReference.Reference = reference[(index + 1)..];
- remoteReference.ValidateReferenceAsDigest();
+ hasError = true;
+ //reference is not a FQDN
+ var index = reference.IndexOf("@");
+ if (index != -1)
+ {
+ // `@` implies *digest*, so drop the *tag* (irrespective of what it is).
+ reference = reference[(index + 1)..];
+ }
+ remoteReference = new Reference(RemoteReference.Registry, RemoteReference.Repository, reference);
+ if (index != -1)
+ {
+ _ = remoteReference.Digest;
}
- else
- {
- remoteReference.ValidateReference();
- }
-
}
if (!hasError)
@@ -442,7 +435,7 @@ public RemoteReference ParseReference(string reference)
$"mismatch between received {JsonSerializer.Serialize(remoteReference)} and expected {JsonSerializer.Serialize(RemoteReference)}");
}
}
- if (string.IsNullOrEmpty(remoteReference.Reference))
+ if (string.IsNullOrEmpty(remoteReference.ContentReference))
{
throw new InvalidReferenceException();
}
diff --git a/src/OrasProject.Oras/Registry/Remote/URLUtiliity.cs b/src/OrasProject.Oras/Registry/Remote/URLUtiliity.cs
index 082b467..25882fb 100644
--- a/src/OrasProject.Oras/Registry/Remote/URLUtiliity.cs
+++ b/src/OrasProject.Oras/Registry/Remote/URLUtiliity.cs
@@ -11,6 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+using OrasProject.Oras.Registry;
+
namespace OrasProject.Oras.Remote
{
@@ -39,9 +41,9 @@ internal static string BuildScheme(bool plainHTTP)
///
///
///
- internal static string BuildRegistryBaseURL(bool plainHTTP, RemoteReference reference)
+ internal static string BuildRegistryBaseURL(bool plainHTTP, Reference reference)
{
- return $"{BuildScheme(plainHTTP)}://{reference.Host()}/v2/";
+ return $"{BuildScheme(plainHTTP)}://{reference.Host}/v2/";
}
///
@@ -52,9 +54,9 @@ internal static string BuildRegistryBaseURL(bool plainHTTP, RemoteReference refe
///
///
///
- internal static string BuildRegistryCatalogURL(bool plainHTTP, RemoteReference reference)
+ internal static string BuildRegistryCatalogURL(bool plainHTTP, Reference reference)
{
- return $"{BuildScheme(plainHTTP)}://{reference.Host()}/v2/_catalog";
+ return $"{BuildScheme(plainHTTP)}://{reference.Host}/v2/_catalog";
}
///
@@ -64,9 +66,9 @@ internal static string BuildRegistryCatalogURL(bool plainHTTP, RemoteReference r
///
///
///
- internal static string BuildRepositoryBaseURL(bool plainHTTP, RemoteReference reference)
+ internal static string BuildRepositoryBaseURL(bool plainHTTP, Reference reference)
{
- return $"{BuildScheme(plainHTTP)}://{reference.Host()}/v2/{reference.Repository}";
+ return $"{BuildScheme(plainHTTP)}://{reference.Host}/v2/{reference.Repository}";
}
///
@@ -77,7 +79,7 @@ internal static string BuildRepositoryBaseURL(bool plainHTTP, RemoteReference re
///
///
///
- internal static string BuildRepositoryTagListURL(bool plainHTTP, RemoteReference reference)
+ internal static string BuildRepositoryTagListURL(bool plainHTTP, Reference reference)
{
return $"{BuildRepositoryBaseURL(plainHTTP, reference)}/tags/list";
}
@@ -90,9 +92,9 @@ internal static string BuildRepositoryTagListURL(bool plainHTTP, RemoteReference
///
///
///
- internal static string BuildRepositoryManifestURL(bool plainHTTP, RemoteReference reference)
+ internal static string BuildRepositoryManifestURL(bool plainHTTP, Reference reference)
{
- return $"{BuildRepositoryBaseURL(plainHTTP, reference)}/manifests/{reference.Reference}";
+ return $"{BuildRepositoryBaseURL(plainHTTP, reference)}/manifests/{reference.ContentReference}";
}
///
@@ -103,9 +105,9 @@ internal static string BuildRepositoryManifestURL(bool plainHTTP, RemoteReferenc
///
///
///
- internal static string BuildRepositoryBlobURL(bool plainHTTP, RemoteReference reference)
+ internal static string BuildRepositoryBlobURL(bool plainHTTP, Reference reference)
{
- return $"{BuildRepositoryBaseURL(plainHTTP, reference)}/blobs/{reference.Reference}";
+ return $"{BuildRepositoryBaseURL(plainHTTP, reference)}/blobs/{reference.ContentReference}";
}
///
@@ -117,7 +119,7 @@ internal static string BuildRepositoryBlobURL(bool plainHTTP, RemoteReference re
///
///
///
- internal static string BuildRepositoryBlobUploadURL(bool plainHTTP, RemoteReference reference)
+ internal static string BuildRepositoryBlobUploadURL(bool plainHTTP, Reference reference)
{
return $"{BuildRepositoryBaseURL(plainHTTP, reference)}/blobs/uploads/";
}
diff --git a/tests/OrasProject.Oras.Tests/RemoteTest/RepositoryTest.cs b/tests/OrasProject.Oras.Tests/RemoteTest/RepositoryTest.cs
index 8f9381d..e8e8715 100644
--- a/tests/OrasProject.Oras.Tests/RemoteTest/RepositoryTest.cs
+++ b/tests/OrasProject.Oras.Tests/RemoteTest/RepositoryTest.cs
@@ -16,6 +16,7 @@
using OrasProject.Oras.Content;
using OrasProject.Oras.Exceptions;
using OrasProject.Oras.Oci;
+using OrasProject.Oras.Registry;
using OrasProject.Oras.Registry.Remote;
using OrasProject.Oras.Remote;
using System.Collections.Immutable;
@@ -1423,12 +1424,7 @@ public async Task BlobStore_FetchReferenceAsync_Seek()
[Fact]
public void GenerateBlobDescriptor_WithVariousDockerContentDigestHeaders()
{
- var reference = new RemoteReference()
- {
- Registry = "eastern.haan.com",
- Reference = "",
- Repository = "from25to220ce"
- };
+ var reference = new Reference("eastern.haan.com", "from25to220ce");
var tests = GetTestIOStructMapForGetDescriptorClass();
foreach ((string testName, TestIOStruct dcdIOStruct) in tests)
{
@@ -1439,7 +1435,7 @@ public void GenerateBlobDescriptor_WithVariousDockerContentDigestHeaders()
HttpMethod[] methods = new HttpMethod[] { HttpMethod.Get, HttpMethod.Head };
foreach ((int i, HttpMethod method) in methods.Select((value, i) => (i, value)))
{
- reference.Reference = dcdIOStruct.clientSuppliedReference;
+ reference.ContentReference = dcdIOStruct.clientSuppliedReference;
var resp = new HttpResponseMessage();
if (method == HttpMethod.Get)
{
@@ -1468,7 +1464,7 @@ public void GenerateBlobDescriptor_WithVariousDockerContentDigestHeaders()
var d = string.Empty;
try
{
- d = reference.Digest();
+ d = reference.Digest;
}
catch
{
@@ -2109,12 +2105,7 @@ public async Task CopyFromRepositoryToMemory()
[Fact]
public async Task ManifestStore_generateDescriptorWithVariousDockerContentDigestHeaders()
{
- var reference = new RemoteReference()
- {
- Registry = "eastern.haan.com",
- Reference = "",
- Repository = "from25to220ce"
- };
+ var reference = new Reference("eastern.haan.com", "from25to220ce");
var tests = GetTestIOStructMapForGetDescriptorClass();
foreach ((string testName, TestIOStruct dcdIOStruct) in tests)
{
@@ -2123,7 +2114,7 @@ public async Task ManifestStore_generateDescriptorWithVariousDockerContentDigest
var s = new ManifestStore(repo);
foreach ((int i, HttpMethod method) in methods.Select((value, i) => (i, value)))
{
- reference.Reference = dcdIOStruct.clientSuppliedReference;
+ reference.ContentReference = dcdIOStruct.clientSuppliedReference;
var resp = new HttpResponseMessage();
if (method == HttpMethod.Get)
{