Updates how package migrations are run. Only publish a single notification.

This commit is contained in:
Shannon
2021-08-18 12:01:56 -06:00
parent 718a9e9593
commit 8ede33f6ba
17 changed files with 210 additions and 178 deletions

View File

@@ -1,19 +0,0 @@
namespace Umbraco.Cms.Core.Notifications
{
/// <summary>
/// Published when a migration plan has been successfully executed.
/// </summary>
public class MigrationPlanExecuted : INotification
{
public MigrationPlanExecuted(string migrationPlanName, string initialState, string finalState)
{
MigrationPlanName = migrationPlanName;
InitialState = initialState;
FinalState = finalState;
}
public string MigrationPlanName { get; }
public string InitialState { get; }
public string FinalState { get; }
}
}

View File

@@ -1,19 +0,0 @@
using System.Collections.Generic;
namespace Umbraco.Cms.Core.Notifications
{
/// <summary>
/// Published when unattended package migrations have been successfully executed
/// </summary>
public class UnattendedPackageMigrationsExecutedNotification : INotification
{
public UnattendedPackageMigrationsExecutedNotification(IReadOnlyList<string> packageMigrations)
=> PackageMigrations = packageMigrations;
/// <summary>
/// The list of package migration names that have been executed.
/// </summary>
public IReadOnlyList<string> PackageMigrations { get; }
}
}

View File

@@ -61,6 +61,5 @@
</ItemGroup>
<ItemGroup>
<Folder Include="ContentEditing" />
</ItemGroup>
</Project>

View File

@@ -40,7 +40,7 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
_connectionStrings = connectionStrings.Value ?? throw new ArgumentNullException(nameof(connectionStrings));
}
public override async Task<InstallSetupResult> ExecuteAsync(object model)
public override Task<InstallSetupResult> ExecuteAsync(object model)
{
var installSteps = InstallStatusTracker.GetStatus().ToArray();
var previousStep = installSteps.Single(x => x.Name == "DatabaseInstall");
@@ -53,7 +53,7 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
var plan = new UmbracoPlan(_umbracoVersion);
plan.AddPostMigration<ClearCsrfCookies>(); // needed when running installer (back-office)
var result = await _databaseBuilder.UpgradeSchemaAndDataAsync(plan);
var result = _databaseBuilder.UpgradeSchemaAndData(plan);
if (result.Success == false)
{
@@ -61,7 +61,7 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
}
}
return null;
return Task.FromResult((InstallSetupResult)null);
}
public override bool RequiresExecution(object model)

View File

