Merge pull request #10880 from umbraco/v9/bugfix/suppress-notifications-migrations

Suppress scope notifications during migrations
This commit is contained in:
Paul Johnson
2021-08-23 13:25:47 +01:00
committed by GitHub
19 changed files with 259 additions and 163 deletions

View File

@@ -1,5 +1,6 @@
namespace Umbraco.Cms.Core.Notifications
{
/// <summary>
/// Used to notify when the core runtime can do an unattended upgrade.
/// </summary>

View File

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

View File

@@ -29,6 +29,8 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddTransient<InstallStepCollection>();
builder.Services.AddUnique<InstallHelper>();
builder.Services.AddTransient<PackageMigrationRunner>();
return builder;
}
}

View File

@@ -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<InstallSetupResult>(null);
return Task.FromResult((InstallSetupResult)null);
}
public override bool RequiresExecution(object model)

View File

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

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,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
{
/// <summary>
/// Handles <see cref="RuntimeUnattendedUpgradeNotification"/> to execute the unattended Umbraco upgrader
/// or the unattended Package migrations runner.
/// </summary>
public class UnattendedUpgrader : INotificationAsyncHandler<RuntimeUnattendedUpgradeNotification>
{
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<Exception>();
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<UnattendedUpgrader>(
"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<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

@@ -1,3 +1,4 @@
using System.Threading.Tasks;
using Umbraco.Cms.Infrastructure.Migrations;
namespace Umbraco.Cms.Core.Migrations

View File

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

View File

@@ -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<TMigration>()
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;
}

View File

@@ -16,7 +16,10 @@ namespace Umbraco.Cms.Infrastructure.Migrations
private readonly IMigrationBuilder _migrationBuilder;
private readonly ILogger<MigrationPlanExecutor> _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<MigrationContext>());
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<MigrationContext>());
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<Type>();
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<Type>();
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;
}
}
}

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

@@ -7,7 +7,7 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
{
/// <summary>
/// Represents an upgrader.
/// Used to run a <see cref="MigrationPlan"/>
/// </summary>
public class Upgrader
{
@@ -36,13 +36,13 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
/// </summary>
/// <param name="scopeProvider">A scope provider.</param>
/// <param name="keyValueService">A key-value service.</param>
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);
}
}
}

View File

@@ -3,6 +3,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;

View File

@@ -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>();
IScope scope = Mock.Of<IScope>(x => x.Notifications == Mock.Of<IScopedNotificationPublisher>());
Mock.Get(scope)
.Setup(x => x.Database)
.Returns(database);

View File

@@ -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>();
IScope scope = Mock.Of<IScope>(x => x.Notifications == Mock.Of<IScopedNotificationPublisher>());
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>();
IScope scope = Mock.Of<IScope>(x => x.Notifications == Mock.Of<IScopedNotificationPublisher>());
Mock.Get(scope)
.Setup(x => x.Database)
.Returns(database);

View File

@@ -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<PackageController> _logger;
public PackageController(
IPackagingService packagingService,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IKeyValueService keyValueService,
PendingPackageMigrations pendingPackageMigrations,
PackageMigrationPlanCollection packageMigrationPlans,
IMigrationPlanExecutor migrationPlanExecutor,
IScopeProvider scopeProvider,
PackageMigrationRunner packageMigrationRunner,
ILogger<PackageController> 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<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
{
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)