Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Refinement + project structure + tests
  • Loading branch information
Harold-Morgan committed Jun 5, 2025
commit f556a63084a1c0e32f7124c11ad062cf68789f83
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<ItemGroup>
<ProjectReference Include="..\CommunityToolkit.Aspire.Hosting.Minio.ServiceDefaults\CommunityToolkit.Aspire.Hosting.Minio.ServiceDefaults.csproj" />
<ProjectReference Include="..\src\CommunityToolkit.Aspire.Minio.Client\CommunityToolkit.Aspire.Minio.Client.csproj" />
<ProjectReference Include="..\..\..\src\CommunityToolkit.Aspire.Minio.Client\CommunityToolkit.Aspire.Minio.Client.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using CommunityToolkit.Aspire.Minio.Client;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Minio;
using Minio.DataModel.Args;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

<ItemGroup>
<ProjectReference Include="..\CommunityToolkit.Aspire.Hosting.Minio.ApiService\CommunityToolkit.Aspire.Hosting.Minio.ApiService.csproj" />
<ProjectReference Include="..\src\CommunityToolkit.Aspire.Hosting.Minio\CommunityToolkit.Aspire.Hosting.Minio.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\..\..\src\CommunityToolkit.Aspire.Hosting.Minio\CommunityToolkit.Aspire.Hosting.Minio.csproj" IsAspireProjectResource="false" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

var builder = DistributedApplication.CreateBuilder(args);

var username = builder.AddParameter("user", "admin");
var password = builder.AddParameter("password", "adminpasswordreallysecret", secret: true);
var username = builder.AddParameter("user", "minioadmin");
var password = builder.AddParameter("password", "minioadmin", secret: true);

var minio = builder.AddMinioContainer("minio", username, password);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Net.Sockets;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Publishing;
using Aspire.Hosting.ApplicationModel;
using CommunityToolkit.Aspire.Hosting.Minio;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
Expand Down Expand Up @@ -38,19 +36,20 @@ public static IResourceBuilder<MinioContainerResource> AddMinioContainer(

var rootPasswordParameter = rootPassword?.Resource ??
ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"{name}-rootPassword");

var rootUserParameter = rootUser?.Resource ?? new ParameterResource("user", _ => MinioContainerResource.DefaultUserName);

var minioContainer = new MinioContainerResource(name, rootUser?.Resource, rootPasswordParameter);
var minioContainer = new MinioContainerResource(name, rootUserParameter, rootPasswordParameter);

var builderWithResource = builder
.AddResource(minioContainer)
.WithManifestPublishingCallback(context => WriteMinioContainerToManifest(context, minioContainer))
.WithImage(MinioContainerImageTags.Image, MinioContainerImageTags.Tag)
.WithImageRegistry(MinioContainerImageTags.Registry)
.WithHttpEndpoint(targetPort: 9000, port: minioPort, name: MinioContainerResource.PrimaryEndpointName)
.WithHttpEndpoint(targetPort: 9001, port: minioConsolePort, name: "console")
.WithAnnotation(new ContainerImageAnnotation { Image = "minio/minio", Tag = "latest" })
.WithEnvironment("MINIO_ADDRESS", $":{minioPort.ToString()}")
.WithEnvironment("MINIO_CONSOLE_ADDRESS", $":{minioConsolePort.ToString()}")
.WithEnvironment("MINIO_PROMETHEUS_AUTH_TYPE", "public")
.WithEnvironment(RootUserEnvVarName, minioContainer.RootUser?.Value ?? MinioContainerResource.DefaultUserName)
.WithEnvironment(RootUserEnvVarName, minioContainer.RootUser.Value)
.WithEnvironment(RootPasswordEnvVarName, minioContainer.RootPassword.Value)
.WithArgs("server", "/data");

