-
Notifications
You must be signed in to change notification settings - Fork 720
Add support for pipeline step tagging and configuration phase dependency management #12293
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
Changes from all commits
c5005bb
6f2b8ab
5c31fd7
f932fd0
d3b007f
fc9f82f
490f3f3
58fd06e
7a6fd89
0b51e28
b6662e2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |
| using System.Globalization; | ||
| using System.Runtime.ExceptionServices; | ||
| using System.Text; | ||
| using Aspire.Hosting.ApplicationModel; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Logging.Abstractions; | ||
|
|
||
|
|
@@ -17,6 +18,7 @@ namespace Aspire.Hosting.Pipelines; | |
| internal sealed class DistributedApplicationPipeline : IDistributedApplicationPipeline | ||
| { | ||
| private readonly List<PipelineStep> _steps = []; | ||
| private readonly List<Func<PipelineConfigurationContext, Task>> _configurationCallbacks = []; | ||
|
|
||
| public bool HasSteps => _steps.Count > 0; | ||
|
|
||
|
|
@@ -103,11 +105,21 @@ public void AddStep(PipelineStep step) | |
| _steps.Add(step); | ||
| } | ||
|
|
||
| public void AddPipelineConfiguration(Func<PipelineConfigurationContext, Task> callback) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(callback); | ||
| _configurationCallbacks.Add(callback); | ||
| } | ||
|
|
||
| public async Task ExecuteAsync(PipelineContext context) | ||
| { | ||
| var annotationSteps = await CollectStepsFromAnnotationsAsync(context).ConfigureAwait(false); | ||
| var (annotationSteps, stepToResourceMap) = await CollectStepsFromAnnotationsAsync(context).ConfigureAwait(false); | ||
| var allSteps = _steps.Concat(annotationSteps).ToList(); | ||
|
|
||
| // Execute configuration callbacks even if there are no steps | ||
| // This allows callbacks to run validation or other logic | ||
| await ExecuteConfigurationCallbacksAsync(context, allSteps, stepToResourceMap).ConfigureAwait(false); | ||
|
Comment on lines
+119
to
+121
|
||
|
|
||
| if (allSteps.Count == 0) | ||
| { | ||
| return; | ||
|
|
@@ -182,9 +194,10 @@ void Visit(string stepName) | |
| return result; | ||
| } | ||
|
|
||
| private static async Task<List<PipelineStep>> CollectStepsFromAnnotationsAsync(PipelineContext context) | ||
| private static async Task<(List<PipelineStep> Steps, Dictionary<PipelineStep, IResource> StepToResourceMap)> CollectStepsFromAnnotationsAsync(PipelineContext context) | ||
| { | ||
| var steps = new List<PipelineStep>(); | ||
| var stepToResourceMap = new Dictionary<PipelineStep, IResource>(); | ||
|
|
||
| foreach (var resource in context.Model.Resources) | ||
| { | ||
|
|
@@ -200,11 +213,53 @@ private static async Task<List<PipelineStep>> CollectStepsFromAnnotationsAsync(P | |
| }; | ||
|
|
||
| var annotationSteps = await annotation.CreateStepsAsync(factoryContext).ConfigureAwait(false); | ||
| steps.AddRange(annotationSteps); | ||
| foreach (var step in annotationSteps) | ||
| { | ||
| steps.Add(step); | ||
| stepToResourceMap[step] = resource; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return steps; | ||
| return (steps, stepToResourceMap); | ||
| } | ||
|
|
||
| private async Task ExecuteConfigurationCallbacksAsync( | ||
| PipelineContext pipelineContext, | ||
| List<PipelineStep> allSteps, | ||
| Dictionary<PipelineStep, IResource> stepToResourceMap) | ||
| { | ||
| // Collect callbacks from the pipeline itself | ||
| var callbacks = new List<Func<PipelineConfigurationContext, Task>>(); | ||
|
|
||
| callbacks.AddRange(_configurationCallbacks); | ||
|
|
||
| // Collect callbacks from resource annotations | ||
| foreach (var resource in pipelineContext.Model.Resources) | ||
| { | ||
| var annotations = resource.Annotations.OfType<PipelineConfigurationAnnotation>(); | ||
| foreach (var annotation in annotations) | ||
| { | ||
| callbacks.Add(annotation.Callback); | ||
| } | ||
| } | ||
|
|
||
| // Execute all callbacks | ||
| if (callbacks.Count > 0) | ||
| { | ||
| var configContext = new PipelineConfigurationContext | ||
| { | ||
| Services = pipelineContext.Services, | ||
| Steps = allSteps.AsReadOnly(), | ||
| Model = pipelineContext.Model, | ||
| StepToResourceMap = stepToResourceMap | ||
| }; | ||
|
|
||
| foreach (var callback in callbacks) | ||
| { | ||
| await callback(configContext).ConfigureAwait(false); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private static void ValidateSteps(IEnumerable<PipelineStep> steps) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| #pragma warning disable ASPIREPUBLISHERS001 | ||
|
|
||
| using System.Diagnostics.CodeAnalysis; | ||
| using Aspire.Hosting.ApplicationModel; | ||
|
|
||
| namespace Aspire.Hosting.Pipelines; | ||
|
|
||
| /// <summary> | ||
| /// An annotation that registers a callback to execute during the pipeline configuration phase, | ||
| /// allowing modification of step dependencies and relationships. | ||
| /// </summary> | ||
| [Experimental("ASPIREPIPELINES001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] | ||
| public class PipelineConfigurationAnnotation : IResourceAnnotation | ||
| { | ||
| /// <summary> | ||
| /// Gets the callback function to execute during the configuration phase. | ||
| /// </summary> | ||
| public Func<PipelineConfigurationContext, Task> Callback { get; } | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="PipelineConfigurationAnnotation"/> class. | ||
| /// </summary> | ||
| /// <param name="callback">The callback function to execute during the configuration phase.</param> | ||
| public PipelineConfigurationAnnotation(Func<PipelineConfigurationContext, Task> callback) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(callback); | ||
| Callback = callback; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="PipelineConfigurationAnnotation"/> class. | ||
| /// </summary> | ||
| /// <param name="callback">The synchronous callback function to execute during the configuration phase.</param> | ||
| public PipelineConfigurationAnnotation(Action<PipelineConfigurationContext> callback) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(callback); | ||
| Callback = (context) => | ||
| { | ||
| callback(context); | ||
| return Task.CompletedTask; | ||
| }; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,66 @@ | ||||||||||
| // 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 Aspire.Hosting.ApplicationModel; | ||||||||||
|
|
||||||||||
| namespace Aspire.Hosting.Pipelines; | ||||||||||
|
|
||||||||||
| /// <summary> | ||||||||||
| /// Provides contextual information for pipeline configuration callbacks. | ||||||||||
| /// </summary> | ||||||||||
| [Experimental("ASPIREPIPELINES001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] | ||||||||||
| public class PipelineConfigurationContext | ||||||||||
| { | ||||||||||
| /// <summary> | ||||||||||
| /// Gets the service provider for dependency resolution. | ||||||||||
| /// </summary> | ||||||||||
| public required IServiceProvider Services { get; init; } | ||||||||||
|
|
||||||||||
| /// <summary> | ||||||||||
| /// Gets the list of pipeline steps collected during the first pass. | ||||||||||
| /// </summary> | ||||||||||
| public required IReadOnlyList<PipelineStep> Steps { get; init; } | ||||||||||
|
|
||||||||||
| /// <summary> | ||||||||||
| /// Gets the distributed application model containing all resources. | ||||||||||
| /// </summary> | ||||||||||
| public required DistributedApplicationModel Model { get; init; } | ||||||||||
|
|
||||||||||
| internal IReadOnlyDictionary<PipelineStep, IResource> StepToResourceMap { get; init; } = null!; | ||||||||||
eerhardt marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is unfortunate that it is internal, but the ctor is public. If we want to keep it that way, we should reall make this:
Suggested change
|
||||||||||
|
|
||||||||||
| /// <summary> | ||||||||||
| /// Gets all pipeline steps with the specified tag. | ||||||||||
| /// </summary> | ||||||||||
| /// <param name="tag">The tag to search for.</param> | ||||||||||
| /// <returns>A collection of steps that have the specified tag.</returns> | ||||||||||
| public IEnumerable<PipelineStep> GetSteps(string tag) | ||||||||||
| { | ||||||||||
| ArgumentNullException.ThrowIfNull(tag); | ||||||||||
| return Steps.Where(s => s.Tags.Contains(tag)); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /// <summary> | ||||||||||
| /// Gets all pipeline steps associated with the specified resource. | ||||||||||
| /// </summary> | ||||||||||
| /// <param name="resource">The resource to search for.</param> | ||||||||||
| /// <returns>A collection of steps associated with the resource.</returns> | ||||||||||
| public IEnumerable<PipelineStep> GetSteps(IResource resource) | ||||||||||
| { | ||||||||||
| ArgumentNullException.ThrowIfNull(resource); | ||||||||||
| return StepToResourceMap.Where(kvp => kvp.Value == resource).Select(kvp => kvp.Key); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /// <summary> | ||||||||||
| /// Gets all pipeline steps with the specified tag that are associated with the specified resource. | ||||||||||
| /// </summary> | ||||||||||
| /// <param name="resource">The resource to search for.</param> | ||||||||||
| /// <param name="tag">The tag to search for.</param> | ||||||||||
| /// <returns>A collection of steps that have the specified tag and are associated with the resource.</returns> | ||||||||||
| public IEnumerable<PipelineStep> GetSteps(IResource resource, string tag) | ||||||||||
| { | ||||||||||
| ArgumentNullException.ThrowIfNull(resource); | ||||||||||
| ArgumentNullException.ThrowIfNull(tag); | ||||||||||
| return GetSteps(resource).Where(s => s.Tags.Contains(tag)); | ||||||||||
| } | ||||||||||
| } | ||||||||||
Uh oh!
There was an error while loading. Please reload this page.