@@ -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
{
/// <summary>
/// Runs the package migration plans
/// </summary>
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<string, PackageMigrationPlan> _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);
}
/// <summary>
/// Runs all migration plans for a package name if any are pending.
/// </summary>
/// <param name="packageName"></param>
/// <returns></returns>
public IEnumerable<ExecutedMigrationPlan> RunPackageMigrationsIfPending(string packageName)
{
IReadOnlyDictionary<string, string> keyValues = _keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix);
IReadOnlyList<string> pendingMigrations = _pendingPackageMigrations.GetPendingPackageMigrations(keyValues);
IEnumerable<string> packagePlans = _packageMigrationPlans.Values
.Where(x => x.PackageName.InvariantEquals(packageName))
.Where(x => pendingMigrations.Contains(x.Name))
.Select(x => x.Name);
return RunPackagePlans(packagePlans);
}
/// <summary>
/// Runs the all specified package migration plans and publishes a <see cref="MigrationPlansExecutedNotification"/>
/// if all are successful.
/// </summary>
/// <param name="plansToRun"></param>
/// <returns></returns>
/// <exception cref="Exception">If any plan fails it will throw an exception.</exception>
public IEnumerable<ExecutedMigrationPlan> RunPackagePlans(IEnumerable<string> plansToRun)
{
var results = new List<ExecutedMigrationPlan>();
// 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<PackageMigrationRunner>(
"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;
}
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -8,15 +7,13 @@ 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
{
@@ -30,35 +27,23 @@ namespace Umbraco.Cms.Infrastructure.Install
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 IEventAggregator _eventAggregator;
private readonly PackageMigrationRunner _packageMigrationRunner;
public UnattendedUpgrader(
IProfilingLogger profilingLogger,
IUmbracoVersion umbracoVersion,
DatabaseBuilder databaseBuilder,
IRuntimeState runtimeState,
PackageMigrationPlanCollection packageMigrationPlans,
IMigrationPlanExecutor migrationPlanExecutor,
IScopeProvider scopeProvider,
IKeyValueService keyValueService,
IEventAggregator eventAggregator)
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;
_eventAggregator = eventAggregator;
_packageMigrationRunner = packageMigrationRunner;
}
public async Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, CancellationToken cancellationToken)
public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, CancellationToken cancellationToken)
{
if (_runtimeState.RunUnattendedBootLogic())
{
@@ -71,7 +56,7 @@ namespace Umbraco.Cms.Infrastructure.Install
"Starting unattended upgrade.",
"Unattended upgrade completed."))
{
DatabaseBuilder.Result result = await _databaseBuilder.UpgradeSchemaAndDataAsync(plan);
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);
@@ -95,74 +80,30 @@ namespace Umbraco.Cms.Infrastructure.Install
throw new InvalidOperationException("No pending migrations found but the runtime level reason is " + Core.RuntimeLevelReason.UpgradePackageMigrations);
}
var exceptions = new List<Exception>();
var packageMigrationsPlans = _packageMigrationPlans.ToDictionary(x => x.Name);
// Create an explicit scope around all package migrations so they are
// all executed in a single transaction.
using (IScope scope = _scopeProvider.CreateScope(autoComplete: true))
try
{
foreach (var migrationName in pendingMigrations)
{
if (!packageMigrationsPlans.TryGetValue(migrationName, out PackageMigrationPlan plan))
{
throw new InvalidOperationException("Cannot find package migration plan " + migrationName);
}
using (_profilingLogger.TraceDuration<UnattendedUpgrader>(
"Starting unattended package migration for " + migrationName,
"Unattended upgrade completed for " + migrationName))
{
var upgrader = new Upgrader(plan);
try
{
await upgrader.ExecuteAsync(_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
{
var packageMigrationsExecutedNotification = new UnattendedPackageMigrationsExecutedNotification(pendingMigrations);
await _eventAggregator.PublishAsync(packageMigrationsExecutedNotification);
IEnumerable<ExecutedMigrationPlan> 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)
{
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);
}
}

View File

@@ -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; }
}
}

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Cms.Infrastructure.Migrations
namespace Umbraco.Cms.Infrastructure.Migrations
{
/// <summary>
/// Marker interface for migration expressions

View File

@@ -5,6 +5,6 @@ namespace Umbraco.Cms.Core.Migrations
{
public interface IMigrationPlanExecutor
{
Task<string> ExecuteAsync(MigrationPlan plan, string fromState);
string Execute(MigrationPlan plan, string fromState);
}
}

View File

@@ -413,7 +413,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
/// configured and it is possible to connect to the database.</para>
/// <para>Runs whichever migrations need to run.</para>
/// </remarks>
public async Task<Result> UpgradeSchemaAndDataAsync(UmbracoPlan plan)
public Result UpgradeSchemaAndData(UmbracoPlan plan)
{
try
{
@@ -427,7 +427,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
// upgrade
var upgrader = new Upgrader(plan);
await upgrader.ExecuteAsync(_migrationPlanExecutor, _scopeProvider, _keyValueService);
upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService);
var message = "<p>Upgrade completed!</p>";

View File

@@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Migrations;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Extensions;
using Type = System.Type;
@@ -17,19 +14,16 @@ namespace Umbraco.Cms.Infrastructure.Migrations
private readonly IScopeProvider _scopeProvider;
private readonly ILoggerFactory _loggerFactory;
private readonly IMigrationBuilder _migrationBuilder;
private readonly IEventAggregator _eventAggregator;
private readonly ILogger<MigrationPlanExecutor> _logger;
public MigrationPlanExecutor(
IScopeProvider scopeProvider,
ILoggerFactory loggerFactory,
IMigrationBuilder migrationBuilder,
IEventAggregator eventAggregator)
IMigrationBuilder migrationBuilder)
{
_scopeProvider = scopeProvider;
_loggerFactory = loggerFactory;
_migrationBuilder = migrationBuilder;
_eventAggregator = eventAggregator;
_logger = _loggerFactory.CreateLogger<MigrationPlanExecutor>();
}
@@ -43,7 +37,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations
/// <param name="loggerFactory"></param>
/// <returns>The final state.</returns>
/// <remarks>The plan executes within the scope, which must then be completed.</remarks>
public async Task<string> ExecuteAsync(MigrationPlan plan, string fromState)
public string Execute(MigrationPlan plan, string fromState)
{
plan.Validate();
@@ -102,8 +96,6 @@ namespace Umbraco.Cms.Infrastructure.Migrations
postMigration.Run();
}
}
await _eventAggregator.PublishAsync(new MigrationPlanExecuted(plan.Name, fromState, nextState));
}
_logger.LogInformation("Done (pending scope completion).");

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
using Umbraco.Cms.Core.Notifications;
namespace Umbraco.Cms.Infrastructure.Migrations.Notifications
{
/// <summary>
/// Published when one or more migration plans have been successfully executed.
/// </summary>
public class MigrationPlansExecutedNotification : INotification
{
public MigrationPlansExecutedNotification(IReadOnlyList<ExecutedMigrationPlan> executedPlans)
=> ExecutedPlans = executedPlans;
public IReadOnlyList<ExecutedMigrationPlan> ExecutedPlans { get; }
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Threading.Tasks;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Migrations;
using Umbraco.Cms.Core.Scoping;
@@ -37,7 +36,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
/// </summary>
/// <param name="scopeProvider">A scope provider.</param>
/// <param name="keyValueService">A key-value service.</param>
public async Task ExecuteAsync(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));
@@ -52,13 +51,13 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
{
currentState = Plan.InitialState;
forceState = true;
}
}
// execute plan
var state = await migrationPlanExecutor.ExecuteAsync(Plan, currentState);
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
@@ -72,6 +71,8 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
}
scope.Complete();
return new ExecutedMigrationPlan(Plan, currentState, state);
}
}
}

