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

Fix Azure ServiceBus persistent container support #7136

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

sebastienros
Copy link
Member

@sebastienros sebastienros commented Jan 17, 2025

Description

Fixes #7071

Issues identified:

  • the lifetime is not forwarded to the dependent container (sqledge)
  • the password was new on every start, invalidating reusability
  • the volume was using a new folder on every start, invalidating reusability

Now the password and the folder are stored in user secrets and reused.

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

var configHostFile = Path.Combine(Directory.CreateTempSubdirectory("AspireServiceBusEmulator").FullName, "Config.json");

if (lifetime == ContainerLifetime.Persistent && builder.ApplicationBuilder.ExecutionContext.IsRunMode && builder.ApplicationBuilder.AppHostAssembly is not null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can centralize this logic.

@davidfowl
Copy link
Member

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@davidfowl
Copy link
Member

AddAzureServiceBusWithEmulatorGetsExpectedPort - actual failing test

@sebastienros sebastienros marked this pull request as ready for review January 22, 2025 20:43
@sebastienros sebastienros requested a review from eerhardt January 23, 2025 00:12
var lifetime = ContainerLifetime.Session;

// Create a default file mount. This could be replaced by a user-provided file mount.
var configHostFile = Path.Combine(Directory.CreateTempSubdirectory("AspireServiceBusEmulator").FullName, "Config.json");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we always want to create the directory up front? In the case where the lifetime of is persistent, we may not want this right?

/// </remarks>
/// <param name="builder">Distributed application builder</param>
/// <param name="name">Name of the parameter</param>
/// <param name="value"></param>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value parameter is a bit odd since it is only used on the first run. After the value is persisted once, calling AddPersistentParameter again with a different value is ignored, since it reads it from user secrets.

Maybe calling this initialValue or similar may help.

/// <param name="name">Name of the parameter</param>
/// <param name="value"></param>
/// <returns>The created <see cref="ParameterResource"/>.</returns>
public static ParameterResource AddPersistentParameter(this IDistributedApplicationBuilder builder, string name, string value)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between this API and AddParameter(..., persist: true)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end it does the same thing, but you need to use a ParameterDefault in the existing one and the one we need is private. So it's either making ConstantParameterDefault public or create a new helper method that uses it (which is what I implemented).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this one be built on top of the other one to show that it is the same thing only with different inputs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given your current build errors, it kind of shows why this API isn't working very well. You want to pass a ParameterDefault that will generate a temp file when the parameter value isn't already persisted. Basically a callback func that produces the default value when the value isn't already there.

@@ -14,6 +14,7 @@

<ItemGroup>
<Compile Include="$(RepoRoot)src\Aspire.Hosting\Utils\PasswordGenerator.cs" Link="Utils\PasswordGenerator.cs" />
<Compile Include="$(SharedDir)SecretsStore.cs" Link="Utils\SecretsStore.cs" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still needed?

// Add emulator container

var password = PasswordGenerator.Generate(16, true, true, true, true, 0, 0, 0, 0);
// The password must be at least 8 characters long and contain characters from three of the following four sets: Uppercase letters, Lowercase letters, Base 10 digits, and Symbols
var passwordParameter = ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder.ApplicationBuilder, $"{builder.Resource.Name}-sqledge-pwd", minLower: 1, minUpper: 1, minNumeric: 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var passwordParameter = ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder.ApplicationBuilder, $"{builder.Resource.Name}-sqledge-pwd", minLower: 1, minUpper: 1, minNumeric: 1);
var passwordParameter = ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder.ApplicationBuilder, $"{builder.Resource.Name}-sql-pwd", minLower: 1, minUpper: 1, minNumeric: 1);

Do we need to explicitly call this "sqledge"? What if we changed it in the future?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if it's not even sql in the future ;) Will update.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the resource is named $"{builder.Resource.Name}-sqledge", that's why it's named $"{builder.Resource.Name}-sqledge-pwd" with the extra -pwd. If we were to change the type of resource, and the name, we could also use a different one for the password.

Or we can name everything without sqledge at all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think let's drop the edge in all for sure. I think sql is fine, but if you want to call it something more generic like -data I wouldn't object.

ContainerMountType.BindMount,
isReadOnly: true);

var hasCustomConfigJson = builder.Resource.Annotations.OfType<ContainerMountAnnotation>().Any(v => v.Target == AzureServiceBusEmulatorResource.EmulatorConfigJsonPath);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we checking this now? We weren't before.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WithConfigurationFile is removing an existing one. Before this PR the call back would be executed before WithConfigurationFile so it would keep only one. Now the callback is called after WithConfigurationFile so it won't have the same effect.

There is a test ensuring a single mount is defined.

@danmoseley danmoseley requested a review from Copilot January 27, 2025 19:23

namespace Aspire.Hosting.Utils;

internal sealed class AspireStore : KeyValueStore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we will want to make this API public because any integration (including outside of dotnet/aspire) will want to write to this folder.

@@ -12,83 +11,92 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal;
/// <summary>
/// Adapted from dotnet user-secrets at https://github.com/dotnet/aspnetcore/blob/482730a4c773ee4b3ae9525186d10999c89b556d/src/Tools/dotnet-user-secrets/src/Internal/SecretsStore.cs
/// </summary>
internal sealed class SecretsStore
internal abstract class KeyValueStore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we need this abstraction. SecretsStore is just about reading/writing the secrets.json file. The new Store should be about reading/writing files to the app directory (wherever it is that we decide).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

The service bus emulator is never persistent when marked as such
3 participants