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

In Auto setup, when IsUninitialized, response http status code 503 #16834

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
5c0cfbe
In Auto setup, when IsUninitialized, response http status code 409
infofromca Oct 4, 2024
cbb43d2
Remove ILogger
infofromca Oct 4, 2024
1573e53
Update src/OrchardCore.Modules/OrchardCore.AutoSetup/Services/IAutoSe…
infofromca Oct 4, 2024
ff715ae
Update src/OrchardCore.Modules/OrchardCore.AutoSetup/Services/AutoSet…
infofromca Oct 4, 2024
5ffc4aa
Update src/OrchardCore.Modules/OrchardCore.AutoSetup/Services/AutoSet…
infofromca Oct 4, 2024
c02123c
Update src/OrchardCore.Modules/OrchardCore.AutoSetup/Services/AutoSet…
infofromca Oct 4, 2024
281daa4
Update src/OrchardCore.Modules/OrchardCore.AutoSetup/Services/AutoSet…
infofromca Oct 4, 2024
511dc12
Update src/OrchardCore.Modules/OrchardCore.AutoSetup/Services/AutoSet…
infofromca Oct 4, 2024
7a51f44
Move the docs to the interface
infofromca Oct 4, 2024
1fbc8c6
Update src/OrchardCore.Modules/OrchardCore.AutoSetup/Services/IAutoSe…
infofromca Oct 4, 2024
6f99df3
Update src/OrchardCore.Modules/OrchardCore.AutoSetup/Services/AutoSet…
infofromca Oct 4, 2024
9c25005
change to 503
infofromca Oct 4, 2024
2d52d80
Consider the failure situation
infofromca Oct 5, 2024
37aaf73
Merge branch 'main' into 409
hishamco Oct 5, 2024
1cf54b3
Show the error message to the user
infofromca Oct 11, 2024
23333d7
Test -- InvokeAsync_InitializedShell_SkipsSetup
infofromca Oct 12, 2024
4f665ce
InvokeAsync_FailedSetup_ReturnsServiceUnavailable
infofromca Oct 12, 2024
4255865
InvokeAsync_FailedLockAcquisition_ThrowsTimeoutException
infofromca Oct 12, 2024
ed75f46
InvokeAsync_UnInitializedShell_PerformsSetup
infofromca Oct 12, 2024
889565e
Clean
infofromca Oct 12, 2024
b039be0
Clean
infofromca Oct 12, 2024
13ba574
Correct DI scope for IAutoSetupService
infofromca Oct 29, 2024
debdf12
Won't stop other Middlewares after success
infofromca Oct 29, 2024
9d0902f
Code styling
Piedone Oct 29, 2024
29a8a10
Shouldn't write error messages to the response directly
infofromca Oct 30, 2024
4b5ae4b
Merge branch 'main' into 409
hishamco Nov 2, 2024
3c806ab
Update test/OrchardCore.Tests/Modules/OrchardCore.AutoSetup/AutoSetup…
infofromca Nov 2, 2024
30cf024
Update test/OrchardCore.Tests/Modules/OrchardCore.AutoSetup/AutoSetup…
infofromca Nov 2, 2024
d02b9e1
Update test/OrchardCore.Tests/Modules/OrchardCore.AutoSetup/AutoSetup…
infofromca Nov 2, 2024
703a622
Update src/OrchardCore.Modules/OrchardCore.AutoSetup/Services/AutoSet…
infofromca Nov 2, 2024
e6be8ef
middleware creation to make reusable across the unit tests
infofromca Nov 11, 2024
e939807
create a private method for middleware creation to make reusable acro…
infofromca Nov 11, 2024
5f3fb82
Merge branch 'main' into 409
hishamco Nov 11, 2024
c8b4dd8
Update test/OrchardCore.Tests/Modules/OrchardCore.AutoSetup/AutoSetup…
infofromca Nov 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 7 additions & 124 deletions src/OrchardCore.Modules/OrchardCore.AutoSetup/AutoSetupMiddleware.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OrchardCore.Abstractions.Setup;
using OrchardCore.AutoSetup.Extensions;
using OrchardCore.AutoSetup.Options;
using OrchardCore.AutoSetup.Services;
using OrchardCore.Environment.Shell;
using OrchardCore.Locking.Distributed;
using OrchardCore.Setup.Services;

namespace OrchardCore.AutoSetup;

Expand Down Expand Up @@ -47,11 +44,6 @@ public class AutoSetupMiddleware
/// </summary>
private readonly AutoSetupOptions _options;

/// <summary>
/// The logger.
/// </summary>
private readonly ILogger _logger;