View File

@@ -32,7 +32,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations
private IMigrationPlanExecutor MigrationPlanExecutor => GetRequiredService<IMigrationPlanExecutor>();
[Test]
public async Task CreateTableOfTDto()
public void CreateTableOfTDto()
{
IMigrationBuilder builder = Mock.Of<IMigrationBuilder>();
Mock.Get(builder)
@@ -54,7 +54,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations
.From(string.Empty)
.To<CreateTableOfTDtoMigration>("done"));
await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
var helper = new DatabaseSchemaCreator(scope.Database, LoggerFactory.CreateLogger<DatabaseSchemaCreator>(), LoggerFactory, UmbracoVersion, EventAggregator);
bool exists = helper.TableExists("umbracoUser");
@@ -65,7 +65,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations
}
[Test]
public async Task DeleteKeysAndIndexesOfTDto()
public void DeleteKeysAndIndexesOfTDto()
{
IMigrationBuilder builder = Mock.Of<IMigrationBuilder>();
Mock.Get(builder)
@@ -91,13 +91,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations
.To<CreateTableOfTDtoMigration>("a")
.To<DeleteKeysAndIndexesMigration>("done"));
await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
scope.Complete();
}
}
[Test]
public async Task CreateKeysAndIndexesOfTDto()
public void CreateKeysAndIndexesOfTDto()
{
IMigrationBuilder builder = Mock.Of<IMigrationBuilder>();
Mock.Get(builder)
@@ -126,13 +126,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations
.To<DeleteKeysAndIndexesMigration>("b")
.To<CreateKeysAndIndexesOfTDtoMigration>("done"));
await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
scope.Complete();
}
}
[Test]
public async Task CreateKeysAndIndexes()
public void CreateKeysAndIndexes()
{
IMigrationBuilder builder = Mock.Of<IMigrationBuilder>();
Mock.Get(builder)
@@ -161,13 +161,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations
.To<DeleteKeysAndIndexesMigration>("b")
.To<CreateKeysAndIndexesMigration>("done"));
await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
scope.Complete();
}
}
[Test]
public async Task CreateColumn()
public void CreateColumn()
{
IMigrationBuilder builder = Mock.Of<IMigrationBuilder>();
Mock.Get(builder)
@@ -193,7 +193,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations
.To<CreateTableOfTDtoMigration>("a")
.To<CreateColumnMigration>("done"));
await upgrader.ExecuteAsync(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of<IKeyValueService>());
scope.Complete();
}
}

View File

