Skip to content

Commit 02a4d7d

Browse files
Copilotdavidfowlmitchdenny
authored
Mark ParameterResource.Value as obsolete in favor of GetValueAsync and direct usage (#10363)
* Initial plan * Mark ParameterResource.Value as obsolete and update internal usage Co-authored-by: davidfowl <[email protected]> * Remove defaultValue parameter setting and use deferred resolution for ParameterResource values Co-authored-by: davidfowl <[email protected]> * Store ParameterResource in HelmExpressionWithValue and defer default value resolution Co-authored-by: davidfowl <[email protected]> * Make MapParameter and callees async and use GetValueAsync Co-authored-by: davidfowl <[email protected]> * Remove uses of parameter.Value in AzurePublishingContext.cs Co-authored-by: davidfowl <[email protected]> * Use BicepIdentifier instead of Value.Compile() for ParameterResource scope resolution Co-authored-by: davidfowl <[email protected]> * Revert tuple destructuring changes and correctly use BicepIdentifier instead of obsolete Value property Co-authored-by: davidfowl <[email protected]> * Revert AzurePublishingContext.cs to original synchronous Visit call Co-authored-by: davidfowl <[email protected]> * Remove pragma warning disable for obsolete ParameterResource.Value usage in AzurePublishingContext Co-authored-by: davidfowl <[email protected]> * Make MapParameter async and use p.GetValueAsync instead of obsolete property Co-authored-by: davidfowl <[email protected]> * Suppress obsolete warnings for ParameterResource.Value in test files Co-authored-by: davidfowl <[email protected]> * Move pragma. --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: davidfowl <[email protected]> Co-authored-by: Mitch Denny <[email protected]>
1 parent 93cdd51 commit 02a4d7d

File tree

21 files changed

+161
-22
lines changed

21 files changed

+161
-22
lines changed

src/Aspire.Hosting.Azure/AzurePublishingContext.cs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,9 @@ private async Task WriteAzureArtifactsOutputAsync(IPublishingStep step, Distribu
117117
.Where(r => !r.IsExcludedFromPublish())
118118
.ToList();
119119

120-
MapParameter(environment.ResourceGroupName);
121-
MapParameter(environment.Location);
122-
MapParameter(environment.PrincipalId);
120+
await MapParameterAsync(environment.ResourceGroupName, cancellationToken).ConfigureAwait(false);
121+
await MapParameterAsync(environment.Location, cancellationToken).ConfigureAwait(false);
122+
await MapParameterAsync(environment.PrincipalId, cancellationToken).ConfigureAwait(false);
123123

124124
var resourceGroupParam = ParameterLookup[environment.ResourceGroupName];
125125
MainInfrastructure.Add(resourceGroupParam);
@@ -163,14 +163,14 @@ private async Task WriteAzureArtifactsOutputAsync(IPublishingStep step, Distribu
163163
// Map parameters from existing resources
164164
if (resource.TryGetLastAnnotation<ExistingAzureResourceAnnotation>(out var existingAnnotation))
165165
{
166-
Visit(existingAnnotation.ResourceGroup, MapParameter);
167-
Visit(existingAnnotation.Name, MapParameter);
166+
await VisitAsync(existingAnnotation.ResourceGroup, MapParameterAsync, cancellationToken).ConfigureAwait(false);
167+
await VisitAsync(existingAnnotation.Name, MapParameterAsync, cancellationToken).ConfigureAwait(false);
168168
}
169169

170170
// Map parameters for the resource itself
171171
foreach (var parameter in resource.Parameters)
172172
{
173-
Visit(parameter.Value, MapParameter);
173+
await VisitAsync(parameter.Value, MapParameterAsync, cancellationToken).ConfigureAwait(false);
174174
}
175175
}
176176

@@ -359,7 +359,7 @@ await task.SucceedAsync(
359359
}
360360
}
361361

362-
private void MapParameter(object? candidate)
362+
private async Task MapParameterAsync(object candidate, CancellationToken cancellationToken = default)
363363
{
364364
if (candidate is ParameterResource p && !ParameterLookup.ContainsKey(p))
365365
{
@@ -372,7 +372,11 @@ private void MapParameter(object? candidate)
372372

373373
if (!p.Secret && p.Default is not null)
374374
{
375-
pp.Value = p.Value;
375+
var value = await p.GetValueAsync(cancellationToken).ConfigureAwait(false);
376+
if (value is not null)
377+
{
378+
pp.Value = value;
379+
}
376380
}
377381

378382
ParameterLookup[p] = pp;
@@ -400,6 +404,27 @@ private static void Visit(object? value, Action<object> visitor, HashSet<object>
400404
}
401405
}
402406

407+
private static Task VisitAsync(object? value, Func<object, CancellationToken, Task> visitor, CancellationToken cancellationToken = default) =>
408+
VisitAsync(value, visitor, [], cancellationToken);
409+
410+
private static async Task VisitAsync(object? value, Func<object, CancellationToken, Task> visitor, HashSet<object> visited, CancellationToken cancellationToken = default)
411+
{
412+
if (value is null || !visited.Add(value))
413+
{
414+
return;
415+
}
416+
417+
await visitor(value, cancellationToken).ConfigureAwait(false);
418+
419+
if (value is IValueWithReferences vwr)
420+
{
421+
foreach (var reference in vwr.References)
422+
{
423+
await VisitAsync(reference, visitor, visited, cancellationToken).ConfigureAwait(false);
424+
}
425+
}
426+
}
427+
403428
/// <summary>
404429
/// Saves the compiled Bicep template to disk.
405430
/// </summary>

src/Aspire.Hosting.Docker/DockerComposePublishingContext.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,15 @@ private async Task WriteDockerComposeOutputAsync(DistributedApplicationModel mod
155155

156156
foreach (var entry in environment.CapturedEnvironmentVariables ?? [])
157157
{
158-
var (key, (description, defaultValue, _)) = entry;
158+
var (key, (description, defaultValue, source)) = entry;
159+
160+
// If the source is a parameter and there's no explicit default value,
161+
// resolve the parameter's default value asynchronously
162+
if (defaultValue is null && source is ParameterResource parameter && !parameter.Secret && parameter.Default is not null)
163+
{
164+
defaultValue = await parameter.GetValueAsync(cancellationToken).ConfigureAwait(false);
165+
}
166+
159167
envFile.AddIfMissing(key, defaultValue, description);
160168
}
161169

src/Aspire.Hosting.Docker/DockerComposeServiceExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public static string AsEnvironmentPlaceholder(this ParameterResource parameter,
9898
return dockerComposeService.Parent.AddEnvironmentVariable(
9999
env,
100100
description: $"Parameter {parameter.Name}",
101-
defaultValue: parameter.Secret || parameter.Default is null ? null : parameter.Value,
101+
defaultValue: null,
102102
source: parameter
103103
);
104104
}

src/Aspire.Hosting.Kubernetes/KubernetesPublishingContext.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,22 +80,22 @@ private async Task WriteKubernetesOutputAsync(DistributedApplicationModel model,
8080
}
8181

8282
await WriteKubernetesTemplatesForResource(resource, serviceResource.GetTemplatedResources()).ConfigureAwait(false);
83-
AppendResourceContextToHelmValues(resource, serviceResource);
83+
await AppendResourceContextToHelmValuesAsync(resource, serviceResource).ConfigureAwait(false);
8484
}
8585
}
8686

8787
await WriteKubernetesHelmChartAsync(environment).ConfigureAwait(false);
8888
await WriteKubernetesHelmValuesAsync().ConfigureAwait(false);
8989
}
9090

