Files
git.stella-ops.org/src/Cli/StellaOps.Cli/Services/OciImageReferenceParser.cs
2026-01-13 18:53:39 +02:00

143 lines
4.1 KiB
C#

using StellaOps.Cli.Services.Models;
namespace StellaOps.Cli.Services;
internal static class OciImageReferenceParser
{
public static OciImageReference Parse(string reference)
{
if (string.IsNullOrWhiteSpace(reference))
{
throw new ArgumentException("Image reference is required.", nameof(reference));
}
reference = reference.Trim();
if (reference.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
reference.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
reference.StartsWith("docker://", StringComparison.OrdinalIgnoreCase))
{
return ParseUri(reference);
}
var registry = string.Empty;
var remainder = reference;
var parts = reference.Split('/', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length > 1 && LooksLikeRegistry(parts[0]))
{
registry = parts[0];
remainder = string.Join('/', parts.Skip(1));
}
else
{
registry = "docker.io";
}
var repository = remainder;
string? tag = null;
string? digest = null;
var atIndex = remainder.LastIndexOf('@');
if (atIndex >= 0)
{
repository = remainder[..atIndex];
digest = remainder[(atIndex + 1)..];
}
else
{
var lastColon = remainder.LastIndexOf(':');
var lastSlash = remainder.LastIndexOf('/');
if (lastColon > lastSlash)
{
repository = remainder[..lastColon];
tag = remainder[(lastColon + 1)..];
}
}
if (string.IsNullOrWhiteSpace(repository))
{
throw new ArgumentException("Image repository is required.", nameof(reference));
}
if (string.Equals(registry, "docker.io", StringComparison.OrdinalIgnoreCase) &&
!repository.Contains('/', StringComparison.Ordinal))
{
repository = $"library/{repository}";
}
if (string.IsNullOrWhiteSpace(tag) && string.IsNullOrWhiteSpace(digest))
{
tag = "latest";
}
return new OciImageReference
{
Registry = registry,
Repository = repository,
Tag = tag,
Digest = digest,
Original = reference
};
}
private static OciImageReference ParseUri(string reference)
{
if (!Uri.TryCreate(reference, UriKind.Absolute, out var uri))
{
throw new ArgumentException("Invalid image reference URI.", nameof(reference));
}
var registry = uri.Authority;
var remainder = uri.AbsolutePath.Trim('/');
string? tag = null;
string? digest = null;
var atIndex = remainder.LastIndexOf('@');
if (atIndex >= 0)
{
digest = remainder[(atIndex + 1)..];
remainder = remainder[..atIndex];
}
else
{
var lastColon = remainder.LastIndexOf(':');
if (lastColon > remainder.LastIndexOf('/'))
{
tag = remainder[(lastColon + 1)..];
remainder = remainder[..lastColon];
}
}
if (string.Equals(registry, "docker.io", StringComparison.OrdinalIgnoreCase) &&
!remainder.Contains('/', StringComparison.Ordinal))
{
remainder = $"library/{remainder}";
}
if (string.IsNullOrWhiteSpace(tag) && string.IsNullOrWhiteSpace(digest))
{
tag = "latest";
}
return new OciImageReference
{
Registry = registry,
Repository = remainder,
Tag = tag,
Digest = digest,
Original = reference
};
}
private static bool LooksLikeRegistry(string value)
{
if (string.Equals(value, "localhost", StringComparison.OrdinalIgnoreCase))
{
return true;
}
return value.Contains('.', StringComparison.Ordinal) || value.Contains(':', StringComparison.Ordinal);
}
}