@@ -29,7 +29,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations
public class MigrationPlanTests
{
[Test]
public async Task CanExecute()
public void CanExecute()
{
NullLoggerFactory loggerFactory = NullLoggerFactory.Instance;
@@ -58,7 +58,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations
}
});
var executor = new MigrationPlanExecutor(scopeProvider, loggerFactory, migrationBuilder, Mock.Of<IEventAggregator>());
var executor = new MigrationPlanExecutor(scopeProvider, loggerFactory, migrationBuilder);
MigrationPlan plan = new MigrationPlan("default")
.From(string.Empty)
@@ -76,7 +76,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations
var sourceState = kvs.GetValue("Umbraco.Tests.MigrationPlan") ?? string.Empty;
// execute plan
state = await executor.ExecuteAsync(plan, sourceState);
state = executor.Execute(plan, sourceState);
// save new state
kvs.SetValue("Umbraco.Tests.MigrationPlan", sourceState, state);

View File

@@ -27,10 +27,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations
{
private static readonly ILoggerFactory s_loggerFactory = NullLoggerFactory.Instance;
private IMigrationPlanExecutor GetMigrationPlanExecutor(IScopeProvider scopeProvider, IMigrationBuilder builder)
=> new MigrationPlanExecutor(scopeProvider, s_loggerFactory, builder, Mock.Of<IEventAggregator>());
=> new MigrationPlanExecutor(scopeProvider, s_loggerFactory, builder);
[Test]
public async Task ExecutesPlanPostMigration()
public void ExecutesPlanPostMigration()
{
IMigrationBuilder builder = Mock.Of<IMigrationBuilder>();
Mock.Get(builder)
@@ -68,7 +68,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations
var upgrader = new Upgrader(plan);
IMigrationPlanExecutor executor = GetMigrationPlanExecutor(scopeProvider, builder);
await upgrader.ExecuteAsync(
upgrader.Execute(
executor,
scopeProvider,
Mock.Of<IKeyValueService>());
@@ -77,7 +77,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations
}
[Test]
public async Task MigrationCanAddPostMigration()
public void MigrationCanAddPostMigration()
{
IMigrationBuilder builder = Mock.Of<IMigrationBuilder>();
Mock.Get(builder)
@@ -119,7 +119,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations
var upgrader = new Upgrader(plan);
IMigrationPlanExecutor executor = GetMigrationPlanExecutor(scopeProvider, builder);
await upgrader.ExecuteAsync(
upgrader.Execute(
executor,
scopeProvider,
Mock.Of<IKeyValueService>());

View File

@@ -23,6 +23,7 @@ using Umbraco.Cms.Core.Scoping;
using Microsoft.Extensions.Logging;
using System.Numerics;
using System.Threading.Tasks;
using Umbraco.Cms.Infrastructure.Install;
namespace Umbraco.Cms.Web.BackOffice.Controllers
{
@@ -39,6 +40,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
private readonly PendingPackageMigrations _pendingPackageMigrations;
private readonly PackageMigrationPlanCollection _packageMigrationPlans;
private readonly IMigrationPlanExecutor _migrationPlanExecutor;
private readonly PackageMigrationRunner _packageMigrationRunner;
private readonly IScopeProvider _scopeProvider;
private readonly ILogger<PackageController> _logger;
@@ -49,6 +51,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
PendingPackageMigrations pendingPackageMigrations,
PackageMigrationPlanCollection packageMigrationPlans,
IMigrationPlanExecutor migrationPlanExecutor,
PackageMigrationRunner packageMigrationRunner,
IScopeProvider scopeProvider,
ILogger<PackageController> logger)
{
@@ -58,6 +61,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
_pendingPackageMigrations = pendingPackageMigrations;
_packageMigrationPlans = packageMigrationPlans;
_migrationPlanExecutor = migrationPlanExecutor;
_packageMigrationRunner = packageMigrationRunner;
_scopeProvider = scopeProvider;
_logger = logger;
}
@@ -118,35 +122,24 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
[HttpPost]
public async Task<ActionResult<IEnumerable<InstalledPackage>>> RunMigrations([FromQuery]string packageName)
public ActionResult<IEnumerable<InstalledPackage>> RunMigrations([FromQuery]string packageName)
{
IReadOnlyDictionary<string, string> keyValues = _keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix);
IReadOnlyList<string> 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
{
await upgrader.ExecuteAsync(_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)