using System.Text.RegularExpressions; namespace StellaOps.Zastava.Webhook.Admission; internal interface IImageDigestResolver { Task ResolveAsync(string imageReference, CancellationToken cancellationToken); } internal sealed class ImageDigestResolver : IImageDigestResolver { private static readonly Regex DigestPattern = new(@"(?[a-z0-9_+.-]+):(?[a-f0-9]{32,})", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); public Task ResolveAsync(string imageReference, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(imageReference)) { return Task.FromResult(ImageResolutionResult.CreateFailure(imageReference, "image.reference.empty")); } if (imageReference.Contains('@', StringComparison.Ordinal)) { var digest = imageReference[(imageReference.IndexOf('@') + 1)..]; if (DigestPattern.IsMatch(digest)) { return Task.FromResult(ImageResolutionResult.CreateSuccess(imageReference, digest)); } return Task.FromResult(ImageResolutionResult.CreateFailure(imageReference, "image.reference.invalid_digest")); } if (DigestPattern.IsMatch(imageReference)) { return Task.FromResult(ImageResolutionResult.CreateSuccess(imageReference, imageReference)); } return Task.FromResult(ImageResolutionResult.CreateFailure(imageReference, "image.reference.tag_unresolved")); } } internal sealed record ImageResolutionResult( string Original, string? ResolvedDigest, bool Success, string? FailureReason) { public static ImageResolutionResult CreateSuccess(string original, string digest) => new(original, digest, true, null); public static ImageResolutionResult CreateFailure(string original, string reason) => new(original, null, false, reason); }