/// <summary>
/// The auto setup lock options.
/// </summary>
Expand All @@ -70,25 +62,21 @@ public class AutoSetupMiddleware
/// <param name="shellSettings">The shell settings.</param>
/// <param name="shellSettingsManager">The shell settings manager.</param>
/// <param name="distributedLock">The distributed lock.</param>
/// <param name="options">The auto setup options.</param>
/// <param name="logger">The logger.</param>
/// <param name="options">The auto setup options.</param>
public AutoSetupMiddleware(
RequestDelegate next,
IShellHost shellHost,
ShellSettings shellSettings,
IShellSettingsManager shellSettingsManager,
IDistributedLock distributedLock,
IOptions<AutoSetupOptions> options,
ILogger<AutoSetupMiddleware> logger)
IOptions<AutoSetupOptions> options)
{
_next = next;
_shellHost = shellHost;
_shellSettings = shellSettings;
_shellSettingsManager = shellSettingsManager;
_distributedLock = distributedLock;
_options = options.Value;
_logger = logger;

_lockOptions = _options.LockOptions;
_setupOptions = _options.Tenants.FirstOrDefault(options => _shellSettings.Name == options.ShellName);
}
Expand Down Expand Up @@ -131,13 +119,13 @@ public async Task InvokeAsync(HttpContext httpContext)
if (!settings.IsUninitialized())
{
await _shellHost.ReloadShellContextAsync(_shellSettings, eventSource: false);
httpContext.Response.Redirect(pathBase);
httpContext.Response.StatusCode = 409;
Piedone marked this conversation as resolved.
Show resolved Hide resolved

return;
}

var setupService = httpContext.RequestServices.GetRequiredService<ISetupService>();
if (await SetupTenantAsync(setupService, _setupOptions, _shellSettings))
var autoSetupService = httpContext.RequestServices.GetRequiredService<IAutoSetupService>();
if (await autoSetupService.SetupTenantAsync(_setupOptions, _shellSettings))
{
if (_setupOptions.IsDefault)
{
Expand All @@ -146,7 +134,7 @@ public async Task InvokeAsync(HttpContext httpContext)
{
if (_setupOptions != setupOptions)
{
await CreateTenantSettingsAsync(setupOptions);
await autoSetupService.CreateTenantSettingsAsync(setupOptions);
}
}
}
Expand All @@ -160,109 +148,4 @@ public async Task InvokeAsync(HttpContext httpContext)

await _next.Invoke(httpContext);
}

/// <summary>
/// Sets up a tenant.
/// </summary>
/// <param name="setupService">The setup service.</param>
/// <param name="setupOptions">The tenant setup options.</param>
/// <param name="shellSettings">The tenant shell settings.</param>
/// <returns>
/// Returns <c>true</c> if successfully setup.
/// </returns>
public async Task<bool> SetupTenantAsync(ISetupService setupService, TenantSetupOptions setupOptions, ShellSettings shellSettings)
{
var setupContext = await GetSetupContextAsync(setupOptions, setupService, shellSettings);

_logger.LogInformation("AutoSetup is initializing the site");

await setupService.SetupAsync(setupContext);

if (setupContext.Errors.Count == 0)
{
_logger.LogInformation("AutoSetup successfully provisioned the site '{SiteName}'.", setupOptions.SiteName);

return true;
}

var stringBuilder = new StringBuilder();
foreach (var error in setupContext.Errors)
{
stringBuilder.AppendLine($"{error.Key} : '{error.Value}'");
}

_logger.LogError("AutoSetup failed installing the site '{SiteName}' with errors: {Errors}", setupOptions.SiteName, stringBuilder);

return false;
}

/// <summary>
/// Creates a tenant shell settings.
/// </summary>
/// <param name="setupOptions">The setup options.</param>
/// <returns>The <see cref="ShellSettings"/>.</returns>
public async Task<ShellSettings> CreateTenantSettingsAsync(TenantSetupOptions setupOptions)
{
using var shellSettings = _shellSettingsManager
.CreateDefaultSettings()
.AsUninitialized()
.AsDisposable();

shellSettings.Name = setupOptions.ShellName;
shellSettings.RequestUrlHost = setupOptions.RequestUrlHost;
shellSettings.RequestUrlPrefix = setupOptions.RequestUrlPrefix;

shellSettings["ConnectionString"] = setupOptions.DatabaseConnectionString;
shellSettings["TablePrefix"] = setupOptions.DatabaseTablePrefix;
shellSettings["Schema"] = setupOptions.DatabaseSchema;
shellSettings["DatabaseProvider"] = setupOptions.DatabaseProvider;
shellSettings["Secret"] = Guid.NewGuid().ToString();
shellSettings["RecipeName"] = setupOptions.RecipeName;
shellSettings["FeatureProfile"] = setupOptions.FeatureProfile;

await _shellHost.UpdateShellSettingsAsync(shellSettings);

return shellSettings;
}