91-
private void AppendResourceContextToHelmValues(IResource resource, KubernetesResource resourceContext)
91+
private async Task AppendResourceContextToHelmValuesAsync(IResource resource, KubernetesResource resourceContext)
9292
{
93-
AddValuesToHelmSection(resource, resourceContext.Parameters, HelmExtensions.ParametersKey);
94-
AddValuesToHelmSection(resource, resourceContext.EnvironmentVariables, HelmExtensions.ConfigKey);
95-
AddValuesToHelmSection(resource, resourceContext.Secrets, HelmExtensions.SecretsKey);
93+
await AddValuesToHelmSectionAsync(resource, resourceContext.Parameters, HelmExtensions.ParametersKey).ConfigureAwait(false);
94+
await AddValuesToHelmSectionAsync(resource, resourceContext.EnvironmentVariables, HelmExtensions.ConfigKey).ConfigureAwait(false);
95+
await AddValuesToHelmSectionAsync(resource, resourceContext.Secrets, HelmExtensions.SecretsKey).ConfigureAwait(false);
9696
}
9797

98-
private void AddValuesToHelmSection(
98+
private async Task AddValuesToHelmSectionAsync(
9999
IResource resource,
100100
Dictionary<string, KubernetesResource.HelmExpressionWithValue> contextItems,
101101
string helmKey)
@@ -114,7 +114,19 @@ private void AddValuesToHelmSection(
114114
continue;
115115
}
116116

117-
paramValues[key] = helmExpressionWithValue.Value ?? string.Empty;
117+
string? value;
118+
119+
// If there's a parameter source, resolve its value asynchronously
120+
if (helmExpressionWithValue.ParameterSource is ParameterResource parameter)
121+
{
122+
value = await parameter.GetValueAsync(cancellationToken).ConfigureAwait(false);
123+
}
124+
else
125+
{
126+
value = helmExpressionWithValue.Value;
127+
}
128+
129+
paramValues[key] = value ?? string.Empty;
118130
}
119131

