diff --git a/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs b/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs
index 50c02cbad5..4d676f68ce 100644
--- a/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs
+++ b/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs
@@ -1,5 +1,6 @@
namespace Umbraco.Cms.Core.Notifications
{
+
///
/// Used to notify when the core runtime can do an unattended upgrade.
///
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index e216516437..7e71756379 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -61,6 +61,5 @@
-
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs
index 2218f14df3..990b158ef3 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs
@@ -29,6 +29,8 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddTransient();
builder.Services.AddUnique();
+ builder.Services.AddTransient();
+
return builder;
}
}
diff --git a/src/Umbraco.Infrastructure/Install/InstallSteps/DatabaseUpgradeStep.cs b/src/Umbraco.Infrastructure/Install/InstallSteps/DatabaseUpgradeStep.cs
index 9e11cbd51f..aba2cdd9f4 100644
--- a/src/Umbraco.Infrastructure/Install/InstallSteps/DatabaseUpgradeStep.cs
+++ b/src/Umbraco.Infrastructure/Install/InstallSteps/DatabaseUpgradeStep.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@@ -61,7 +61,7 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
}
}
- return Task.FromResult(null);
+ return Task.FromResult((InstallSetupResult)null);
}
public override bool RequiresExecution(object model)
diff --git a/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs
index a7ddf3378f..bf8aaa54ac 100644
--- a/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs
+++ b/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs
@@ -137,6 +137,9 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
{
var installState = InstallState.Unknown;
+ // TODO: we need to do a null check here since this could be entirely missing and we end up with a null ref
+ // exception in the installer.
+
var databaseSettings = _connectionStrings.UmbracoConnectionString;
var hasConnString = databaseSettings != null && _databaseBuilder.IsDatabaseConfigured;
diff --git a/src/Umbraco.Infrastructure/Install/PackageMigrationRunner.cs b/src/Umbraco.Infrastructure/Install/PackageMigrationRunner.cs
new file mode 100644
index 0000000000..54448c68c0
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Install/PackageMigrationRunner.cs
@@ -0,0 +1,108 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using Umbraco.Cms.Core.Events;
+using Umbraco.Cms.Core.Logging;
+using Umbraco.Cms.Core.Packaging;
+using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Infrastructure.Migrations.Upgrade;
+using Umbraco.Extensions;
+using Umbraco.Cms.Core.Migrations;
+using Umbraco.Cms.Core.Scoping;
+using Umbraco.Cms.Infrastructure.Migrations;
+using Umbraco.Cms.Infrastructure.Migrations.Notifications;
+using Umbraco.Cms.Core;
+
+namespace Umbraco.Cms.Infrastructure.Install
+{
+ ///
+ /// Runs the package migration plans
+ ///
+ public class PackageMigrationRunner
+ {
+ private readonly IProfilingLogger _profilingLogger;
+ private readonly IScopeProvider _scopeProvider;
+ private readonly PendingPackageMigrations _pendingPackageMigrations;
+ private readonly IMigrationPlanExecutor _migrationPlanExecutor;
+ private readonly IKeyValueService _keyValueService;
+ private readonly IEventAggregator _eventAggregator;
+ private readonly Dictionary _packageMigrationPlans;
+
+ public PackageMigrationRunner(
+ IProfilingLogger profilingLogger,
+ IScopeProvider scopeProvider,
+ PendingPackageMigrations pendingPackageMigrations,
+ PackageMigrationPlanCollection packageMigrationPlans,
+ IMigrationPlanExecutor migrationPlanExecutor,
+ IKeyValueService keyValueService,
+ IEventAggregator eventAggregator)
+ {
+ _profilingLogger = profilingLogger;
+ _scopeProvider = scopeProvider;
+ _pendingPackageMigrations = pendingPackageMigrations;
+ _migrationPlanExecutor = migrationPlanExecutor;
+ _keyValueService = keyValueService;
+ _eventAggregator = eventAggregator;
+ _packageMigrationPlans = packageMigrationPlans.ToDictionary(x => x.Name);
+ }
+
+ ///
+ /// Runs all migration plans for a package name if any are pending.
+ ///
+ ///
+ ///
+ public IEnumerable RunPackageMigrationsIfPending(string packageName)
+ {
+ IReadOnlyDictionary keyValues = _keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix);
+ IReadOnlyList pendingMigrations = _pendingPackageMigrations.GetPendingPackageMigrations(keyValues);
+
+ IEnumerable packagePlans = _packageMigrationPlans.Values
+ .Where(x => x.PackageName.InvariantEquals(packageName))
+ .Where(x => pendingMigrations.Contains(x.Name))
+ .Select(x => x.Name);
+
+ return RunPackagePlans(packagePlans);
+ }
+
+ ///
+ /// Runs the all specified package migration plans and publishes a
+ /// if all are successful.
+ ///
+ ///
+ ///
+ /// If any plan fails it will throw an exception.
+ public IEnumerable RunPackagePlans(IEnumerable plansToRun)
+ {
+ var results = new List();
+
+ // Create an explicit scope around all package migrations so they are
+ // all executed in a single transaction. If one package migration fails,
+ // none of them will be committed. This is intended behavior so we can
+ // ensure when we publish the success notification that is is done when they all succeed.
+ using (IScope scope = _scopeProvider.CreateScope(autoComplete: true))
+ {
+ foreach (var migrationName in plansToRun)
+ {
+ if (!_packageMigrationPlans.TryGetValue(migrationName, out PackageMigrationPlan plan))
+ {
+ throw new InvalidOperationException("Cannot find package migration plan " + migrationName);
+ }
+
+ using (_profilingLogger.TraceDuration(
+ "Starting unattended package migration for " + migrationName,
+ "Unattended upgrade completed for " + migrationName))
+ {
+ var upgrader = new Upgrader(plan);
+ // This may throw, if so the transaction will be rolled back
+ results.Add(upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService));
+ }
+ }
+ }
+
+ var executedPlansNotification = new MigrationPlansExecutedNotification(results);
+ _eventAggregator.Publish(executedPlansNotification);
+
+ return results;
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs b/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs
index 24cbce273f..5f5c8f16a8 100644
--- a/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs
+++ b/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs
@@ -1,5 +1,4 @@
using System;
-using System.Linq;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -8,47 +7,40 @@ using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Exceptions;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Notifications;
-using Umbraco.Cms.Core.Packaging;
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;
-using Umbraco.Cms.Core.Migrations;
-using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core;
+using Umbraco.Cms.Infrastructure.Migrations;
namespace Umbraco.Cms.Infrastructure.Install
{
+ ///
+ /// Handles to execute the unattended Umbraco upgrader
+ /// or the unattended Package migrations runner.
+ ///
public class UnattendedUpgrader : INotificationAsyncHandler
{
private readonly IProfilingLogger _profilingLogger;
private readonly IUmbracoVersion _umbracoVersion;
private readonly DatabaseBuilder _databaseBuilder;
private readonly IRuntimeState _runtimeState;
- private readonly PackageMigrationPlanCollection _packageMigrationPlans;
- private readonly IMigrationPlanExecutor _migrationPlanExecutor;
- private readonly IScopeProvider _scopeProvider;
- private readonly IKeyValueService _keyValueService;
+ private readonly PackageMigrationRunner _packageMigrationRunner;
public UnattendedUpgrader(
IProfilingLogger profilingLogger,
IUmbracoVersion umbracoVersion,
DatabaseBuilder databaseBuilder,
IRuntimeState runtimeState,
- PackageMigrationPlanCollection packageMigrationPlans,
- IMigrationPlanExecutor migrationPlanExecutor,
- IScopeProvider scopeProvider,
- IKeyValueService keyValueService)
+ PackageMigrationRunner packageMigrationRunner)
{
_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));
- _packageMigrationPlans = packageMigrationPlans;
- _migrationPlanExecutor = migrationPlanExecutor;
- _scopeProvider = scopeProvider;
- _keyValueService = keyValueService;
+ _packageMigrationRunner = packageMigrationRunner;
}
public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, CancellationToken cancellationToken)
@@ -69,7 +61,6 @@ namespace Umbraco.Cms.Infrastructure.Install
{
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;
@@ -83,73 +74,36 @@ namespace Umbraco.Cms.Infrastructure.Install
{
throw new InvalidOperationException($"The required key {RuntimeState.PendingPacakgeMigrationsStateKey} does not exist in startup state");
}
-
+
if (pendingMigrations.Count == 0)
{
throw new InvalidOperationException("No pending migrations found but the runtime level reason is " + Core.RuntimeLevelReason.UpgradePackageMigrations);
}
- var exceptions = new List();
- var packageMigrationsPlans = _packageMigrationPlans.ToDictionary(x => x.Name);
-
- foreach (var migrationName in pendingMigrations)
- {
- if (!packageMigrationsPlans.TryGetValue(migrationName, out PackageMigrationPlan plan))
- {
- throw new InvalidOperationException("Cannot find package migration plan " + migrationName);
- }
-
- using (_profilingLogger.TraceDuration(
- "Starting unattended package migration for " + migrationName,
- "Unattended upgrade completed for " + migrationName))
- {
- var upgrader = new Upgrader(plan);
-
- try
- {
- upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService);
- }
- catch (Exception ex)
- {
- exceptions.Add(new UnattendedInstallException("Unattended package migration failed for " + migrationName, ex));
- }
- }
- }
-
- if (exceptions.Count > 0)
- {
- notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors;
- SetRuntimeErrors(exceptions);
- }
- else
+ try
{
+ IEnumerable result = _packageMigrationRunner.RunPackagePlans(pendingMigrations);
notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.PackageMigrationComplete;
}
+ catch (Exception ex )
+ {
+ SetRuntimeError(ex);
+ notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors;
+ }
}
break;
default:
throw new InvalidOperationException("Invalid reason " + _runtimeState.Reason);
}
}
+
return Task.CompletedTask;
}
- private void SetRuntimeErrors(List exception)
- {
- Exception innerException;
- if (exception.Count == 1)
- {
- innerException = exception[0];
- }
- else
- {
- innerException = new AggregateException(exception);
- }
-
- _runtimeState.Configure(
+ private void SetRuntimeError(Exception exception)
+ => _runtimeState.Configure(
RuntimeLevel.BootFailed,
RuntimeLevelReason.BootFailedOnException,
- innerException);
- }
+ exception);
}
}
diff --git a/src/Umbraco.Infrastructure/Migrations/ExecutedMigrationPlan.cs b/src/Umbraco.Infrastructure/Migrations/ExecutedMigrationPlan.cs
new file mode 100644
index 0000000000..9979da1e40
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Migrations/ExecutedMigrationPlan.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Umbraco.Cms.Infrastructure.Migrations
+{
+ public class ExecutedMigrationPlan
+ {
+ public ExecutedMigrationPlan(MigrationPlan plan, string initialState, string finalState)
+ {
+ Plan = plan;
+ InitialState = initialState ?? throw new ArgumentNullException(nameof(initialState));
+ FinalState = finalState ?? throw new ArgumentNullException(nameof(finalState));
+ }
+
+ public MigrationPlan Plan { get; }
+ public string InitialState { get; }
+ public string FinalState { get; }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Migrations/IMigrationExpression.cs b/src/Umbraco.Infrastructure/Migrations/IMigrationExpression.cs
index 30d3385632..3a5a4649fe 100644
--- a/src/Umbraco.Infrastructure/Migrations/IMigrationExpression.cs
+++ b/src/Umbraco.Infrastructure/Migrations/IMigrationExpression.cs
@@ -1,4 +1,4 @@
-namespace Umbraco.Cms.Infrastructure.Migrations
+namespace Umbraco.Cms.Infrastructure.Migrations
{
///
/// Marker interface for migration expressions
diff --git a/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs b/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs
index 4610e02d60..41a831360a 100644
--- a/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs
+++ b/src/Umbraco.Infrastructure/Migrations/IMigrationPlanExecutor.cs
@@ -1,3 +1,4 @@
+using System.Threading.Tasks;
using Umbraco.Cms.Infrastructure.Migrations;
namespace Umbraco.Cms.Core.Migrations
diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs
index 691400121f..b7437f4c2d 100644
--- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration;
diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs b/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs
index 9d0f110a74..bdb5aeb780 100644
--- a/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs
+++ b/src/Umbraco.Infrastructure/Migrations/MigrationPlan.cs
@@ -1,8 +1,6 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
-using Microsoft.Extensions.Logging;
-using Umbraco.Cms.Core.Scoping;
using Umbraco.Extensions;
using Type = System.Type;
@@ -217,6 +215,11 @@ namespace Umbraco.Cms.Infrastructure.Migrations
public virtual MigrationPlan AddPostMigration()
where TMigration : MigrationBase
{
+ // TODO: Post migrations are obsolete/irrelevant. Notifications should be used instead.
+ // The only place we use this is to clear cookies in the installer which could be done
+ // via notification. Then we can clean up all the code related to post migrations which is
+ // not insignificant.
+
_postMigrationTypes.Add(typeof(TMigration));
return this;
}
diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs
index 51fc613c21..09cddbc20b 100644
--- a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs
+++ b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs
@@ -16,7 +16,10 @@ namespace Umbraco.Cms.Infrastructure.Migrations
private readonly IMigrationBuilder _migrationBuilder;
private readonly ILogger _logger;
- public MigrationPlanExecutor(IScopeProvider scopeProvider, ILoggerFactory loggerFactory, IMigrationBuilder migrationBuilder)
+ public MigrationPlanExecutor(
+ IScopeProvider scopeProvider,
+ ILoggerFactory loggerFactory,
+ IMigrationBuilder migrationBuilder)
{
_scopeProvider = scopeProvider;
_loggerFactory = loggerFactory;
@@ -40,51 +43,58 @@ namespace Umbraco.Cms.Infrastructure.Migrations
_logger.LogInformation("Starting '{MigrationName}'...", plan.Name);
- var origState = fromState ?? string.Empty;
+ fromState ??= string.Empty;
+ var nextState = fromState;
- _logger.LogInformation("At {OrigState}", string.IsNullOrWhiteSpace(origState) ? "origin" : origState);
+ _logger.LogInformation("At {OrigState}", string.IsNullOrWhiteSpace(nextState) ? "origin" : nextState);
- if (!plan.Transitions.TryGetValue(origState, out MigrationPlan.Transition transition))
+ if (!plan.Transitions.TryGetValue(nextState, out MigrationPlan.Transition transition))
{
- plan.ThrowOnUnknownInitialState(origState);
+ plan.ThrowOnUnknownInitialState(nextState);
}
using (IScope scope = _scopeProvider.CreateScope(autoComplete: true))
{
- var context = new MigrationContext(plan, scope.Database, _loggerFactory.CreateLogger());
-
- while (transition != null)
+ // We want to suppress scope (service, etc...) notifications during a migration plan
+ // execution. This is because if a package that doesn't have their migration plan
+ // executed is listening to service notifications to perform some persistence logic,
+ // that packages notification handlers may explode because that package isn't fully installed yet.
+ using (scope.Notifications.Suppress())
{
- _logger.LogInformation("Execute {MigrationType}", transition.MigrationType.Name);
+ var context = new MigrationContext(plan, scope.Database, _loggerFactory.CreateLogger());
- var migration = _migrationBuilder.Build(transition.MigrationType, context);
- migration.Run();
-
- var nextState = transition.TargetState;
- origState = nextState;
-
- _logger.LogInformation("At {OrigState}", origState);
-
- // throw a raw exception here: this should never happen as the plan has
- // been validated - this is just a paranoid safety test
- if (!plan.Transitions.TryGetValue(origState, out transition))
+ while (transition != null)
{
- throw new InvalidOperationException($"Unknown state \"{origState}\".");
+ _logger.LogInformation("Execute {MigrationType}", transition.MigrationType.Name);
+
+ var migration = _migrationBuilder.Build(transition.MigrationType, context);
+ migration.Run();
+
+ nextState = transition.TargetState;
+
+ _logger.LogInformation("At {OrigState}", nextState);
+
+ // throw a raw exception here: this should never happen as the plan has
+ // been validated - this is just a paranoid safety test
+ if (!plan.Transitions.TryGetValue(nextState, out transition))
+ {
+ throw new InvalidOperationException($"Unknown state \"{nextState}\".");
+ }
}
- }
- // prepare and de-duplicate post-migrations, only keeping the 1st occurence
- var temp = new HashSet();
- var postMigrationTypes = context.PostMigrations
- .Where(x => !temp.Contains(x))
- .Select(x => { temp.Add(x); return x; });
+ // prepare and de-duplicate post-migrations, only keeping the 1st occurence
+ var temp = new HashSet();
+ var postMigrationTypes = context.PostMigrations
+ .Where(x => !temp.Contains(x))
+ .Select(x => { temp.Add(x); return x; });
- // run post-migrations
- foreach (var postMigrationType in postMigrationTypes)
- {
- _logger.LogInformation($"PostMigration: {postMigrationType.FullName}.");
- var postMigration = _migrationBuilder.Build(postMigrationType, context);
- postMigration.Run();
+ // run post-migrations
+ foreach (var postMigrationType in postMigrationTypes)
+ {
+ _logger.LogInformation($"PostMigration: {postMigrationType.FullName}.");
+ var postMigration = _migrationBuilder.Build(postMigrationType, context);
+ postMigration.Run();
+ }
}
}
@@ -93,12 +103,12 @@ namespace Umbraco.Cms.Infrastructure.Migrations
// safety check - again, this should never happen as the plan has been validated,
// and this is just a paranoid safety test
var finalState = plan.FinalState;
- if (origState != finalState)
+ if (nextState != finalState)
{
- throw new InvalidOperationException($"Internal error, reached state {origState} which is not final state {finalState}");
+ throw new InvalidOperationException($"Internal error, reached state {nextState} which is not final state {finalState}");
}
- return origState;
+ return nextState;
}
}
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Notifications/MigrationPlansExecutedNotification.cs b/src/Umbraco.Infrastructure/Migrations/Notifications/MigrationPlansExecutedNotification.cs
new file mode 100644
index 0000000000..50ee5c3582
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Migrations/Notifications/MigrationPlansExecutedNotification.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using Umbraco.Cms.Core.Notifications;
+
+namespace Umbraco.Cms.Infrastructure.Migrations.Notifications
+{
+ ///
+ /// Published when one or more migration plans have been successfully executed.
+ ///
+ public class MigrationPlansExecutedNotification : INotification
+ {
+ public MigrationPlansExecutedNotification(IReadOnlyList executedPlans)
+ => ExecutedPlans = executedPlans;
+
+ public IReadOnlyList ExecutedPlans { get; }
+
+
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs
index fc0e01c3d9..afc4fdec81 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/Upgrader.cs
@@ -7,7 +7,7 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
{
///
- /// Represents an upgrader.
+ /// Used to run a
///
public class Upgrader
{
@@ -36,13 +36,13 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
///
/// A scope provider.
/// A key-value service.
- public void Execute(IMigrationPlanExecutor migrationPlanExecutor, IScopeProvider scopeProvider, IKeyValueService keyValueService)
+ public ExecutedMigrationPlan Execute(IMigrationPlanExecutor migrationPlanExecutor, IScopeProvider scopeProvider, IKeyValueService keyValueService)
{
if (scopeProvider == null) throw new ArgumentNullException(nameof(scopeProvider));
if (keyValueService == null) throw new ArgumentNullException(nameof(keyValueService));
- using (var scope = scopeProvider.CreateScope())
- {
+ using (IScope scope = scopeProvider.CreateScope())
+ {
// read current state
var currentState = keyValueService.GetValue(StateValueKey);
var forceState = false;
@@ -51,13 +51,13 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
{
currentState = Plan.InitialState;
forceState = true;
- }
+ }
// execute plan
var state = migrationPlanExecutor.Execute(Plan, currentState);
if (string.IsNullOrWhiteSpace(state))
{
- throw new Exception("Plan execution returned an invalid null or empty state.");
+ throw new InvalidOperationException("Plan execution returned an invalid null or empty state.");
}
// save new state
@@ -69,8 +69,10 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
{
keyValueService.SetValue(StateValueKey, currentState, state);
}
-
+
scope.Complete();
+
+ return new ExecutedMigrationPlan(Plan, currentState, state);
}
}
}
diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs
index b41e4dce49..40dbad176e 100644
--- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs
+++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs
index 26616d9d69..08bcee255e 100644
--- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
@@ -11,6 +12,7 @@ using Moq;
using NPoco;
using NUnit.Framework;
using Umbraco.Cms.Core.Configuration.Models;
+using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Migrations;
@@ -32,7 +34,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations
NullLoggerFactory loggerFactory = NullLoggerFactory.Instance;
var database = new TestDatabase();
- IScope scope = Mock.Of();
+ IScope scope = Mock.Of(x => x.Notifications == Mock.Of());
Mock.Get(scope)
.Setup(x => x.Database)
.Returns(database);
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PostMigrationTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PostMigrationTests.cs
index 2b6ff721eb..a61de49ee5 100644
--- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PostMigrationTests.cs
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PostMigrationTests.cs
@@ -1,7 +1,8 @@
-// Copyright (c) Umbraco.
+// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
@@ -9,6 +10,7 @@ using Moq;
using NPoco;
using NUnit.Framework;
using Umbraco.Cms.Core.Configuration.Models;
+using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Migrations;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
@@ -47,7 +49,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations
});
var database = new TestDatabase();
- IScope scope = Mock.Of();
+ IScope scope = Mock.Of(x => x.Notifications == Mock.Of());
Mock.Get(scope)
.Setup(x => x.Database)
.Returns(database);
@@ -96,7 +98,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations
});
var database = new TestDatabase();
- IScope scope = Mock.Of();
+ IScope scope = Mock.Of(x => x.Notifications == Mock.Of());
Mock.Get(scope)
.Setup(x => x.Database)
.Returns(database);
diff --git a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs
index dca3a320ec..813682c70e 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs
@@ -7,21 +7,15 @@ using System.Text;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
-using Umbraco.Extensions;
-using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Packaging;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
-using Umbraco.Cms.Web.BackOffice.Extensions;
using Umbraco.Cms.Web.Common.ActionsResults;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
using Constants = Umbraco.Cms.Core.Constants;
-using Umbraco.Cms.Infrastructure.Migrations.Upgrade;
-using Umbraco.Cms.Core.Migrations;
-using Umbraco.Cms.Core.Scoping;
using Microsoft.Extensions.Logging;
-using System.Numerics;
+using Umbraco.Cms.Infrastructure.Install;
namespace Umbraco.Cms.Web.BackOffice.Controllers
{
@@ -34,30 +28,18 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
private readonly IPackagingService _packagingService;
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
- private readonly IKeyValueService _keyValueService;
- private readonly PendingPackageMigrations _pendingPackageMigrations;
- private readonly PackageMigrationPlanCollection _packageMigrationPlans;
- private readonly IMigrationPlanExecutor _migrationPlanExecutor;
- private readonly IScopeProvider _scopeProvider;
+ private readonly PackageMigrationRunner _packageMigrationRunner;
private readonly ILogger _logger;
public PackageController(
IPackagingService packagingService,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
- IKeyValueService keyValueService,
- PendingPackageMigrations pendingPackageMigrations,
- PackageMigrationPlanCollection packageMigrationPlans,
- IMigrationPlanExecutor migrationPlanExecutor,
- IScopeProvider scopeProvider,
+ PackageMigrationRunner packageMigrationRunner,
ILogger logger)
{
_packagingService = packagingService ?? throw new ArgumentNullException(nameof(packagingService));
_backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor));
- _keyValueService = keyValueService;
- _pendingPackageMigrations = pendingPackageMigrations;
- _packageMigrationPlans = packageMigrationPlans;
- _migrationPlanExecutor = migrationPlanExecutor;
- _scopeProvider = scopeProvider;
+ _packageMigrationRunner = packageMigrationRunner;
_logger = logger;
}
@@ -119,33 +101,22 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
[HttpPost]
public ActionResult> RunMigrations([FromQuery]string packageName)
{
- IReadOnlyDictionary keyValues = _keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix);
- IReadOnlyList pendingMigrations = _pendingPackageMigrations.GetPendingPackageMigrations(keyValues);
- foreach(PackageMigrationPlan plan in _packageMigrationPlans.Where(x => x.PackageName.InvariantEquals(packageName)))
+ try
{
- if (pendingMigrations.Contains(plan.Name))
- {
- var upgrader = new Upgrader(plan);
-
- try
- {
- upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Package migration failed on package {Package} for plan {Plan}", packageName, plan.Name);
-
- return ValidationErrorResult.CreateNotificationValidationErrorResult(
- $"Package migration failed on package {packageName} for plan {plan.Name} with error: {ex.Message}. Check log for full details.");
- }
- }
+ _packageMigrationRunner.RunPackageMigrationsIfPending(packageName);
+ return _packagingService.GetAllInstalledPackages().ToList();
}
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Package migration failed on package {Package}", packageName);
- return _packagingService.GetAllInstalledPackages().ToList();
+ return ValidationErrorResult.CreateNotificationValidationErrorResult(
+ $"Package migration failed on package {packageName} with error: {ex.Message}. Check log for full details.");
+ }
}
[HttpGet]
- public IActionResult DownloadCreatedPackage(int id)
+ public IActionResult DownloadCreatedPackage(int id)
{
var package = _packagingService.GetCreatedPackageById(id);
if (package == null)