/// <summary>
/// Gets a setup context from the configuration.
/// </summary>
/// <param name="options">The tenant setup options.</param>
/// <param name="setupService">The setup service.</param>
/// <param name="shellSettings">The tenant shell settings.</param>
/// <returns> The <see cref="SetupContext"/> used to setup the site.</returns>
private static async Task<SetupContext> GetSetupContextAsync(TenantSetupOptions options, ISetupService setupService, ShellSettings shellSettings)
{
var recipes = await setupService.GetSetupRecipesAsync();

var recipe = recipes.SingleOrDefault(r => r.Name == options.RecipeName);

var setupContext = new SetupContext
{
Recipe = recipe,
ShellSettings = shellSettings,
Errors = new Dictionary<string, string>()
};

if (shellSettings.IsDefaultShell())
{
// The 'Default' shell is first created by the infrastructure,
// so the following 'Autosetup' options need to be passed.
shellSettings.RequestUrlHost = options.RequestUrlHost;
shellSettings.RequestUrlPrefix = options.RequestUrlPrefix;
}

setupContext.Properties[SetupConstants.AdminEmail] = options.AdminEmail;
setupContext.Properties[SetupConstants.AdminPassword] = options.AdminPassword;
setupContext.Properties[SetupConstants.AdminUsername] = options.AdminUsername;
setupContext.Properties[SetupConstants.DatabaseConnectionString] = options.DatabaseConnectionString;
setupContext.Properties[SetupConstants.DatabaseProvider] = options.DatabaseProvider;
setupContext.Properties[SetupConstants.DatabaseTablePrefix] = options.DatabaseTablePrefix;
setupContext.Properties[SetupConstants.DatabaseSchema] = options.DatabaseSchema;
setupContext.Properties[SetupConstants.SiteName] = options.SiteName;
setupContext.Properties[SetupConstants.SiteTimeZone] = options.SiteTimeZone;

return setupContext;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using System.Text;
using Microsoft.Extensions.Logging;
using OrchardCore.Abstractions.Setup;
using OrchardCore.AutoSetup.Options;
using OrchardCore.Environment.Shell;
using OrchardCore.Setup.Services;

namespace OrchardCore.AutoSetup.Services;
public class AutoSetupService : IAutoSetupService
infofromca marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// The shell host.
/// </summary>
private readonly IShellHost _shellHost;

/// <summary>
/// The shell settings manager.
/// </summary>
private readonly IShellSettingsManager _shellSettingsManager;

/// <summary>
/// The setup service.
/// </summary>
private readonly ISetupService _setupService;

/// <summary>
/// The logger.
/// </summary>
private readonly ILogger _logger;
infofromca marked this conversation as resolved.
Show resolved Hide resolved

public AutoSetupService(
IShellHost shellHost,
IShellSettingsManager shellSettingsManager,
ISetupService setupService,
ILogger<AutoSetupService> logger
)
{
_shellHost = shellHost;
_shellSettingsManager = shellSettingsManager;
_setupService = setupService;
_logger = logger;
}

/// <summary>
/// Sets up a tenant.
/// </summary>
/// <param name="setupOptions">The tenant setup options.</param>
/// <param name="shellSettings">The tenant shell settings.</param>
/// <returns>
/// Returns <c>true</c> if successfully setup.
/// </returns>
public async Task<bool> SetupTenantAsync(TenantSetupOptions setupOptions, ShellSettings shellSettings)
{
var setupContext = await GetSetupContextAsync(setupOptions, shellSettings);

_logger.LogInformation("AutoSetup is initializing the site");
infofromca marked this conversation as resolved.
Show resolved Hide resolved

await _setupService.SetupAsync(setupContext);

if (setupContext.Errors.Count == 0)
{
_logger.LogInformation("AutoSetup successfully provisioned the site '{SiteName}'.", setupOptions.SiteName);
infofromca marked this conversation as resolved.
Show resolved Hide resolved

return true;
}

var stringBuilder = new StringBuilder();
foreach (var error in setupContext.Errors)
{
stringBuilder.AppendLine($"{error.Key} : '{error.Value}'");
}

_logger.LogError("AutoSetup failed installing the site '{SiteName}' with errors: {Errors}", setupOptions.SiteName, stringBuilder);
infofromca marked this conversation as resolved.
Show resolved Hide resolved

return false;
}

/// <summary>
hishamco marked this conversation as resolved.
Show resolved Hide resolved
/// Creates a tenant shell settings.
/// </summary>
/// <param name="setupOptions">The setup options.</param>
/// <returns>The <see cref="ShellSettings"/>.</returns>
public async Task<ShellSettings> CreateTenantSettingsAsync(TenantSetupOptions setupOptions)
{
using var shellSettings = _shellSettingsManager
.CreateDefaultSettings()
.AsUninitialized()
.AsDisposable();

shellSettings.Name = setupOptions.ShellName;
shellSettings.RequestUrlHost = setupOptions.RequestUrlHost;
shellSettings.RequestUrlPrefix = setupOptions.RequestUrlPrefix;

infofromca marked this conversation as resolved.
Show resolved Hide resolved
shellSettings["ConnectionString"] = setupOptions.DatabaseConnectionString;
shellSettings["TablePrefix"] = setupOptions.DatabaseTablePrefix;
shellSettings["Schema"] = setupOptions.DatabaseSchema;
shellSettings["DatabaseProvider"] = setupOptions.DatabaseProvider;
shellSettings["Secret"] = Guid.NewGuid().ToString();
shellSettings["RecipeName"] = setupOptions.RecipeName;
shellSettings["FeatureProfile"] = setupOptions.FeatureProfile;

await _shellHost.UpdateShellSettingsAsync(shellSettings);

return shellSettings;
}

/// <summary>
/// Gets a setup context from the configuration.
/// </summary>
/// <param name="options">The tenant setup options.</param>
/// <param name="shellSettings">The tenant shell settings.</param>
/// <returns> The <see cref="SetupContext"/> used to setup the site.</returns>
public async Task<SetupContext> GetSetupContextAsync(TenantSetupOptions options, ShellSettings shellSettings)
{
var recipes = await _setupService.GetSetupRecipesAsync();

var recipe = recipes.SingleOrDefault(r => r.Name == options.RecipeName);
infofromca marked this conversation as resolved.
Show resolved Hide resolved

var setupContext = new SetupContext
{
Recipe = recipe,
ShellSettings = shellSettings,
Errors = new Dictionary<string, string>()
};

if (shellSettings.IsDefaultShell())
{
// The 'Default' shell is first created by the infrastructure,
// so the following 'Autosetup' options need to be passed.
shellSettings.RequestUrlHost = options.RequestUrlHost;
shellSettings.RequestUrlPrefix = options.RequestUrlPrefix;
}

setupContext.Properties[SetupConstants.AdminEmail] = options.AdminEmail;
setupContext.Properties[SetupConstants.AdminPassword] = options.AdminPassword;
setupContext.Properties[SetupConstants.AdminUsername] = options.AdminUsername;
setupContext.Properties[SetupConstants.DatabaseConnectionString] = options.DatabaseConnectionString;
setupContext.Properties[SetupConstants.DatabaseProvider] = options.DatabaseProvider;
setupContext.Properties[SetupConstants.DatabaseTablePrefix] = options.DatabaseTablePrefix;
setupContext.Properties[SetupConstants.DatabaseSchema] = options.DatabaseSchema;
setupContext.Properties[SetupConstants.SiteName] = options.SiteName;
setupContext.Properties[SetupConstants.SiteTimeZone] = options.SiteTimeZone;

return setupContext;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using OrchardCore.AutoSetup.Options;
using OrchardCore.Environment.Shell;
using OrchardCore.Setup.Services;

namespace OrchardCore.AutoSetup.Services;
public interface IAutoSetupService
{
Task<ShellSettings> CreateTenantSettingsAsync(TenantSetupOptions setupOptions);
Task<SetupContext> GetSetupContextAsync(TenantSetupOptions options, ShellSettings shellSettings);
infofromca marked this conversation as resolved.
Show resolved Hide resolved
Task<bool> SetupTenantAsync(TenantSetupOptions setupOptions, ShellSettings shellSettings);
}
3 changes: 3 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.AutoSetup/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OrchardCore.AutoSetup.Options;
using OrchardCore.AutoSetup.Services;
using OrchardCore.Environment.Shell;
using OrchardCore.Environment.Shell.Configuration;
using OrchardCore.Modules;
Expand Down Expand Up @@ -67,6 +68,8 @@ public override void ConfigureServices(IServiceCollection services)
{
services.Configure<AutoSetupOptions>(o => o.ConfigurationExists = true);
}

services.AddScoped<IAutoSetupService, AutoSetupService>();
}

/// <summary>
Expand Down