120132
if (paramValues.Count > 0)

src/Aspire.Hosting.Kubernetes/KubernetesResource.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,25 @@ private void ProcessEnvironmentDefaultValue(object value, string key, string res
311311
EnvironmentVariables[key] = new(configExpression, value.ToString() ?? string.Empty);
312312
}
313313

314-
internal class HelmExpressionWithValue(string helmExpression, string? value)
314+
internal class HelmExpressionWithValue
315315
{
316-
public string HelmExpression { get; } = helmExpression;
317-
public string? Value { get; } = value;
316+
public HelmExpressionWithValue(string helmExpression, string? value)
317+
{
318+
HelmExpression = helmExpression;
319+
Value = value;
320+
ParameterSource = null;
321+
}
322+
323+
public HelmExpressionWithValue(string helmExpression, ParameterResource parameterSource)
324+
{
325+
HelmExpression = helmExpression;
326+
Value = null;
327+
ParameterSource = parameterSource;
328+
}
329+
330+
public string HelmExpression { get; }
331+
public string? Value { get; }
332+
public ParameterResource? ParameterSource { get; }
318333
public bool IsHelmSecretExpression => HelmExpression.ContainsHelmSecretExpression();
319334
public bool ValueContainsSecretExpression => Value?.ContainsHelmSecretExpression() ?? false;
320335
public bool ValueContainsHelmExpression => Value?.ContainsHelmExpression() ?? false;

src/Aspire.Hosting.Kubernetes/KubernetesServiceResourceExtensions.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,15 @@ private static HelmExpressionWithValue AllocateParameter(ParameterResource param
122122
formattedName.ToHelmSecretExpression(resource.Name) :
123123
formattedName.ToHelmConfigExpression(resource.Name);
124124

125-
var value = parameter.Default is null || parameter.Secret ? null : parameter.Value;
126-
return new(expression, value);
125+
// Store the parameter itself for deferred resolution instead of resolving the value immediately
126+
if (parameter.Default is null || parameter.Secret)
127+
{
128+
return new(expression, (string?)null);
129+
}
130+
else
131+
{
132+
return new(expression, parameter);
133+
}
127134
}
128135

129136
private static HelmExpressionWithValue ResolveUnknownValue(IManifestExpressionProvider parameter, IResource resource)

src/Aspire.Hosting/ApplicationModel/ParameterResource.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ public ParameterResource(string name, Func<ParameterDefault?, string> callback,
3131
/// <summary>
3232
/// Gets the value of the parameter.
3333
/// </summary>
34+
/// <remarks>
35+
/// This property is obsolete. Use <see cref="GetValueAsync(CancellationToken)"/> for async access or pass the <see cref="ParameterResource"/> directly to methods that accept it (e.g., environment variables).
36+
/// </remarks>
37+
[Obsolete("Use GetValueAsync for async access or pass the ParameterResource directly to methods that accept it (e.g., environment variables).")]
3438
public string Value => GetValueAsync(default).AsTask().GetAwaiter().GetResult()!;
3539

3640
internal string ValueInternal => _lazyValue.Value;

tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ public async Task AddAzureRedisRunAsContainerProducesCorrectConnectionString()
103103
Assert.True(redis.Resource.IsContainer(), "The resource should now be a container resource.");
104104

105105
Assert.NotNull(redisResource?.PasswordParameter);
106+
#pragma warning disable CS0618 // Type or member is obsolete
106107
Assert.Equal($"localhost:12455,password={redisResource.PasswordParameter.Value}", await redis.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None));
108+
#pragma warning restore CS0618 // Type or member is obsolete
107109
}
108110

