diff --git a/Elsa.sln b/Elsa.sln index c99bab03f..edef8869e 100644 --- a/Elsa.sln +++ b/Elsa.sln @@ -198,6 +198,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.AspNet.ProtoAc EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.AspNet.HttpEndpoints", "src\samples\aspnet\Elsa.Samples.AspNet.HttpEndpoints\Elsa.Samples.AspNet.HttpEndpoints.csproj", "{165ACC9D-B67C-4B49-A818-ECFD247C899C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Environments", "src\modules\Elsa.Environments\Elsa.Environments.csproj", "{B5CDF747-8066-40D5-9BAE-CBE397BC9B51}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -504,6 +506,10 @@ Global {165ACC9D-B67C-4B49-A818-ECFD247C899C}.Debug|Any CPU.Build.0 = Debug|Any CPU {165ACC9D-B67C-4B49-A818-ECFD247C899C}.Release|Any CPU.ActiveCfg = Release|Any CPU {165ACC9D-B67C-4B49-A818-ECFD247C899C}.Release|Any CPU.Build.0 = Release|Any CPU + {B5CDF747-8066-40D5-9BAE-CBE397BC9B51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5CDF747-8066-40D5-9BAE-CBE397BC9B51}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5CDF747-8066-40D5-9BAE-CBE397BC9B51}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5CDF747-8066-40D5-9BAE-CBE397BC9B51}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} @@ -592,5 +598,6 @@ Global {044C3108-FE79-460A-9C31-A03C30228836} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} {3212A999-4AC4-4911-9AA4-92AB906BCB5E} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} {165ACC9D-B67C-4B49-A818-ECFD247C899C} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} + {B5CDF747-8066-40D5-9BAE-CBE397BC9B51} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} EndGlobalSection EndGlobal diff --git a/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj b/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj index b93d2e1d4..e9f32223e 100644 --- a/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj +++ b/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj @@ -14,6 +14,7 @@ + diff --git a/src/bundles/Elsa.WorkflowServer.Web/Program.cs b/src/bundles/Elsa.WorkflowServer.Web/Program.cs index e92083e99..0826c5d2a 100644 --- a/src/bundles/Elsa.WorkflowServer.Web/Program.cs +++ b/src/bundles/Elsa.WorkflowServer.Web/Program.cs @@ -42,7 +42,7 @@ services runtime.UseAsyncWorkflowStateExporter(); runtime.UseMassTransitDispatcher(); }) - .UseLabels(labels => labels.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString))) + .UseEnvironments(environments => environments.EnvironmentsOptions = options => configuration.GetSection("Environments").Bind(options)) .UseScheduling() .UseWorkflowsApi(api => api.AddFastEndpointsAssembly()) .UseJavaScript() diff --git a/src/bundles/Elsa.WorkflowServer.Web/appsettings.json b/src/bundles/Elsa.WorkflowServer.Web/appsettings.json index a9d882b0c..3356f5478 100644 --- a/src/bundles/Elsa.WorkflowServer.Web/appsettings.json +++ b/src/bundles/Elsa.WorkflowServer.Web/appsettings.json @@ -61,5 +61,21 @@ "hashedClientSecretSalt": "xRKy14Ok1/tU3kLf/8V1fcbLIegy9vcM90Peu2tzohU=" } ] + }, + "Environments": { + "Environments": [ + { + "Name": "Local", + "ServerUrl": "https://localhost:5001/elsa/api" + }, + { + "Name": "Staging", + "ServerUrl": "https://staging.acme.com/elsa/api" + }, + { + "Name": "Production", + "ServerUrl": "https://production.acme.com/elsa/api" + } + ] } } diff --git a/src/modules/Elsa.Environments/Contracts/IEnvironmentsManager.cs b/src/modules/Elsa.Environments/Contracts/IEnvironmentsManager.cs new file mode 100644 index 000000000..e29aeeb2a --- /dev/null +++ b/src/modules/Elsa.Environments/Contracts/IEnvironmentsManager.cs @@ -0,0 +1,16 @@ +using Elsa.Environments.Models; + +namespace Elsa.Environments.Contracts; + +/// +/// Manages environments. +/// +public interface IEnvironmentsManager +{ + /// + /// Returns all environments. + /// + /// The cancellation token. + /// All environments. + Task> ListEnvironmentsAsync(CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/modules/Elsa.Environments/Contracts/IEnvironmentsProvider.cs b/src/modules/Elsa.Environments/Contracts/IEnvironmentsProvider.cs new file mode 100644 index 000000000..e1b672de9 --- /dev/null +++ b/src/modules/Elsa.Environments/Contracts/IEnvironmentsProvider.cs @@ -0,0 +1,16 @@ +using Elsa.Environments.Models; + +namespace Elsa.Environments.Contracts; + +/// +/// Provides environments to the system. +/// +public interface IEnvironmentsProvider +{ + /// + /// Returns a list of workflow environments. + /// + /// The cancellation token. + /// A list of workflow environments. + ValueTask> GetEnvironmentsAsync(CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/modules/Elsa.Environments/Elsa.Environments.csproj b/src/modules/Elsa.Environments/Elsa.Environments.csproj new file mode 100644 index 000000000..555c9558f --- /dev/null +++ b/src/modules/Elsa.Environments/Elsa.Environments.csproj @@ -0,0 +1,19 @@ + + + + + + + net6.0;net7.0 + 3.0.0 + + Provides API endpoints for client applications to enumerate available environments. + + elsa workflows environments api + + + + + + + diff --git a/src/modules/Elsa.Environments/Endpoints/Environments/List/Endpoint.cs b/src/modules/Elsa.Environments/Endpoints/Environments/List/Endpoint.cs new file mode 100644 index 000000000..58c6d42ce --- /dev/null +++ b/src/modules/Elsa.Environments/Endpoints/Environments/List/Endpoint.cs @@ -0,0 +1,32 @@ +using Elsa.Abstractions; +using Elsa.Environments.Contracts; +using Elsa.Environments.Models; +using JetBrains.Annotations; + +namespace Elsa.Environments.Endpoints.Environments.List; + +[PublicAPI] +internal class Endpoint : ElsaEndpointWithoutRequest +{ + private readonly IEnvironmentsManager _environmentsManager; + + public Endpoint(IEnvironmentsManager environmentsManager) + { + _environmentsManager = environmentsManager; + } + + public override void Configure() + { + Get("/environments"); + ConfigurePermissions("read:environments"); + } + + public override async Task ExecuteAsync(CancellationToken cancellationToken) + { + var environments = await _environmentsManager.ListEnvironmentsAsync(cancellationToken); + return new(environments.ToList()); + } +} + +[PublicAPI] +internal record Response(ICollection Environments); \ No newline at end of file diff --git a/src/modules/Elsa.Environments/Extensions/ModuleExtensions.cs b/src/modules/Elsa.Environments/Extensions/ModuleExtensions.cs new file mode 100644 index 000000000..28a6f16ec --- /dev/null +++ b/src/modules/Elsa.Environments/Extensions/ModuleExtensions.cs @@ -0,0 +1,25 @@ +using Elsa.Environments.Features; +using Elsa.Features.Services; +using JetBrains.Annotations; + +// ReSharper disable once CheckNamespace +namespace Elsa.Extensions; + +/// +/// Extension methods for to add environment services. +/// +[PublicAPI] +public static class ModuleExtensions +{ + /// + /// Adds support for environments. + /// + /// The module. + /// The configuration delegate. + /// The module. + public static IModule UseEnvironments(this IModule module, Action? configure = default) + { + module.Use(configure); + return module; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Environments/Features/EnvironmentsFeature.cs b/src/modules/Elsa.Environments/Features/EnvironmentsFeature.cs new file mode 100644 index 000000000..91897c818 --- /dev/null +++ b/src/modules/Elsa.Environments/Features/EnvironmentsFeature.cs @@ -0,0 +1,43 @@ +using Elsa.Environments.Contracts; +using Elsa.Environments.Options; +using Elsa.Environments.Providers; +using Elsa.Environments.Services; +using Elsa.Extensions; +using Elsa.Features.Abstractions; +using Elsa.Features.Services; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Environments.Features; + +/// +/// Installs the feature. +/// +[PublicAPI] +public class EnvironmentsFeature : FeatureBase +{ + /// + public EnvironmentsFeature(IModule module) : base(module) + { + } + + /// + /// A delegate to configure the . + /// + public Action EnvironmentsOptions { get; set; } = _ => { }; + + /// + public override void Configure() + { + Module.AddFastEndpointsAssembly(GetType()); + } + + /// + public override void Apply() + { + Services.Configure(EnvironmentsOptions); + + Services.AddSingleton(); + Services.AddSingleton(); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Environments/FodyWeavers.xml b/src/modules/Elsa.Environments/FodyWeavers.xml new file mode 100644 index 000000000..00e1d9a1c --- /dev/null +++ b/src/modules/Elsa.Environments/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/modules/Elsa.Environments/Models/WorkflowsEnvironment.cs b/src/modules/Elsa.Environments/Models/WorkflowsEnvironment.cs new file mode 100644 index 000000000..607be96bc --- /dev/null +++ b/src/modules/Elsa.Environments/Models/WorkflowsEnvironment.cs @@ -0,0 +1,22 @@ +namespace Elsa.Environments.Models; + +/// +/// Represents the environment in which the workflow engine is running. +/// +public class WorkflowsEnvironment +{ + /// + /// The name of the environment. + /// + public string Name { get; set; } = default!; + + /// + /// The URL of the server hosting the workflow engine. + /// + public Uri ServerUrl { get; set; } = default!; + + /// + /// A dictionary of custom properties. + /// + public IDictionary CustomProperties { get; set; } = new Dictionary(); +} \ No newline at end of file diff --git a/src/modules/Elsa.Environments/Options/EnvironmentsOptions.cs b/src/modules/Elsa.Environments/Options/EnvironmentsOptions.cs new file mode 100644 index 000000000..a0a970375 --- /dev/null +++ b/src/modules/Elsa.Environments/Options/EnvironmentsOptions.cs @@ -0,0 +1,14 @@ +using Elsa.Environments.Models; + +namespace Elsa.Environments.Options; + +/// +/// EnvironmentsOptions for providing environments. +/// +public class EnvironmentsOptions +{ + /// + /// Gets or sets a list of environments. + /// + public ICollection Environments { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/modules/Elsa.Environments/Providers/ConfigurationEnvironmentsProvider.cs b/src/modules/Elsa.Environments/Providers/ConfigurationEnvironmentsProvider.cs new file mode 100644 index 000000000..bef5fa3bd --- /dev/null +++ b/src/modules/Elsa.Environments/Providers/ConfigurationEnvironmentsProvider.cs @@ -0,0 +1,28 @@ +using Elsa.Environments.Contracts; +using Elsa.Environments.Models; +using Elsa.Environments.Options; +using Microsoft.Extensions.Options; + +namespace Elsa.Environments.Providers; + +/// +/// An implementation of that reads environment definitions from configuration. +/// +public class ConfigurationEnvironmentsProvider : IEnvironmentsProvider +{ + private readonly IOptions _options; + + /// + /// Initializes a new instance of . + /// + public ConfigurationEnvironmentsProvider(IOptions options) + { + _options = options; + } + + /// + public ValueTask> GetEnvironmentsAsync(CancellationToken cancellationToken = default) + { + return new (_options.Value.Environments); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Environments/Services/DefaultEnvironmentsManager.cs b/src/modules/Elsa.Environments/Services/DefaultEnvironmentsManager.cs new file mode 100644 index 000000000..c94170933 --- /dev/null +++ b/src/modules/Elsa.Environments/Services/DefaultEnvironmentsManager.cs @@ -0,0 +1,32 @@ +using Elsa.Environments.Contracts; +using Elsa.Environments.Models; + +namespace Elsa.Environments.Services; + +/// +public class DefaultEnvironmentsManager : IEnvironmentsManager +{ + private readonly IEnumerable _providers; + + /// + /// Initializes a new instance of . + /// + public DefaultEnvironmentsManager(IEnumerable providers) + { + _providers = providers; + } + + /// + public async Task> ListEnvironmentsAsync(CancellationToken cancellationToken = default) + { + var allEnvironments = new List(); + + foreach (var provider in _providers) + { + var environments = await provider.GetEnvironmentsAsync(cancellationToken); + allEnvironments.AddRange(environments); + } + + return allEnvironments; + } +} \ No newline at end of file