Skip to content
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

feat: Align IImage properties with Docker DSL #1256

Merged
merged 4 commits into from
Sep 10, 2024
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
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project>
<PropertyGroup>
<PackageId>$(AssemblyName)</PackageId>
<Version>3.11.0</Version>
<Version>4.0.0</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version)</FileVersion>
<Product>Testcontainers</Product>
Expand Down
2 changes: 1 addition & 1 deletion src/Testcontainers.Pulsar/PulsarContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public async Task<string> CreateAuthenticationTokenAsync(TimeSpan expiryTime, Ca
"--secret-key",
PulsarBuilder.SecretKeyFilePath,
"--subject",
PulsarBuilder.Username
PulsarBuilder.Username,
};

if (!Timeout.InfiniteTimeSpan.Equals(expiryTime))
Expand Down
2 changes: 1 addition & 1 deletion src/Testcontainers/Builders/ContainerBuilder`3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public TBuilderEntity WithImage(IImage image)
return Clone(new ContainerConfiguration(image: image));
}

return Clone(new ContainerConfiguration(image: new DockerImage(image.Repository, image.Name, image.Tag, TestcontainersSettings.HubImageNamePrefix)));
return Clone(new ContainerConfiguration(image: new DockerImage(image.Repository, image.Registry, image.Tag, image.Digest, TestcontainersSettings.HubImageNamePrefix)));
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ namespace DotNet.Testcontainers.Builders
{
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Text.Json;
using DotNet.Testcontainers.Configurations;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ namespace DotNet.Testcontainers.Builders
{
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using DotNet.Testcontainers.Configurations;
using JetBrains.Annotations;
Expand All @@ -27,7 +26,7 @@ public RootlessUnixEndpointAuthenticationProvider()
/// <param name="socketPaths">A list of socket paths.</param>
public RootlessUnixEndpointAuthenticationProvider(params string[] socketPaths)
{
var socketPath = socketPaths.FirstOrDefault(File.Exists);
var socketPath = Array.Find(socketPaths, File.Exists);
DockerEngine = socketPath == null ? null : new Uri("unix://" + socketPath);
}

Expand Down
120 changes: 75 additions & 45 deletions src/Testcontainers/Images/DockerImage.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
namespace DotNet.Testcontainers.Images
namespace DotNet.Testcontainers.Images
{
using System;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using JetBrains.Annotations;

Expand All @@ -14,33 +13,39 @@ public sealed class DockerImage : IImage

private const string NightlyTag = "nightly";

private static readonly char[] TrimChars = [' ', ':', '/'];

private static readonly Func<string, IImage> GetDockerImage = MatchImage.Match;

private static readonly char[] TrimChars = { ' ', ':', '/' };
[NotNull]
private readonly string _repository;

private static readonly char[] HostnameIdentifierChars = { '.', ':' };
[CanBeNull]
private readonly string _registry;

private readonly string _hubImageNamePrefix;
[CanBeNull]
private readonly string _tag;

private readonly Lazy<string> _lazyFullName;
[CanBeNull]
private readonly string _digit;

private readonly Lazy<string> _lazyHostname;
[CanBeNull]
private readonly string _hubImageNamePrefix;

/// <summary>
/// Initializes a new instance of the <see cref="DockerImage" /> class.
/// </summary>
/// <param name="image">The image.</param>
public DockerImage(IImage image)
: this(image.Repository, image.Name, image.Tag)
: this(image.Repository, image.Registry, image.Tag, image.Digest)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="DockerImage" /> class.
/// </summary>
/// <param name="image">The image.</param>
/// <exception cref="ArgumentNullException">Thrown when any argument is null.</exception>
/// <example>"fedora/httpd:version1.0" where "fedora" is the repository, "httpd" the name and "version1.0" the tag.</example>
/// <example><c>fedora/httpd:version1.0</c> where <c>fedora/httpd</c> is the repository and <c>version1.0</c> the tag.</example>
public DockerImage(string image)
: this(GetDockerImage(image))
{
Expand All @@ -51,65 +56,78 @@ public DockerImage(string image)
/// </summary>
/// <param name="repository">The repository.</param>
/// <param name="name">The name.</param>
[Obsolete("We will remove this construct and replace it with a more efficient implementation. Please use 'DockerImage(string, string = null, string = null, string = null, string = null)' instead. All arguments except for 'repository' (the first) are optional.")]
public DockerImage(string repository, string name)
: this(string.Join("/", repository, name).Trim('/'))
{
}

/// <summary>
/// Initializes a new instance of the <see cref="DockerImage" /> class.
/// </summary>
/// <param name="repository">The repository.</param>
/// <param name="name">The name.</param>
/// <param name="tag">The tag.</param>
[Obsolete("We will remove this construct and replace it with a more efficient implementation. Please use 'DockerImage(string, string = null, string = null, string = null, string = null)' instead. All arguments except for 'repository' (the first) are optional.")]
public DockerImage(string repository, string name, string tag)
: this(string.Join("/", repository, name).Trim('/') + (":" + tag).TrimEnd(':'))
{
}

/// <summary>
/// Initializes a new instance of the <see cref="DockerImage" /> class.
/// </summary>
/// <param name="repository">The repository.</param>
/// <param name="registry">The registry.</param>
/// <param name="tag">The tag.</param>
/// <param name="digest">The digest.</param>
/// <param name="hubImageNamePrefix">The Docker Hub image name prefix.</param>
/// <exception cref="ArgumentNullException">Thrown when any argument is null.</exception>
/// <example>"fedora/httpd:version1.0" where "fedora" is the repository, "httpd" the name and "version1.0" the tag.</example>
/// <example><c>fedora/httpd:version1.0</c> where <c>fedora/httpd</c> is the repository and <c>version1.0</c> the tag.</example>
public DockerImage(
string repository,
string name,
string registry = null,
string tag = null,
string digest = null,
string hubImageNamePrefix = null)
{
_ = Guard.Argument(repository, nameof(repository))
.NotNull()
.NotUppercase();

_ = Guard.Argument(name, nameof(name))
.NotNull()
.NotEmpty()
.NotUppercase();

_hubImageNamePrefix = TrimOrDefault(hubImageNamePrefix);
var defaultTag = tag == null && digest == null ? LatestTag : null;

Repository = TrimOrDefault(repository, repository);
Name = TrimOrDefault(name, name);
Tag = TrimOrDefault(tag, LatestTag);

_lazyFullName = new Lazy<string>(() =>
{
var imageComponents = new[] { _hubImageNamePrefix, Repository, Name }
.Where(imageComponent => !string.IsNullOrEmpty(imageComponent));
_repository = TrimOrDefault(repository);
_registry = TrimOrDefault(registry);
_tag = TrimOrDefault(tag, defaultTag);
_digit = TrimOrDefault(digest);
_hubImageNamePrefix = TrimOrDefault(hubImageNamePrefix);
}

return string.Join("/", imageComponents) + ":" + Tag;
});
/// <inheritdoc />
public string Repository => _repository;

_lazyHostname = new Lazy<string>(() =>
{
var firstSegmentOfRepository = new[] { _hubImageNamePrefix, Repository }
.Where(imageComponent => !string.IsNullOrEmpty(imageComponent))
.DefaultIfEmpty(string.Empty)
.First()
.Split('/')[0];

return firstSegmentOfRepository.IndexOfAny(HostnameIdentifierChars) >= 0 ? firstSegmentOfRepository : null;
});
}
/// <inheritdoc />
public string Registry => string.IsNullOrEmpty(_hubImageNamePrefix) ? _registry : _hubImageNamePrefix;

/// <inheritdoc />
public string Repository { get; }
public string Tag => _tag;

/// <inheritdoc />
public string Name { get; }
public string Digest => _digit;

/// <inheritdoc />
public string Tag { get; }
public string FullName => $"{Registry}/{Repository}:{Tag}".Trim(TrimChars);

/// <inheritdoc />
public string FullName => _lazyFullName.Value;
[Obsolete("We will remove this property, it does not follow the DSL. Use the 'Repository' property instead.")]
public string Name => GetBackwardsCompatibleName();

/// <inheritdoc />
public string GetHostname() => _lazyHostname.Value;
public string GetHostname()
{
return Registry;
}

/// <inheritdoc />
public bool MatchLatestOrNightly()
Expand All @@ -126,6 +144,11 @@ public bool MatchVersion(Predicate<string> predicate)
/// <inheritdoc />
public bool MatchVersion(Predicate<Version> predicate)
{
if (Tag == null)
{
return false;
}

var versionMatch = Regex.Match(Tag, "^(\\d+)(\\.\\d+)?(\\.\\d+)?", RegexOptions.None, TimeSpan.FromSeconds(1));

if (!versionMatch.Success)
Expand All @@ -142,7 +165,14 @@ public bool MatchVersion(Predicate<Version> predicate)
return predicate(new Version(int.Parse(versionMatch.Groups[1].Value, NumberStyles.None), 0));
}

private static string TrimOrDefault(string value, string defaultValue = default)
private string GetBackwardsCompatibleName()
{
// The last index will never be a `/`, we trim it in the constructor.
var lastIndex = _repository.LastIndexOf('/');
return lastIndex == -1 ? _repository : _repository.Substring(lastIndex + 1);
}

private static string TrimOrDefault(string value, string defaultValue = null)
{
return string.IsNullOrEmpty(value) ? defaultValue : value.Trim(TrimChars);
}
Expand Down
24 changes: 22 additions & 2 deletions src/Testcontainers/Images/FutureDockerImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ public string Repository
}

/// <inheritdoc />
public string Name
public string Registry
{
get
{
ThrowIfResourceNotFound();
return _configuration.Image.Name;
return _configuration.Image.Registry;
}
}

Expand All @@ -58,6 +58,16 @@ public string Tag
}
}

/// <inheritdoc />
public string Digest
{
get
{
ThrowIfResourceNotFound();
return _configuration.Image.Digest;
}
}

/// <inheritdoc />
public string FullName
{
Expand All @@ -68,6 +78,16 @@ public string FullName
}
}

/// <inheritdoc />
public string Name
{
get
{
ThrowIfResourceNotFound();
return _configuration.Image.Name;
}
}

/// <inheritdoc />
public string GetHostname()
{
Expand Down
21 changes: 17 additions & 4 deletions src/Testcontainers/Images/IImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,23 @@ public interface IImage
string Repository { get; }

/// <summary>
/// Gets the name.
/// Gets the registry.
/// </summary>
[NotNull]
string Name { get; }
[CanBeNull]
string Registry { get; }

/// <summary>
/// Gets the tag.
/// </summary>
[NotNull]
[CanBeNull]
string Tag { get; }

/// <summary>
/// Gets the digest.
/// </summary>
[CanBeNull]
string Digest { get; }

/// <summary>
/// Gets the full image name.
/// </summary>
Expand All @@ -36,6 +42,13 @@ public interface IImage
[NotNull]
string FullName { get; }

/// <summary>
/// Gets the name.
/// </summary>
[NotNull]
[Obsolete("We will remove this property, it does not follow the DSL. Use the 'Repository' property instead.")]
string Name { get; }

/// <summary>
/// Gets the registry hostname.
/// </summary>
Expand Down
Loading
Loading