109111
[Fact]
@@ -195,7 +197,9 @@ public async Task PublishAsRedisPublishesRedisAsAzureRedisInfrastructure()
195197
Assert.True(redis.Resource.IsContainer());
196198
Assert.NotNull(redis.Resource.PasswordParameter);
197199

200+
#pragma warning disable CS0618 // Type or member is obsolete
198201
Assert.Equal($"localhost:12455,password={redis.Resource.PasswordParameter.Value}", await redis.Resource.GetConnectionStringAsync());
202+
#pragma warning restore CS0618 // Type or member is obsolete
199203

200204
var manifest = await AzureManifestUtils.GetManifestWithBicep(redis.Resource);
201205

tests/Aspire.Hosting.Milvus.Tests/MilvusFunctionalTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume)
7676
{
7777
using var builder1 = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(testOutputHelper);
7878
var milvus1 = builder1.AddMilvus("milvus1");
79+
#pragma warning disable CS0618 // Type or member is obsolete
7980
var password = milvus1.Resource.ApiKeyParameter.Value;
81+
#pragma warning restore CS0618 // Type or member is obsolete
8082

8183
var db1 = milvus1.AddDatabase("milvusdb1", dbname);
8284

tests/Aspire.Hosting.MongoDB.Tests/AddMongoDBTests.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,13 @@ public async Task MongoDBCreatesConnectionString()
8787
Assert.NotNull(connectionStringResource);
8888
var connectionString = await connectionStringResource.GetConnectionStringAsync();
8989

90+
#pragma warning disable CS0618 // Type or member is obsolete
9091
Assert.Equal($"mongodb://admin:{dbResource.Parent.PasswordParameter?.Value}@localhost:27017?authSource=admin&authMechanism=SCRAM-SHA-256", await serverResource.GetConnectionStringAsync());
92+
#pragma warning restore CS0618 // Type or member is obsolete
9193
Assert.Equal("mongodb://admin:{mongodb-password.value}@{mongodb.bindings.tcp.host}:{mongodb.bindings.tcp.port}?authSource=admin&authMechanism=SCRAM-SHA-256", serverResource.ConnectionStringExpression.ValueExpression);
94+
#pragma warning disable CS0618 // Type or member is obsolete
9295
Assert.Equal($"mongodb://admin:{dbResource.Parent.PasswordParameter?.Value}@localhost:27017/mydatabase?authSource=admin&authMechanism=SCRAM-SHA-256", connectionString);
96+
#pragma warning restore CS0618 // Type or member is obsolete
9397
Assert.Equal("mongodb://admin:{mongodb-password.value}@{mongodb.bindings.tcp.host}:{mongodb.bindings.tcp.port}/mydatabase?authSource=admin&authMechanism=SCRAM-SHA-256", connectionStringResource.ConnectionStringExpression.ValueExpression);
9498
}
9599

0 commit comments

Comments
 (0)