Skip to content

Commit

Permalink
Feature/379 support active mq artemis (#386)
Browse files Browse the repository at this point in the history
* #379 support ActiveMQ Artemis

* #379: start ActiveMQ Classic

* #379 test settings

* #379 implemented review

* #379 correction unittest

* #379 (Un)Shipped updated

* #379 tried to move new or changed API's to unshipped

* #379 again tried to move changed and new to unshipped

* #379 update shipped and unshipped

* #379 shipped and unshipped

---------

Co-authored-by: Alireza Baloochi <[email protected]>
  • Loading branch information
anoordover and Alirexaa authored Jan 20, 2025
1 parent a12e081 commit 6787af5
Show file tree
Hide file tree
Showing 14 changed files with 323 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
var password = builder.AddParameter("password", "admin", secret: true);

var amq = builder.AddActiveMQ("amq", username, password, 61616, "activemq", webPort: 8161)
.PublishAsConnectionString();
.PublishAsConnectionString();

builder.AddProject<CommunityToolkit_Aspire_Hosting_ActiveMQ_MassTransit>("masstransitExample")
.WithReference(amq)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ Accept: application/json
GET http://localhost:5004/received
Accept: application/json

###
### Jolokia call for Classic broker status
GET http://admin:admin@localhost:8161/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=localhost,service=Health/CurrentStatus
origin: localhost

###
GET http://localhost:8161/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=localhost,service=Health/CurrentStatus
### Jolokia call for Artemis broker status
GET http://admin:admin@localhost:8161/console/jolokia/read/org.apache.activemq.artemis:broker=%220.0.0.0%22/Started
origin: localhost
Authorization: Basic YWRtaW46YWRtaW4=

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace CommunityToolkit.Aspire.Hosting.ActiveMQ;

internal static class ActiveMQArtemisContainerImageSettings
{
public const string Registry = "docker.io";
public const string Image = "apache/activemq-artemis";
public const string Tag = "2.39.0";
public const string EnvironmentVariableUsername = "ARTEMIS_USER";
public const string EnvironmentVariablePassword = "ARTEMIS_PASSWORD";
public const string JolokiaPath = "/console/jolokia/read/org.apache.activemq.artemis:broker=%220.0.0.0%22/Started";
public const string DataPath = "/var/lib/artemis-instance";
public const string ConfPath = "/var/lib/artemis-instance/etc-override";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using CommunityToolkit.Aspire.Hosting.ActiveMQ;

namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// A resource that represents a ActiveMQ Artemis resource.
/// </summary>
/// <param name="name">The name of the resource.</param>
/// <param name="userName">A parameter that contains the ActiveMQ server username, or <see langword="null"/> to use a default value.</param>
/// <param name="password">A parameter that contains the ActiveMQ server password.</param>
/// <param name="scheme">Scheme used in the connectionString (e.g. tcp or activemq, see MassTransit)</param>
public class ActiveMQArtemisServerResource(string name, ParameterResource? userName, ParameterResource password, string scheme) : ActiveMQServerResourceBase(name, userName, password, scheme, ActiveMQSettings.ForArtemis)
{
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.


using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Utils;
using CommunityToolkit.Aspire.Hosting.ActiveMQ;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Text;
Expand Down Expand Up @@ -46,20 +44,56 @@ public static IResourceBuilder<ActiveMQServerResource> AddActiveMQ(this IDistrib
?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"{name}-password", special: false);

ActiveMQServerResource activeMq = new(name, userName?.Resource, passwordParameter, scheme);
IResourceBuilder<ActiveMQServerResource> activemq = builder.AddResource(activeMq)
.WithImage(ActiveMQContainerImageTags.Image, ActiveMQContainerImageTags.Tag)
.WithImageRegistry(ActiveMQContainerImageTags.Registry)
.WithEndpoint(port: port, targetPort: 61616, name: ActiveMQServerResource.PrimaryEndpointName, scheme: scheme)
.WithEndpoint(port: webPort, targetPort: 8161, name: "web", scheme: "http")
.WithEnvironment(context =>
{
context.EnvironmentVariables["ACTIVEMQ_CONNECTION_USER"] = activeMq.UserNameReference;
context.EnvironmentVariables["ACTIVEMQ_CONNECTION_PASSWORD"] = activeMq.PasswordParameter;
});

activemq.WithJolokiaHealthCheck();

return activemq;
return builder.Build(port, scheme, webPort, activeMq);
}

/// <summary>
/// Adds a ActiveMQ Artemis container to the application model.
/// </summary>
/// <remarks>
/// The default image and tag are "apache/activemq-artemis" and "2.39.0".
/// </remarks>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <param name="userName">The parameter used to provide the username for the ActiveMQ resource. If <see langword="null"/> a default value will be used.</param>
/// <param name="password">The parameter used to provide the password for the ActiveMQ resource. If <see langword="null"/> a random password will be generated.</param>
/// <param name="port">The host port that the underlying container is bound to when running locally.</param>
/// <param name="scheme">The scheme of the endpoint, e.g. tcp or activemq (for masstransit). Defaults to tcp.</param>
/// <param name="webPort">The host port that the underlying webconsole is bound to when running locally.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<ActiveMQArtemisServerResource> AddActiveMQArtemis(this IDistributedApplicationBuilder builder,
[ResourceName] string name,
IResourceBuilder<ParameterResource>? userName = null,
IResourceBuilder<ParameterResource>? password = null,
int? port = null,
string scheme = "tcp",
int? webPort = null)
{
ArgumentNullException.ThrowIfNull(name, nameof(name));
ArgumentNullException.ThrowIfNull(scheme, nameof(scheme));

// don't use special characters in the password, since it goes into a URI
ParameterResource passwordParameter = password?.Resource
?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"{name}-password", special: false);

ActiveMQArtemisServerResource activeMq = new(name, userName?.Resource, passwordParameter, scheme);
return builder.Build(port, scheme, webPort, activeMq);
}

