Skip to content

Commit

Permalink
Update TestShop to store RabbitMQ in user secrets (#3446)
Browse files Browse the repository at this point in the history
  • Loading branch information
DamianEdwards authored Apr 12, 2024
1 parent 9dc5ccd commit 5200f67
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 2 deletions.
117 changes: 117 additions & 0 deletions playground/TestShop/AppHost/ParameterExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using System.Diagnostics;
using System.Reflection;
using Aspire.Hosting.Publishing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.UserSecrets;
using Microsoft.Extensions.Hosting;

namespace Aspire.Hosting;

internal static class ParameterExtensions
{
/// <summary>
/// Creates a password parameter that in the development environment is generated once and stored in the user secrets store.
/// </summary>
/// <remarks>
/// The password is only stable when in the development environment and the application is running. In all other cases, the password is generated each time.
/// </remarks>
public static IResourceBuilder<ParameterResource> CreateStablePassword(this IDistributedApplicationBuilder builder, string name,
bool lower = true, bool upper = true, bool numeric = true, bool special = true,
int minLower = 0, int minUpper = 0, int minNumeric = 0, int minSpecial = 0)
{
ParameterDefault generatedPassword = new GenerateParameterDefault
{
MinLength = 22, // enough to give 128 bits of entropy when using the default 67 possible characters. See remarks in PasswordGenerator.Generate
Lower = lower,
Upper = upper,
Numeric = numeric,
Special = special,
MinLower = minLower,
MinUpper = minUpper,
MinNumeric = minNumeric,
MinSpecial = minSpecial
};

if (builder.Environment.IsDevelopment() && builder.ExecutionContext.IsRunMode)
{
// In development mode, generate a new password each time the application starts
generatedPassword = new UserSecretsParameterDefault(builder.Environment.ApplicationName, name, generatedPassword);
}

var parameterResource = new ParameterResource(name, parameterDefault => GetParameterValue(builder.Configuration, name, parameterDefault), true)
{
Default = generatedPassword
};

return ResourceBuilder.Create(parameterResource, builder);
}

private static string GetParameterValue(IConfiguration configuration, string name, ParameterDefault? parameterDefault)
{
var configurationKey = $"Parameters:{name}";
return configuration[configurationKey]
?? parameterDefault?.GetDefaultValue()
?? throw new DistributedApplicationException($"Parameter resource could not be used because configuration key '{configurationKey}' is missing and the Parameter has no default value."); ;
}

sealed class UserSecretsParameterDefault(string applicationName, string parameterName, ParameterDefault parameterDefault) : ParameterDefault
{
public override string GetDefaultValue()
{
var value = parameterDefault.GetDefaultValue();
var configurationKey = $"Parameters:{parameterName}";
TrySetUserSecret(applicationName, configurationKey, value);
return value;
}

public override void WriteToManifest(ManifestPublishingContext context) => parameterDefault.WriteToManifest(context);

private static bool TrySetUserSecret(string applicationName, string name, string value)
{
if (!string.IsNullOrEmpty(applicationName))
{
var appAssembly = Assembly.Load(new AssemblyName(applicationName));
if (appAssembly is not null && appAssembly.GetCustomAttribute<UserSecretsIdAttribute>()?.UserSecretsId is { } userSecretsId)
{
// Save the value to the secret store
try
{
var startInfo = new ProcessStartInfo
{
FileName = "dotnet",
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
};
new List<string>(["user-secrets", "set", name, value, "--id", userSecretsId]).ForEach(startInfo.ArgumentList.Add);
var setUserSecrets = Process.Start(startInfo);
setUserSecrets?.WaitForExit(TimeSpan.FromSeconds(10));
return setUserSecrets?.ExitCode == 0;
}
catch (Exception) { }
}
}

return false;
}
}

sealed class ResourceBuilder
{
public static IResourceBuilder<T> Create<T>(T resource, IDistributedApplicationBuilder distributedApplicationBuilder) where T : IResource
{
return new ResourceBuilder<T>(resource, distributedApplicationBuilder);
}
}

sealed class ResourceBuilder<T>(T resource, IDistributedApplicationBuilder distributedApplicationBuilder) : IResourceBuilder<T> where T : IResource
{
public IDistributedApplicationBuilder ApplicationBuilder { get; } = distributedApplicationBuilder;

public T Resource { get; } = resource;

public IResourceBuilder<T> WithAnnotation<TAnnotation>(TAnnotation annotation, ResourceAnnotationMutationBehavior behavior = ResourceAnnotationMutationBehavior.Append) where TAnnotation : IResourceAnnotation
{
throw new NotImplementedException();
}
}
}
3 changes: 1 addition & 2 deletions playground/TestShop/AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
.WithReference(catalogDb)
.WithReplicas(2);

var rabbitMqPassword = builder.AddParameter("rabbitmq-password", secret: true);
var messaging = builder.AddRabbitMQ("messaging", password: rabbitMqPassword)
var messaging = builder.AddRabbitMQ("messaging", password: builder.CreateStablePassword("rabbitmq-password", special: false))
.WithDataVolume()
.WithManagementPlugin()
.PublishAsContainer();
Expand Down

0 comments on commit 5200f67

Please sign in to comment.