From 144014dc734aacbc9b27e100e8f8797b0e011d34 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 10 Jun 2021 08:06:17 -0600 Subject: [PATCH] Fixes runtime state and boot failed middleware so that it actually runs. Separates out unattended install/upgrade into notification handlers. --- .../RuntimeUnattendedInstallNotification.cs | 13 ++ .../RuntimeUnattendedUpgradeNotification.cs | 24 ++++ .../Packaging/InstallationSummary.cs | 25 ++++ src/Umbraco.Core/Services/IRuntimeState.cs | 6 +- .../UmbracoBuilder.CoreServices.cs | 3 + .../Install/UnattendedInstaller.cs | 134 ++++++++++++++++++ .../Install/UnattendedUpgrader.cs | 56 ++++++++ .../ImportPackageBuilderExpression.cs | 42 ++++-- .../Runtime/CoreRuntime.cs | 38 ++--- .../{ => Runtime}/RuntimeState.cs | 90 +----------- 10 files changed, 303 insertions(+), 128 deletions(-) create mode 100644 src/Umbraco.Core/Notifications/RuntimeUnattendedInstallNotification.cs create mode 100644 src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs create mode 100644 src/Umbraco.Infrastructure/Install/UnattendedInstaller.cs create mode 100644 src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs rename src/Umbraco.Infrastructure/{ => Runtime}/RuntimeState.cs (78%) diff --git a/src/Umbraco.Core/Notifications/RuntimeUnattendedInstallNotification.cs b/src/Umbraco.Core/Notifications/RuntimeUnattendedInstallNotification.cs new file mode 100644 index 0000000000..f638ec2d3c --- /dev/null +++ b/src/Umbraco.Core/Notifications/RuntimeUnattendedInstallNotification.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Cms.Core.Notifications +{ + /// + /// Used to notify when the core runtime can do an unattended install. + /// + /// + /// It is entirely up to the handler to determine if an unattended installation should occur and + /// to perform the logic. + /// + public class RuntimeUnattendedInstallNotification : INotification + { + } +} diff --git a/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs b/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs new file mode 100644 index 0000000000..4d8e69120c --- /dev/null +++ b/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs @@ -0,0 +1,24 @@ +namespace Umbraco.Cms.Core.Notifications +{ + /// + /// Used to notify when the core runtime can do an unattended upgrade. + /// + /// + /// It is entirely up to the handler to determine if an unattended upgrade should occur and + /// to perform the logic. + /// + public class RuntimeUnattendedUpgradeNotification : INotification + { + /// + /// Gets/sets the result of the unattended upgrade + /// + public UpgradeResult UnattendedUpgradeResult { get; set; } = UpgradeResult.NotRequired; + + public enum UpgradeResult + { + NotRequired = 0, + CoreUpgradeComplete = 100, + PackageMigrationComplete = 101 + } + } +} diff --git a/src/Umbraco.Core/Packaging/InstallationSummary.cs b/src/Umbraco.Core/Packaging/InstallationSummary.cs index a6448f99f8..2c6be59e0f 100644 --- a/src/Umbraco.Core/Packaging/InstallationSummary.cs +++ b/src/Umbraco.Core/Packaging/InstallationSummary.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; +using System.Text; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Packaging; @@ -22,6 +23,30 @@ namespace Umbraco.Cms.Core.Packaging public IEnumerable ContentInstalled { get; set; } = Enumerable.Empty(); public IEnumerable MediaInstalled { get; set; } = Enumerable.Empty(); + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("Content items installed: "); + sb.Append(ContentInstalled.Count()); + sb.Append("Media items installed: "); + sb.Append(MediaInstalled.Count()); + sb.Append("Dictionary items installed: "); + sb.Append(DictionaryItemsInstalled.Count()); + sb.Append("Macros installed: "); + sb.Append(MacrosInstalled.Count()); + sb.Append("Stylesheets installed: "); + sb.Append(StylesheetsInstalled.Count()); + sb.Append("Templates installed: "); + sb.Append(TemplatesInstalled.Count()); + sb.Append("Templates installed: "); + sb.Append("Document types installed: "); + sb.Append(DocumentTypesInstalled.Count()); + sb.Append("Media types installed: "); + sb.Append(MediaTypesInstalled.Count()); + sb.Append("Data types items installed: "); + sb.Append(DataTypesInstalled.Count()); + return sb.ToString(); + } } } diff --git a/src/Umbraco.Core/Services/IRuntimeState.cs b/src/Umbraco.Core/Services/IRuntimeState.cs index e36c10290e..a138fa0aae 100644 --- a/src/Umbraco.Core/Services/IRuntimeState.cs +++ b/src/Umbraco.Core/Services/IRuntimeState.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Cms.Core.Exceptions; using Umbraco.Cms.Core.Semver; @@ -54,8 +54,6 @@ namespace Umbraco.Cms.Core.Services /// void DetermineRuntimeLevel(); - void Configure(RuntimeLevel level, RuntimeLevelReason reason); - - void DoUnattendedInstall(); + void Configure(RuntimeLevel level, RuntimeLevelReason reason, Exception bootFailedException = null); } } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 8e230c4963..e9197c818f 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -16,6 +16,7 @@ using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Migrations; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Packaging; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.PropertyEditors.ValueConverters; @@ -61,6 +62,8 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(factory => factory.GetRequiredService().SqlContext); builder.Services.AddUnique(); builder.Services.AddUnique(); + builder.AddNotificationAsyncHandler(); + builder.AddNotificationAsyncHandler(); // composers builder diff --git a/src/Umbraco.Infrastructure/Install/UnattendedInstaller.cs b/src/Umbraco.Infrastructure/Install/UnattendedInstaller.cs new file mode 100644 index 0000000000..babf882e1b --- /dev/null +++ b/src/Umbraco.Infrastructure/Install/UnattendedInstaller.cs @@ -0,0 +1,134 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Exceptions; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Migrations.Install; +using Umbraco.Cms.Infrastructure.Persistence; + +namespace Umbraco.Cms.Infrastructure.Install +{ + public class UnattendedInstaller : INotificationAsyncHandler + { + private readonly IOptions _unattendedSettings; + + private readonly DatabaseSchemaCreatorFactory _databaseSchemaCreatorFactory; + private readonly IEventAggregator _eventAggregator; + private readonly IUmbracoDatabaseFactory _databaseFactory; + private readonly IOptions _globalSettings; + private readonly ILogger _logger; + private readonly IRuntimeState _runtimeState; + + public UnattendedInstaller( + DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory, + IEventAggregator eventAggregator, + IOptions unattendedSettings, + IUmbracoDatabaseFactory databaseFactory, + IOptions globalSettings, + ILogger logger, + IRuntimeState runtimeState) + { + _databaseSchemaCreatorFactory = databaseSchemaCreatorFactory ?? throw new ArgumentNullException(nameof(databaseSchemaCreatorFactory)); + _eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator)); + _unattendedSettings = unattendedSettings; + _databaseFactory = databaseFactory; + _globalSettings = globalSettings; + _logger = logger; + _runtimeState = runtimeState; + } + + public Task HandleAsync(RuntimeUnattendedInstallNotification notification, CancellationToken cancellationToken) + { + // unattended install is not enabled + if (_unattendedSettings.Value.InstallUnattended == false) + { + return Task.CompletedTask; + } + + // no connection string set + if (_databaseFactory.Configured == false) + { + return Task.CompletedTask; + } + + var tries = _globalSettings.Value.InstallMissingDatabase ? 2 : 5; + + bool connect; + try + { + for (var i = 0; ;) + { + connect = _databaseFactory.CanConnect; + if (connect || ++i == tries) + { + break; + } + + _logger.LogDebug("Could not immediately connect to database, trying again."); + Thread.Sleep(1000); + } + } + catch (Exception ex) + { + _logger.LogInformation(ex, "Error during unattended install."); + + var innerException = new UnattendedInstallException("Unattended installation failed.", ex); + _runtimeState.Configure(Core.RuntimeLevel.BootFailed, Core.RuntimeLevelReason.BootFailedOnException, innerException); + return Task.CompletedTask; + } + + // could not connect to the database + if (connect == false) + { + return Task.CompletedTask; + } + + IUmbracoDatabase database = null; + try + { + using (database = _databaseFactory.CreateDatabase()) + { + var hasUmbracoTables = database.IsUmbracoInstalled(); + + // database has umbraco tables, assume Umbraco is already installed + if (hasUmbracoTables) + { + return Task.CompletedTask; + } + + // all conditions fulfilled, do the install + _logger.LogInformation("Starting unattended install."); + + database.BeginTransaction(); + DatabaseSchemaCreator creator = _databaseSchemaCreatorFactory.Create(database); + creator.InitializeDatabaseSchema(); + database.CompleteTransaction(); + _logger.LogInformation("Unattended install completed."); + + // Emit an event with EventAggregator that unattended install completed + // Then this event can be listened for and create an unattended user + _eventAggregator.Publish(new UnattendedInstallNotification()); + } + } + catch (Exception ex) + { + _logger.LogInformation(ex, "Error during unattended install."); + database?.AbortTransaction(); + + var innerException = new UnattendedInstallException( + "The database configuration failed." + + "\n Please check log file for additional information (can be found in '/Umbraco/Data/Logs/')", + ex); + + _runtimeState.Configure(Core.RuntimeLevel.BootFailed, Core.RuntimeLevelReason.BootFailedOnException, innerException); + } + + return Task.CompletedTask; + } + } +} diff --git a/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs b/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs new file mode 100644 index 0000000000..5d6b45eb2e --- /dev/null +++ b/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs @@ -0,0 +1,56 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Exceptions; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Migrations.Install; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade; +using Umbraco.Cms.Infrastructure.Runtime; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Install +{ + public class UnattendedUpgrader : INotificationAsyncHandler + { + private readonly IProfilingLogger _profilingLogger; + private readonly IUmbracoVersion _umbracoVersion; + private readonly DatabaseBuilder _databaseBuilder; + private readonly IRuntimeState _runtimeState; + + public UnattendedUpgrader(IProfilingLogger profilingLogger, IUmbracoVersion umbracoVersion, DatabaseBuilder databaseBuilder, IRuntimeState runtimeState) + { + _profilingLogger = profilingLogger ?? throw new ArgumentNullException(nameof(profilingLogger)); + _umbracoVersion = umbracoVersion ?? throw new ArgumentNullException(nameof(umbracoVersion)); + _databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder)); + _runtimeState = runtimeState ?? throw new ArgumentNullException(nameof(runtimeState)); + } + + public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, CancellationToken cancellationToken) + { + if (_runtimeState.RunUnattendedBootLogic()) + { + // TODO: Here is also where we would run package migrations! + + var plan = new UmbracoPlan(_umbracoVersion); + using (_profilingLogger.TraceDuration("Starting unattended upgrade.", "Unattended upgrade completed.")) + { + DatabaseBuilder.Result result = _databaseBuilder.UpgradeSchemaAndData(plan); + if (result.Success == false) + { + var innerException = new UnattendedInstallException("An error occurred while running the unattended upgrade.\n" + result.Message); + _runtimeState.Configure(Core.RuntimeLevel.BootFailed, Core.RuntimeLevelReason.BootFailedOnException, innerException); + return Task.CompletedTask; + } + + notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.CoreUpgradeComplete; + } + } + + return Task.CompletedTask; + } + } +} diff --git a/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilderExpression.cs b/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilderExpression.cs index 76ac87dc76..e51c617f0a 100644 --- a/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilderExpression.cs +++ b/src/Umbraco.Infrastructure/Packaging/ImportPackageBuilderExpression.cs @@ -1,7 +1,10 @@ using System; using System.IO; +using System.Linq; using System.Reflection; +using System.Text; using System.Xml.Linq; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Migrations; @@ -23,22 +26,33 @@ namespace Umbraco.Cms.Core.Packaging throw new InvalidOperationException($"Nothing to execute, {nameof(FromEmbeddedResource)} has not been called."); } - // lookup the embedded resource by convention - Type currentType = GetType(); - Assembly currentAssembly = currentType.Assembly; - var fileName = $"{currentType.Namespace}.package.xml"; - Stream stream = currentAssembly.GetManifestResourceStream(fileName); - if (stream == null) + try { - throw new FileNotFoundException("Cannot find the embedded file.", fileName); - } - XDocument xml; - using (stream) - { - xml = XDocument.Load(stream); - } + // lookup the embedded resource by convention + Type currentType = GetType(); + Assembly currentAssembly = currentType.Assembly; + var fileName = $"{currentType.Namespace}.package.xml"; + Stream stream = currentAssembly.GetManifestResourceStream(fileName); + if (stream == null) + { + throw new FileNotFoundException("Cannot find the embedded file.", fileName); + } + XDocument xml; + using (stream) + { + xml = XDocument.Load(stream); + } - // TODO: Use the packaging service + InstallationSummary installationSummary = _packagingService.InstallCompiledPackageData(xml); + + Logger.LogInformation($"Package migration executed. Summary: {installationSummary}"); + } + catch (Exception ex) + { + Logger.LogError(ex, "Package migration failed."); + + // TODO: We need to exit with a status + } } } } diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 955343e184..c2c8f4a24e 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -30,7 +30,6 @@ namespace Umbraco.Cms.Infrastructure.Runtime private readonly IUmbracoDatabaseFactory _databaseFactory; private readonly IEventAggregator _eventAggregator; private readonly IHostingEnvironment _hostingEnvironment; - private readonly DatabaseBuilder _databaseBuilder; private readonly IUmbracoVersion _umbracoVersion; private CancellationToken _cancellationToken; @@ -47,7 +46,6 @@ namespace Umbraco.Cms.Infrastructure.Runtime IUmbracoDatabaseFactory databaseFactory, IEventAggregator eventAggregator, IHostingEnvironment hostingEnvironment, - DatabaseBuilder databaseBuilder, IUmbracoVersion umbracoVersion) { State = state; @@ -59,7 +57,6 @@ namespace Umbraco.Cms.Infrastructure.Runtime _databaseFactory = databaseFactory; _eventAggregator = eventAggregator; _hostingEnvironment = hostingEnvironment; - _databaseBuilder = databaseBuilder; _umbracoVersion = umbracoVersion; _logger = _loggerFactory.CreateLogger(); } @@ -104,7 +101,8 @@ namespace Umbraco.Cms.Infrastructure.Runtime // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate AcquireMainDom(); - DoUnattendedInstall(); + // notify for unattended install + await _eventAggregator.PublishAsync(new RuntimeUnattendedInstallNotification()); DetermineRuntimeLevel(); if (!State.UmbracoCanBoot()) @@ -119,11 +117,10 @@ namespace Umbraco.Cms.Infrastructure.Runtime } // if level is Run and reason is UpgradeMigrations, that means we need to perform an unattended upgrade - if (State.RunUnattendedBootLogic()) + var unattendedUpgradeNotification = new RuntimeUnattendedUpgradeNotification(); + await _eventAggregator.PublishAsync(unattendedUpgradeNotification); + if ((int)unattendedUpgradeNotification.UnattendedUpgradeResult >= 100) { - // do the upgrade - DoUnattendedUpgrade(); - // upgrade is done, set reason to Run DetermineRuntimeLevel(); } @@ -134,25 +131,6 @@ namespace Umbraco.Cms.Infrastructure.Runtime await _eventAggregator.PublishAsync(new UmbracoApplicationStartingNotification(State.Level), cancellationToken); } - private void DoUnattendedUpgrade() - { - // TODO: Here is also where we would run package migrations! - - var plan = new UmbracoPlan(_umbracoVersion); - using (_profilingLogger.TraceDuration("Starting unattended upgrade.", "Unattended upgrade completed.")) - { - var result = _databaseBuilder.UpgradeSchemaAndData(plan); - if (result.Success == false) - throw new UnattendedInstallException("An error occurred while running the unattended upgrade.\n" + result.Message); - } - - } - - private void DoUnattendedInstall() - { - State.DoUnattendedInstall(); - } - public async Task StopAsync(CancellationToken cancellationToken) { _components.Terminate(); @@ -178,6 +156,12 @@ namespace Umbraco.Cms.Infrastructure.Runtime private void DetermineRuntimeLevel() { + if (State.BootFailedException != null) + { + // there's already been an exception so cannot boot and no need to check + return; + } + using DisposableTimer timer = _profilingLogger.DebugDuration("Determining runtime level.", "Determined."); try diff --git a/src/Umbraco.Infrastructure/RuntimeState.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs similarity index 78% rename from src/Umbraco.Infrastructure/RuntimeState.cs rename to src/Umbraco.Infrastructure/Runtime/RuntimeState.cs index df07403d3d..f74b0eac50 100644 --- a/src/Umbraco.Infrastructure/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs @@ -4,20 +4,19 @@ using System.Linq; using System.Threading; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Exceptions; -using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Packaging; using Umbraco.Cms.Core.Semver; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Infrastructure.Migrations.Install; using Umbraco.Cms.Infrastructure.Migrations.Upgrade; using Umbraco.Cms.Infrastructure.Persistence; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Infrastructure.Runtime { + /// /// Represents the state of the Umbraco runtime. /// @@ -28,8 +27,6 @@ namespace Umbraco.Cms.Core private readonly IUmbracoVersion _umbracoVersion; private readonly IUmbracoDatabaseFactory _databaseFactory; private readonly ILogger _logger; - private readonly DatabaseSchemaCreatorFactory _databaseSchemaCreatorFactory; - private readonly IEventAggregator _eventAggregator; private readonly PackageMigrationPlanCollection _packageMigrationPlans; /// @@ -51,8 +48,6 @@ namespace Umbraco.Cms.Core IUmbracoVersion umbracoVersion, IUmbracoDatabaseFactory databaseFactory, ILogger logger, - DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory, - IEventAggregator eventAggregator, PackageMigrationPlanCollection packageMigrationPlans) { _globalSettings = globalSettings; @@ -60,8 +55,6 @@ namespace Umbraco.Cms.Core _umbracoVersion = umbracoVersion; _databaseFactory = databaseFactory; _logger = logger; - _databaseSchemaCreatorFactory = databaseSchemaCreatorFactory; - _eventAggregator = eventAggregator; _packageMigrationPlans = packageMigrationPlans; } @@ -227,83 +220,14 @@ namespace Umbraco.Cms.Core } } - public void Configure(RuntimeLevel level, RuntimeLevelReason reason) + public void Configure(RuntimeLevel level, RuntimeLevelReason reason, Exception bootFailedException = null) { Level = level; Reason = reason; - } - public void DoUnattendedInstall() - { - // unattended install is not enabled - if (_unattendedSettings.Value.InstallUnattended == false) + if (bootFailedException != null) { - return; - } - - // no connection string set - if (_databaseFactory.Configured == false) - { - return; - } - - var tries = _globalSettings.Value.InstallMissingDatabase ? 2 : 5; - - bool connect; - for (var i = 0; ;) - { - connect = _databaseFactory.CanConnect; - if (connect || ++i == tries) - { - break; - } - - _logger.LogDebug("Could not immediately connect to database, trying again."); - Thread.Sleep(1000); - } - - // could not connect to the database - if (connect == false) - { - return; - } - - using (var database = _databaseFactory.CreateDatabase()) - { - var hasUmbracoTables = database.IsUmbracoInstalled(); - - // database has umbraco tables, assume Umbraco is already installed - if (hasUmbracoTables) - return; - - // all conditions fulfilled, do the install - _logger.LogInformation("Starting unattended install."); - - try - { - database.BeginTransaction(); - var creator = _databaseSchemaCreatorFactory.Create(database); - creator.InitializeDatabaseSchema(); - database.CompleteTransaction(); - _logger.LogInformation("Unattended install completed."); - - // Emit an event with EventAggregator that unattended install completed - // Then this event can be listened for and create an unattended user - _eventAggregator.Publish(new UnattendedInstallNotification()); - - } - catch (Exception ex) - { - _logger.LogInformation(ex, "Error during unattended install."); - database.AbortTransaction(); - - var innerException = new UnattendedInstallException( - "The database configuration failed with the following message: " + ex.Message - + "\n Please check log file for additional information (can be found in '/App_Data/Logs/')"); - BootFailedException = new BootFailedException(innerException.Message, innerException); - - throw BootFailedException; - } + BootFailedException = new BootFailedException(bootFailedException.Message, bootFailedException); } } @@ -328,7 +252,7 @@ namespace Umbraco.Cms.Core var result = new List(packageMigrationPlans.Count); - foreach(PackageMigrationPlan plan in packageMigrationPlans) + foreach (PackageMigrationPlan plan in packageMigrationPlans) { string currentMigrationState = null; var planKeyValueKey = Constants.Conventions.Migrations.KeyValuePrefix + plan.Name;