private static IResourceBuilder<T> Build<T>(this IDistributedApplicationBuilder builder, int? port, string scheme, int? webPort, T activeMq)
where T : ActiveMQServerResourceBase
{
IResourceBuilder<T> result = builder.AddResource(activeMq)
.WithImage(activeMq.ActiveMqSettings.Image, activeMq.ActiveMqSettings.Tag)
.WithImageRegistry(activeMq.ActiveMqSettings.Registry)
.WithEndpoint(port: port, targetPort: 61616, name: ActiveMQServerResourceBase.PrimaryEndpointName, scheme: scheme)
.WithEndpoint(port: webPort, targetPort: 8161, name: "web", scheme: "http")
.WithEnvironment(context =>
{
context.EnvironmentVariables[activeMq.ActiveMqSettings.EnvironmentVariableUsername] = activeMq.UserNameReference;
context.EnvironmentVariables[activeMq.ActiveMqSettings.EnvironmentVariablePassword] = activeMq.PasswordParameter;
});
return result.WithJolokiaHealthCheck();
}

/// <summary>
Expand All @@ -69,12 +103,13 @@ public static IResourceBuilder<ActiveMQServerResource> AddActiveMQ(this IDistrib
/// <param name="name">The name of the volume. Defaults to an auto-generated name based on the application and resource names.</param>
/// <param name="isReadOnly">A flag that indicates if this is a read-only volume.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<ActiveMQServerResource> WithDataVolume(this IResourceBuilder<ActiveMQServerResource> builder, string? name = null, bool isReadOnly = false) =>
public static IResourceBuilder<T> WithDataVolume<T>(this IResourceBuilder<T> builder, string? name = null, bool isReadOnly = false)
where T : ActiveMQServerResourceBase =>
builder
#pragma warning disable CTASPIRE001
.WithVolume(name ?? VolumeNameGenerator.CreateVolumeName(builder, "data"),
#pragma warning restore CTASPIRE001
"/opt/apache-activemq/data",
builder.Resource.ActiveMqSettings.DataPath,
isReadOnly);

/// <summary>
Expand All @@ -84,12 +119,13 @@ public static IResourceBuilder<ActiveMQServerResource> WithDataVolume(this IReso
/// <param name="name">The name of the volume. Defaults to an auto-generated name based on the application and resource names.</param>
/// <param name="isReadOnly">A flag that indicates if this is a read-only volume.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<ActiveMQServerResource> WithConfVolume(this IResourceBuilder<ActiveMQServerResource> builder, string? name = null, bool isReadOnly = false) =>
public static IResourceBuilder<T> WithConfVolume<T>(this IResourceBuilder<T> builder, string? name = null, bool isReadOnly = false)
where T : ActiveMQServerResourceBase =>
builder
#pragma warning disable CTASPIRE001
.WithVolume(name ?? VolumeNameGenerator.CreateVolumeName(builder, "conf"),
#pragma warning restore CTASPIRE001
"/opt/apache-activemq/conf",
builder.Resource.ActiveMqSettings.ConfPath,
isReadOnly);

/// <summary>
Expand All @@ -99,8 +135,9 @@ public static IResourceBuilder<ActiveMQServerResource> WithConfVolume(this IReso
/// <param name="source">The source directory on the host to mount into the container.</param>
/// <param name="isReadOnly">A flag that indicates if this is a read-only mount.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<ActiveMQServerResource> WithDataBindMount(this IResourceBuilder<ActiveMQServerResource> builder, string source, bool isReadOnly = false) =>
builder.WithBindMount(source, "/opt/apache-activemq/data", isReadOnly);
public static IResourceBuilder<T> WithDataBindMount<T>(this IResourceBuilder<T> builder, string source, bool isReadOnly = false)
where T : ActiveMQServerResourceBase =>
builder.WithBindMount(source, builder.Resource.ActiveMqSettings.DataPath, isReadOnly);

/// <summary>
/// Adds a bind mount for the conf folder to a ActiveMQ container resource.
Expand All @@ -109,12 +146,14 @@ public static IResourceBuilder<ActiveMQServerResource> WithDataBindMount(this IR
/// <param name="source">The source directory on the host to mount into the container.</param>
/// <param name="isReadOnly">A flag that indicates if this is a read-only mount.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<ActiveMQServerResource> WithConfBindMount(this IResourceBuilder<ActiveMQServerResource> builder, string source, bool isReadOnly = false) =>
builder.WithBindMount(source, "/opt/apache-activemq/conf", isReadOnly);
public static IResourceBuilder<T> WithConfBindMount<T>(this IResourceBuilder<T> builder, string source, bool isReadOnly = false)
where T : ActiveMQServerResourceBase =>
builder.WithBindMount(source, builder.Resource.ActiveMqSettings.ConfPath, isReadOnly);

private static IResourceBuilder<ActiveMQServerResource> WithJolokiaHealthCheck(this IResourceBuilder<ActiveMQServerResource> builder)
private static IResourceBuilder<T> WithJolokiaHealthCheck<T>(
this IResourceBuilder<T> builder)
where T : ActiveMQServerResourceBase
{
const string path = "/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=localhost,service=Health/CurrentStatus";
const int statusCode = 200;
const string endpointName = "web";
const string scheme = "http";
Expand Down Expand Up @@ -145,7 +184,7 @@ private static IResourceBuilder<ActiveMQServerResource> WithJolokiaHealthCheck(t
basicAuthentication = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{userName}:{password}"));
uri = new UriBuilder(baseUri)
{
Path = path
Path = builder.Resource.ActiveMqSettings.JolokiaPath
}.Uri;
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace CommunityToolkit.Aspire.Hosting.ActiveMQ;

internal static class ActiveMQClassicContainerImageSettings
{
public const string Registry = "docker.io";
public const string Image = "apache/activemq-classic";
public const string Tag = "6.1.4";
public const string EnvironmentVariableUsername = "ACTIVEMQ_CONNECTION_USER";
public const string EnvironmentVariablePassword = "ACTIVEMQ_CONNECTION_PASSWORD";
public const string JolokiaPath =
"/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=localhost,service=Health/CurrentStatus";
public const string DataPath = "/opt/apache-activemq/data";
public const string ConfPath = "/opt/apache-activemq/conf";
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using CommunityToolkit.Aspire.Hosting.ActiveMQ;

namespace Aspire.Hosting.ApplicationModel;

Expand All @@ -13,39 +12,6 @@ namespace Aspire.Hosting.ApplicationModel;
/// <param name="userName">A parameter that contains the ActiveMQ server username, or <see langword="null"/> to use a default value.</param>
/// <param name="password">A parameter that contains the ActiveMQ server password.</param>
/// <param name="scheme">Scheme used in the connectionString (e.g. tcp or activemq, see MassTransit)</param>
public class ActiveMQServerResource(string name, ParameterResource? userName, ParameterResource password, string scheme) : ContainerResource(name), IResourceWithConnectionString, IResourceWithEnvironment
public class ActiveMQServerResource(string name, ParameterResource? userName, ParameterResource password, string scheme) : ActiveMQServerResourceBase(name, userName, password, scheme, ActiveMQSettings.ForClassic)
{
internal const string PrimaryEndpointName = "tcp";
private const string DefaultUserName = "admin";
private EndpointReference? _primaryEndpoint;

/// <summary>
/// Gets the primary endpoint for the ActiveMQ server.
/// </summary>
public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new EndpointReference(this, PrimaryEndpointName);

/// <summary>
/// Gets the parameter that contains the ActiveMQ server username.
/// </summary>
public ParameterResource? UserNameParameter { get; } = userName;

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

/// <summary>
/// Gets the parameter that contains the ActiveMQ server password.
/// </summary>
public ParameterResource PasswordParameter { get; } = ThrowIfNull(password);

/// <summary>
/// Gets the connection string expression for the ActiveMQ server.
/// </summary>
public ReferenceExpression ConnectionStringExpression =>
ReferenceExpression.Create(
$"{scheme}://{UserNameReference}:{PasswordParameter}@{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}");

private static T ThrowIfNull<T>([NotNull] T? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
=> argument ?? throw new ArgumentNullException(paramName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using CommunityToolkit.Aspire.Hosting.ActiveMQ;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// Base class form ActiveMQ Classic and Artemis server resources.
/// </summary>
/// <param name="name">The name of the resource.</param>
/// <param name="userName">A parameter that contains the ActiveMQ server username, or <see langword="null"/> to use a default value.</param>
/// <param name="password">A parameter that contains the ActiveMQ server password.</param>
/// <param name="scheme">Scheme used in the connectionString (e.g. tcp or activemq, see MassTransit)</param>
/// <param name="settings">Settings being used for ActiveMQ Classic or Artemis</param>
public abstract class ActiveMQServerResourceBase(string name, ParameterResource? userName, ParameterResource password, string scheme, IActiveMQSettings settings) : ContainerResource(name), IResourceWithConnectionString, IResourceWithEnvironment
{
internal const string PrimaryEndpointName = "tcp";
internal const string DefaultUserName = "admin";
internal EndpointReference? _primaryEndpoint;


/// <inheritdoc />
public ReferenceExpression ConnectionStringExpression =>
ReferenceExpression.Create(
$"{scheme}://{UserNameReference}:{PasswordParameter}@{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}");

/// <summary>
/// Gets the primary endpoint for the ActiveMQ server.
/// </summary>
public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new EndpointReference(this, PrimaryEndpointName);

/// <summary>
/// Gets the parameter that contains the ActiveMQ server username.
/// </summary>
public ParameterResource? UserNameParameter { get; } = userName;

/// <summary>
/// Gets the parameter that contains the ActiveMQ server password.
/// </summary>
public ParameterResource PasswordParameter { get; } = ThrowIfNull(password);


/// <summary>
/// Gets the ActiveMQ settings.
/// </summary>
public IActiveMQSettings ActiveMqSettings { get; } = settings;

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

private static T ThrowIfNull<T>([NotNull] T? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
=> argument ?? throw new ArgumentNullException(paramName);
}
Loading

0 comments on commit 6787af5

Please sign in to comment.