Expand All @@ -60,7 +59,12 @@ public static IResourceBuilder<MinioContainerResource> AddMinioContainer(
builder.Services.AddHealthChecks()
.Add(new HealthCheckRegistration(
healthCheckKey,
sp => new MinioHealthCheck(endpoint.Url),
sp =>
{
var httpClient = sp.GetRequiredService<IHttpClientFactory>().CreateClient("miniohealth");

return new MinioHealthCheck(endpoint.Url, httpClient);
},
failureStatus: default,
tags: default,
timeout: default));
Expand All @@ -69,10 +73,4 @@ public static IResourceBuilder<MinioContainerResource> AddMinioContainer(

return builderWithResource;
}

private static async Task WriteMinioContainerToManifest(ManifestPublishingContext context, MinioContainerResource resource)
{
// Want to see if there is interest
await context.WriteContainerAsync(resource);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace CommunityToolkit.Aspire.Hosting.Minio;

internal static class MinioContainerImageTags
{
/// <summary>docker.io</summary>
public const string Registry = "docker.io";
/// <summary>minio/minio</summary>
public const string Image = "minio/minio";
/// <summary>RELEASE.2025-04-22T22-12-26Z</summary>
public const string Tag = "RELEASE.2025-04-22T22-12-26Z";
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@
/// <param name="rootPassword"> A parameter that contains the Minio server admin password</param>
public sealed class MinioContainerResource(
string name,
ParameterResource? rootUser,
ParameterResource rootUser,
ParameterResource rootPassword) : ContainerResource(name),
IResourceWithConnectionString
{
internal const string PrimaryEndpointName = "http";
internal const string DefaultUserName = "admin";
internal const string DefaultUserName = "minioadmin";

/// <summary>
/// The MiniO root user.
/// </summary>
public ParameterResource? RootUser { get; set; } = rootUser;
public ParameterResource RootUser { get; set; } = rootUser;

/// <summary>
/// The MiniO root password.
Expand All @@ -31,15 +31,22 @@ public sealed class MinioContainerResource(
/// Gets the primary endpoint for the Minio. This endpoint is used for all API calls over HTTP.
/// </summary>
public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new(this, PrimaryEndpointName);

internal ReferenceExpression RootUserNameReference =>
RootUser is not null ?
ReferenceExpression.Create($"{RootUser}") :
ReferenceExpression.Create($"{DefaultUserName}");

/// <summary>
/// Gets the connection string expression for the Minio
/// </summary>
public ReferenceExpression ConnectionStringExpression =>
ReferenceExpression.Create($"Endpoint=http://{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}");
public ReferenceExpression ConnectionStringExpression => GetConnectionString();

private ReferenceExpression GetConnectionString()
{
var builder = new ReferenceExpressionBuilder();

builder.Append(
$"Endpoint=http://{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}");

builder.Append($";AccessKey={RootUser.Value}");
builder.Append($";SecretKey={RootPassword.Value}");

return builder.Build();
}
}
4 changes: 2 additions & 2 deletions src/CommunityToolkit.Aspire.Hosting.Minio/MinioHealthCheck.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ internal sealed class MinioHealthCheck : IHealthCheck
private readonly Uri _minioHealthClusterUri;
private readonly Uri _minioHealthClusterReadUri;

public MinioHealthCheck(string minioBaseUrl)
public MinioHealthCheck(string minioBaseUrl, HttpClient? httpClient = null)
{
_httpClient = new HttpClient();
_httpClient = httpClient ?? new HttpClient();
_httpClient.BaseAddress = new Uri(minioBaseUrl);
_minioHealthLiveUri = new Uri("/minio/health/live", UriKind.Relative);
_minioHealthClusterUri = new Uri("/minio/health/cluster", UriKind.Relative);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using Microsoft.Extensions.Hosting;
using CommunityToolkit.Aspire.Minio.Client;
using Minio;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace CommunityToolkit.Aspire.Minio.Client;
namespace Microsoft.Extensions.Hosting;

/// <summary>
/// Provides extension methods for registering MiniO-related services in an <see cref="IHostApplicationBuilder"/>.
Expand All @@ -14,74 +16,85 @@ public static class MinioClientBuilderExtensionMethods
/// <summary>
/// Adds Minio Client to ASPNet host
/// </summary>
/// <param name="builder"></param>
/// <param name="builder">The <see cref="IHostApplicationBuilder"/> used to add services.</param>
/// <param name="configurationSectionName">Name of the configuration settings section</param>
/// <param name="connectionName">The connection name to use to find a connection string.</param>
/// <param name="configureSettings">An optional delegate that can be used for customizing options. It is invoked after the settings are read from the configuration.</param>
public static void AddMinioClient(
this IHostApplicationBuilder builder,
string connectionName,
string? connectionName = null,
string? configurationSectionName = DefaultConfigSectionName,
Action<MinioClientSettings>? configureSettings = null)
{
var settings = GetMinioClientSettings(builder, configurationSectionName, configureSettings);
ArgumentNullException.ThrowIfNull(builder);

var settings = GetMinioClientSettings(builder, connectionName, configurationSectionName, configureSettings);

builder.AddMinioInternal(connectionName, settings);
builder.AddMinioInternal(settings);
}

private static void AddMinioInternal(this IHostApplicationBuilder builder, string connectionName, MinioClientSettings settings)
private static void AddMinioInternal(this IHostApplicationBuilder builder, MinioClientSettings settings)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(settings);

if (builder.Configuration.GetConnectionString(connectionName) is string connectionString)

// Add the Minio client to the service collection.
void ConfigureClient(IMinioClient configureClient)
{
settings.ParseConnectionString(connectionString);
var client = configureClient.WithEndpoint(settings.Endpoint)
.WithSSL(settings.UseSsl);

if (settings.Credentials is not null) client.WithCredentials(settings.Credentials.AccessKey, settings.Credentials.SecretKey);

if (settings.UserAgentHeaderInfo is not null) client.SetAppInfo(settings.UserAgentHeaderInfo.AppName, settings.UserAgentHeaderInfo.AppVersion);

if (settings.SetTraceOn)
client.SetTraceOn();
else
client.SetTraceOff();
}

if (settings.Credentials is null)
var minioClientFactory = new MinioClientFactory(ConfigureClient);
builder.Services.TryAddSingleton<IMinioClientFactory>(minioClientFactory);

IMinioClient GetClient()
{
var credentials = new MinioCredentials();
credentials.SecretKey = builder.Configuration.GetValue<string>("Parameters:user") ?? "admin";
credentials.AccessKey = builder.Configuration.GetValue<string>("Parameters:password") ?? "admin";
if (settings.Endpoint is null)
throw new InvalidOperationException("The MiniO endpoint must be provided either in configuration section, or as a part of connection string or settings delegate");

settings.Credentials = credentials;
return minioClientFactory.CreateClient();
}

switch (settings.ServiceLifetime)
{
case ServiceLifetime.Singleton:
builder.Services.TryAddSingleton(_ => GetClient());
break;
case ServiceLifetime.Scoped:
builder.Services.TryAddScoped(_ => GetClient());
break;
case ServiceLifetime.Transient:
builder.Services.TryAddTransient(_ => GetClient());
break;
}

// Add the Minio client to the service collection.
builder.Services.AddMinio(
configureClient =>
{
var client = configureClient
.WithEndpoint(settings.Endpoint)
.WithSSL(settings.UseSsl);

if (settings.Credentials is not null)
client.WithCredentials(settings.Credentials.AccessKey, settings.Credentials.SecretKey);

if (settings.UserAgentHeaderInfo is not null)
client.SetAppInfo(settings.UserAgentHeaderInfo.AppName, settings.UserAgentHeaderInfo.AppVersion);

if (settings.SetTraceOn)
client.SetTraceOn();
else
client.SetTraceOff();
},
settings.ServiceLifetime
);
}

private static MinioClientSettings GetMinioClientSettings(IHostApplicationBuilder builder,
string? connectionName,
string? configurationSectionName,
Action<MinioClientSettings>? configureSettings)
{
var settings = new MinioClientSettings();

builder.Configuration.Bind(configurationSectionName ?? DefaultConfigSectionName, settings);

if (configureSettings is not null)
configureSettings.Invoke(settings);

if (!string.IsNullOrEmpty(connectionName) &&
builder.Configuration.GetConnectionString(connectionName) is string connectionString)
{
settings.ParseConnectionString(connectionString);
}

configureSettings?.Invoke(settings);

return settings;
}
}
19 changes: 17 additions & 2 deletions src/CommunityToolkit.Aspire.Minio.Client/MinioClientSettings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Minio;
using System.Data.Common;

namespace CommunityToolkit.Aspire.Minio.Client;
Expand All @@ -9,6 +10,8 @@ namespace CommunityToolkit.Aspire.Minio.Client;
public sealed class MinioClientSettings
{
private const string ConnectionStringEndpoint = "Endpoint";
private const string AccessKey = "AccessKey";
private const string SecretKey = "SecretKey";

/// <summary>
/// Endpoint URL
Expand All @@ -32,9 +35,10 @@ public sealed class MinioClientSettings
public ServiceLifetime ServiceLifetime = ServiceLifetime.Singleton;

/// <summary>
/// Turn on tracing
/// Turn on tracing.
/// Isn't aspire tracing compatible yet. <see cref="MinioClient.SetTraceOn"/>
/// </summary>
public bool SetTraceOn { get; set; } = true;
public bool SetTraceOn { get; set; } = false;

internal void ParseConnectionString(string? connectionString)
{
Expand All @@ -56,6 +60,17 @@ internal void ParseConnectionString(string? connectionString)
Endpoint = serviceUri;
}

if (connectionBuilder.TryGetValue(AccessKey, out var accessKey)
&&
connectionBuilder.TryGetValue(SecretKey, out var secretKey)
&&
!string.IsNullOrEmpty(accessKey.ToString()) && !string.IsNullOrEmpty(secretKey.ToString()))
{
Credentials = new MinioCredentials
{
AccessKey = accessKey.ToString()!, SecretKey = secretKey.ToString()!
};
}
}
}
}
Expand Down
Loading