Skip to content

Commit 521cad4

Browse files
Copilotdavidfowl
andauthored
Add async PipelineStepFactoryContext and WithPipelineStepFactory extension methods (#12165)
* Initial plan * Implement PipelineStepFactoryContext with PipelineContext and Resource properties Co-authored-by: davidfowl <[email protected]> * Make PipelineStepAnnotation factory async to support walking resource graph Co-authored-by: davidfowl <[email protected]> * Add WithPipelineStep extension methods with sync and async overloads Co-authored-by: davidfowl <[email protected]> * Add sync constructor overloads and update code to use WithPipelineStep Co-authored-by: davidfowl <[email protected]> * Rename WithPipelineStep to WithPipelineStepFactory Co-authored-by: davidfowl <[email protected]> * Change DependsOnSteps and RequiredBySteps to init properties Co-authored-by: davidfowl <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: davidfowl <[email protected]>
1 parent d1648bf commit 521cad4

File tree

7 files changed

+334
-31
lines changed

7 files changed

+334
-31
lines changed

src/Aspire.Hosting.Azure/AzureEnvironmentResource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public AzureEnvironmentResource(string name, ParameterResource location, Paramet
5959
{
6060
Annotations.Add(new PublishingCallbackAnnotation(PublishAsync));
6161

62-
Annotations.Add(new PipelineStepAnnotation(() =>
62+
Annotations.Add(new PipelineStepAnnotation((factoryContext) =>
6363
{
6464
ProvisioningContext? provisioningContext = null;
6565

src/Aspire.Hosting/Pipelines/DistributedApplicationPipeline.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ public void AddStep(PipelineStep step)
104104

105105
public async Task ExecuteAsync(PipelineContext context)
106106
{
107-
var allSteps = _steps.Concat(CollectStepsFromAnnotations(context)).ToList();
107+
var annotationSteps = await CollectStepsFromAnnotationsAsync(context).ConfigureAwait(false);
108+
var allSteps = _steps.Concat(annotationSteps).ToList();
108109

109110
if (allSteps.Count == 0)
110111
{
@@ -119,21 +120,29 @@ public async Task ExecuteAsync(PipelineContext context)
119120
await ExecuteStepsAsTaskDag(allSteps, stepsByName, context).ConfigureAwait(false);
120121
}
121122

122-
private static IEnumerable<PipelineStep> CollectStepsFromAnnotations(PipelineContext context)
123+
private static async Task<List<PipelineStep>> CollectStepsFromAnnotationsAsync(PipelineContext context)
123124
{
125+
var steps = new List<PipelineStep>();
126+
124127
foreach (var resource in context.Model.Resources)
125128
{
126129
var annotations = resource.Annotations
127130
.OfType<PipelineStepAnnotation>();
128131

129132
foreach (var annotation in annotations)
130133
{
131-
foreach (var step in annotation.CreateSteps())
134+
var factoryContext = new PipelineStepFactoryContext
132135
{
133-
yield return step;
134-
}
136+
PipelineContext = context,
137+
Resource = resource
138+
};
139+
140+
var annotationSteps = await annotation.CreateStepsAsync(factoryContext).ConfigureAwait(false);
141+
steps.AddRange(annotationSteps);
135142
}
136143
}
144+
145+
return steps;
137146
}
138147

139148
private static void ValidateSteps(IEnumerable<PipelineStep> steps)

src/Aspire.Hosting/Pipelines/PipelineStep.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ public class PipelineStep
2424
public required Func<PipelineStepContext, Task> Action { get; init; }
2525

2626
/// <summary>
27-
/// Gets the list of step names that this step depends on.
27+
/// Gets or initializes the list of step names that this step depends on.
2828
/// </summary>
29-
public List<string> DependsOnSteps { get; } = [];
29+
public List<string> DependsOnSteps { get; init; } = [];
3030

3131
/// <summary>
32-
/// Gets the list of step names that require this step to complete before they can finish.
32+
/// Gets or initializes the list of step names that require this step to complete before they can finish.
3333
/// </summary>
34-
public List<string> RequiredBySteps { get; } = [];
34+
public List<string> RequiredBySteps { get; init; } = [];
3535

3636
/// <summary>
3737
/// Adds a dependency on another step.

src/Aspire.Hosting/Pipelines/PipelineStepAnnotation.cs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,48 @@ namespace Aspire.Hosting.Pipelines;
1414
[Experimental("ASPIREPIPELINES001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")]
1515
public class PipelineStepAnnotation : IResourceAnnotation
1616
{
17-
private readonly Func<IEnumerable<PipelineStep>> _factory;
17+
private readonly Func<PipelineStepFactoryContext, Task<IEnumerable<PipelineStep>>> _factory;
1818

1919
/// <summary>
2020
/// Initializes a new instance of the <see cref="PipelineStepAnnotation"/> class.
2121
/// </summary>
2222
/// <param name="factory">A factory function that creates the pipeline step.</param>
23-
public PipelineStepAnnotation(Func<PipelineStep> factory)
23+
public PipelineStepAnnotation(Func<PipelineStepFactoryContext, PipelineStep> factory)
2424
{
25-
_factory = () => [factory()];
25+
_factory = (context) => Task.FromResult<IEnumerable<PipelineStep>>([factory(context)]);
26+
}
27+
28+
/// <summary>
29+
/// Initializes a new instance of the <see cref="PipelineStepAnnotation"/> class.
30+
/// </summary>
31+
/// <param name="factory">An async factory function that creates the pipeline step.</param>
32+
public PipelineStepAnnotation(Func<PipelineStepFactoryContext, Task<PipelineStep>> factory)
33+
{
34+
_factory = async (context) => [await factory(context).ConfigureAwait(false)];
2635
}
2736

2837
/// <summary>
2938
/// Initializes a new instance of the <see cref="PipelineStepAnnotation"/> class with a factory that creates multiple pipeline steps.
3039
/// </summary>
3140
/// <param name="factory">A factory function that creates multiple pipeline steps.</param>
32-
public PipelineStepAnnotation(Func<IEnumerable<PipelineStep>> factory)
41+
public PipelineStepAnnotation(Func<PipelineStepFactoryContext, IEnumerable<PipelineStep>> factory)
42+
{
43+
_factory = (context) => Task.FromResult(factory(context));
44+
}
45+
46+
/// <summary>
47+
/// Initializes a new instance of the <see cref="PipelineStepAnnotation"/> class with a factory that creates multiple pipeline steps.
48+
/// </summary>
49+
/// <param name="factory">An async factory function that creates multiple pipeline steps.</param>
50+
public PipelineStepAnnotation(Func<PipelineStepFactoryContext, Task<IEnumerable<PipelineStep>>> factory)
3351
{
3452
_factory = factory;
3553
}
3654

3755
/// <summary>
38-
/// Creates pipeline steps.
56+
/// Creates pipeline steps asynchronously.
3957
/// </summary>
40-
/// <returns>The created pipeline steps.</returns>
41-
public IEnumerable<PipelineStep> CreateSteps() => _factory();
58+
/// <param name="context">The factory context containing the pipeline context and resource.</param>
59+
/// <returns>A task that represents the asynchronous operation and contains the created pipeline steps.</returns>
60+
public Task<IEnumerable<PipelineStep>> CreateStepsAsync(PipelineStepFactoryContext context) => _factory(context);
4261
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#pragma warning disable ASPIREPUBLISHERS001
5+
6+
using System.Diagnostics.CodeAnalysis;
7+
using Aspire.Hosting.ApplicationModel;
8+
9+
namespace Aspire.Hosting.Pipelines;
10+
11+
/// <summary>
12+
/// Provides extension methods for adding pipeline steps to resources.
13+
/// </summary>
14+
[Experimental("ASPIREPIPELINES001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")]
15+
public static class PipelineStepExtensions
16+
{
17+
/// <summary>
18+
/// Adds a pipeline step to the resource that will be executed during deployment.
19+
/// </summary>
20+
/// <typeparam name="T">The type of the resource.</typeparam>
21+
/// <param name="builder">The resource builder.</param>
22+
/// <param name="factory">A factory function that creates the pipeline step.</param>
23+
/// <returns>The resource builder for chaining.</returns>
24+
public static IResourceBuilder<T> WithPipelineStepFactory<T>(
25+
this IResourceBuilder<T> builder,
26+
Func<PipelineStepFactoryContext, PipelineStep> factory) where T : IResource
27+
{
28+
ArgumentNullException.ThrowIfNull(builder);
29+
ArgumentNullException.ThrowIfNull(factory);
30+
31+
return builder.WithAnnotation(new PipelineStepAnnotation(factory));
32+
}
33+
34+
/// <summary>
35+
/// Adds a pipeline step to the resource that will be executed during deployment.
36+
/// </summary>
37+
/// <typeparam name="T">The type of the resource.</typeparam>
38+
/// <param name="builder">The resource builder.</param>
39+
/// <param name="factory">An async factory function that creates the pipeline step.</param>
40+
/// <returns>The resource builder for chaining.</returns>
41+
public static IResourceBuilder<T> WithPipelineStepFactory<T>(
42+
this IResourceBuilder<T> builder,
43+
Func<PipelineStepFactoryContext, Task<PipelineStep>> factory) where T : IResource
44+
{
45+
ArgumentNullException.ThrowIfNull(builder);
46+
ArgumentNullException.ThrowIfNull(factory);
47+
48+
return builder.WithAnnotation(new PipelineStepAnnotation(factory));
49+
}
50+
51+
/// <summary>
52+
/// Adds multiple pipeline steps to the resource that will be executed during deployment.
53+
/// </summary>
54+
/// <typeparam name="T">The type of the resource.</typeparam>
55+
/// <param name="builder">The resource builder.</param>
56+
/// <param name="factory">A factory function that creates multiple pipeline steps.</param>
57+
/// <returns>The resource builder for chaining.</returns>
58+
public static IResourceBuilder<T> WithPipelineStepFactory<T>(
59+
this IResourceBuilder<T> builder,
60+
Func<PipelineStepFactoryContext, IEnumerable<PipelineStep>> factory) where T : IResource
61+
{
62+
ArgumentNullException.ThrowIfNull(builder);
63+
ArgumentNullException.ThrowIfNull(factory);
64+
65+
return builder.WithAnnotation(new PipelineStepAnnotation(factory));
66+
}
67+
68+
/// <summary>
69+
/// Adds multiple pipeline steps to the resource that will be executed during deployment.
70+
/// </summary>
71+
/// <typeparam name="T">The type of the resource.</typeparam>
72+
/// <param name="builder">The resource builder.</param>
73+
/// <param name="factory">An async factory function that creates multiple pipeline steps.</param>
74+
/// <returns>The resource builder for chaining.</returns>
75+
public static IResourceBuilder<T> WithPipelineStepFactory<T>(
76+
this IResourceBuilder<T> builder,
77+
Func<PipelineStepFactoryContext, Task<IEnumerable<PipelineStep>>> factory) where T : IResource
78+
{
79+
ArgumentNullException.ThrowIfNull(builder);
80+
ArgumentNullException.ThrowIfNull(factory);
81+
82+
return builder.WithAnnotation(new PipelineStepAnnotation(factory));
83+
}
84+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
using Aspire.Hosting.ApplicationModel;
6+
7+
namespace Aspire.Hosting.Pipelines;
8+
9+
/// <summary>
10+
/// Provides contextual information for creating pipeline steps from a <see cref="PipelineStepAnnotation"/>.
11+
/// </summary>
12+
[Experimental("ASPIREPIPELINES001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")]
13+
public class PipelineStepFactoryContext
14+
{
15+
/// <summary>
16+
/// Gets the pipeline context that has the model and other properties.
17+
/// </summary>
18+
public required PipelineContext PipelineContext { get; init; }
19+
20+
/// <summary>
21+
/// Gets the resource that this factory is associated with.
22+
/// </summary>
23+
public required IResource Resource { get; init; }
24+
}

0 commit comments

Comments
 (0)